diff --git a/.vscode/settings.json b/.vscode/settings.json
index df7560e217..ddc6cfc49b 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -21,5 +21,6 @@
"editor.codeActionsOnSave": {
"source.fixAll.tslint": true
- }
+ },
+ "typescript.tsdk": "node_modules/typescript/lib"
}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 28923d0106..d6b8792c9c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [2.1.0] - 2019-10-29
+
+### Added
+
+- Block User #481
+
+### Changed
+
+- Apollo with React Hooks! #450
+- Show userName in profile area #479
+- Error Codes: remove `USER_FOLLOW_FAILED`, add `ACTION_FAILED` #480
+
## [2.0.1] - 2019-10-21
### Changed
diff --git a/package-lock.json b/package-lock.json
index 264639590c..aea94fef7e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "matters-web",
- "version": "1.14.1",
+ "version": "2.0.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -61,27 +61,60 @@
}
},
"@apollo/react-components": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@apollo/react-components/-/react-components-3.1.2.tgz",
- "integrity": "sha512-D1habJ8IvylC8KpgzlM6yYskYGcTYuyOU5cPgtluamTc4ro6P/98bILMO4qHDDj0zkBARIgHrf2QV6oityTfvA==",
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@apollo/react-components/-/react-components-3.1.3.tgz",
+ "integrity": "sha512-H0l2JKDQMz+LkM93QK7j3ThbNXkWQCduN3s3eKxFN3Rdg7rXsrikJWvx2wQ868jmqy0VhwJbS1vYdRLdh114uQ==",
"requires": {
- "@apollo/react-common": "^3.1.2",
- "@apollo/react-hooks": "^3.1.2",
+ "@apollo/react-common": "^3.1.3",
+ "@apollo/react-hooks": "^3.1.3",
"prop-types": "^15.7.2",
"ts-invariant": "^0.4.4",
"tslib": "^1.10.0"
+ },
+ "dependencies": {
+ "@apollo/react-common": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@apollo/react-common/-/react-common-3.1.3.tgz",
+ "integrity": "sha512-Q7ZjDOeqjJf/AOGxUMdGxKF+JVClRXrYBGVq+SuVFqANRpd68MxtVV2OjCWavsFAN0eqYnRqRUrl7vtUCiJqeg==",
+ "requires": {
+ "ts-invariant": "^0.4.4",
+ "tslib": "^1.10.0"
+ }
+ },
+ "@apollo/react-hooks": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@apollo/react-hooks/-/react-hooks-3.1.3.tgz",
+ "integrity": "sha512-reIRO9xKdfi+B4gT/o/hnXuopUnm7WED/ru8VQydPw+C/KG/05Ssg1ZdxFKHa3oxwiTUIDnevtccIH35POanbA==",
+ "requires": {
+ "@apollo/react-common": "^3.1.3",
+ "@wry/equality": "^0.1.9",
+ "ts-invariant": "^0.4.4",
+ "tslib": "^1.10.0"
+ }
+ }
}
},
"@apollo/react-hoc": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@apollo/react-hoc/-/react-hoc-3.1.2.tgz",
- "integrity": "sha512-VbykBrxPBurt/yIAK8oFg7ZHL5ls2QI1y93AtLqJNwe4oM0m3oJC2jGIr2jobSVhbmGdzIGWshKJTbtrRowQ3g==",
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@apollo/react-hoc/-/react-hoc-3.1.3.tgz",
+ "integrity": "sha512-oCPma0uBVPTcYTR5sOvtMbpaWll4xDBvYfKr6YkDorUcQVeNzFu1LK1kmQjJP64bKsaziKYji5ibFaeCnVptmA==",
"requires": {
- "@apollo/react-common": "^3.1.2",
- "@apollo/react-components": "^3.1.2",
+ "@apollo/react-common": "^3.1.3",
+ "@apollo/react-components": "^3.1.3",
"hoist-non-react-statics": "^3.3.0",
"ts-invariant": "^0.4.4",
"tslib": "^1.10.0"
+ },
+ "dependencies": {
+ "@apollo/react-common": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@apollo/react-common/-/react-common-3.1.3.tgz",
+ "integrity": "sha512-Q7ZjDOeqjJf/AOGxUMdGxKF+JVClRXrYBGVq+SuVFqANRpd68MxtVV2OjCWavsFAN0eqYnRqRUrl7vtUCiJqeg==",
+ "requires": {
+ "ts-invariant": "^0.4.4",
+ "tslib": "^1.10.0"
+ }
+ }
}
},
"@apollo/react-hooks": {
@@ -109,6 +142,7 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@apollo/react-testing/-/react-testing-3.1.2.tgz",
"integrity": "sha512-nADLe8ju6K7LinKy5bJnN8cXMeje/s7zNIfKESviQz09+AOORFHtDPlgQPwkaogNor4yTKFwrrwtvokhRu8tnQ==",
+ "dev": true,
"requires": {
"@apollo/react-common": "^3.1.2",
"fast-json-stable-stringify": "^2.0.0",
@@ -1683,59 +1717,59 @@
}
},
"@sentry/browser": {
- "version": "5.7.0",
- "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-5.7.0.tgz",
- "integrity": "sha512-hbybYP5onstb8PfqjCubMuXkoXQBjZ3RCaxrOFLOIqpIxajrQ2zmbnaCzfBPWWwKeHa9P+i625OT973OhhHFMA==",
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-5.7.1.tgz",
+ "integrity": "sha512-K0x1XhsHS8PPdtlVOLrKZyYvi5Vexs9WApdd214bO6KaGF296gJvH1mG8XOY0+7aA5i2A7T3ttcaJNDYS49lzw==",
"requires": {
- "@sentry/core": "5.7.0",
- "@sentry/types": "5.7.0",
- "@sentry/utils": "5.7.0",
+ "@sentry/core": "5.7.1",
+ "@sentry/types": "5.7.1",
+ "@sentry/utils": "5.7.1",
"tslib": "^1.9.3"
}
},
"@sentry/core": {
- "version": "5.7.0",
- "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.7.0.tgz",
- "integrity": "sha512-gQel0d7LBSWJGHc7gfZllYAu+RRGD9GcYGmkRfemurmDyDGQDf/sfjiBi8f9QxUc2iFTHnvIR5nMTyf0U3yl3Q==",
- "requires": {
- "@sentry/hub": "5.7.0",
- "@sentry/minimal": "5.7.0",
- "@sentry/types": "5.7.0",
- "@sentry/utils": "5.7.0",
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.7.1.tgz",
+ "integrity": "sha512-AOn3k3uVWh2VyajcHbV9Ta4ieDIeLckfo7UMLM+CTk2kt7C89SayDGayJMSsIrsZlL4qxBoLB9QY4W2FgAGJrg==",
+ "requires": {
+ "@sentry/hub": "5.7.1",
+ "@sentry/minimal": "5.7.1",
+ "@sentry/types": "5.7.1",
+ "@sentry/utils": "5.7.1",
"tslib": "^1.9.3"
}
},
"@sentry/hub": {
- "version": "5.7.0",
- "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.7.0.tgz",
- "integrity": "sha512-qNdYheJ6j4P9Sk0eqIINpJohImmu/+trCwFb4F8BGLQth5iGMVQD6D0YUrgjf4ZaQwfhw9tv4W6VEfF5tyASoA==",
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.7.1.tgz",
+ "integrity": "sha512-evGh323WR073WSBCg/RkhlUmCQyzU0xzBzCZPscvcoy5hd4SsLE6t9Zin+WACHB9JFsRQIDwNDn+D+pj3yKsig==",
"requires": {
- "@sentry/types": "5.7.0",
- "@sentry/utils": "5.7.0",
+ "@sentry/types": "5.7.1",
+ "@sentry/utils": "5.7.1",
"tslib": "^1.9.3"
}
},
"@sentry/minimal": {
- "version": "5.7.0",
- "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.7.0.tgz",
- "integrity": "sha512-0sizE2prS9nmfLyVUKmVzFFFqRNr9iorSCCejwnlRe3crqKqjf84tuRSzm6NkZjIyYj9djuuo9l9XN12NLQ/4A==",
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.7.1.tgz",
+ "integrity": "sha512-nS/Dg+jWAZtcxQW8wKbkkw4dYvF6uyY/vDiz/jFCaux0LX0uhgXAC9gMOJmgJ/tYBLJ64l0ca5LzpZa7BMJQ0g==",
"requires": {
- "@sentry/hub": "5.7.0",
- "@sentry/types": "5.7.0",
+ "@sentry/hub": "5.7.1",
+ "@sentry/types": "5.7.1",
"tslib": "^1.9.3"
}
},
"@sentry/types": {
- "version": "5.7.0",
- "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.7.0.tgz",
- "integrity": "sha512-bFRVortg713dE2yJXNFgNe6sNBVVSkpoELLkGPatdVQi0dYc6OggIIX4UZZvkynFx72GwYqO1NOrtUcJY2gmMg=="
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.7.1.tgz",
+ "integrity": "sha512-tbUnTYlSliXvnou5D4C8Zr+7/wJrHLbpYX1YkLXuIJRU0NSi81bHMroAuHWILcQKWhVjaV/HZzr7Y/hhWtbXVQ=="
},
"@sentry/utils": {
- "version": "5.7.0",
- "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.7.0.tgz",
- "integrity": "sha512-XmwQpLqea9mj8x1N7P/l4JvnEb0Rn5Py5OtBgl0ctk090W+GB1uM8rl9mkMf6698o1s1Z8T/tI/QY0yFA5uZXg==",
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.7.1.tgz",
+ "integrity": "sha512-nhirUKj/qFLsR1i9kJ5BRvNyzdx/E2vorIsukuDrbo8e3iZ11JMgCOVrmC8Eq9YkHBqgwX4UnrPumjFyvGMZ2Q==",
"requires": {
- "@sentry/types": "5.7.0",
+ "@sentry/types": "5.7.1",
"tslib": "^1.9.3"
}
},
@@ -1775,12 +1809,12 @@
}
},
"@tippy.js/react": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/@tippy.js/react/-/react-3.0.1.tgz",
- "integrity": "sha512-mLm+8LTyidXB6X2E9KVoL76Fp5JW16EmWOSwwNV8T7q9+eOFwLf8+cXtMSMCP6qY8gaO1oRCKcRr5tFFS0tIxA==",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@tippy.js/react/-/react-3.1.0.tgz",
+ "integrity": "sha512-UhhgqyYAZG8XSHrSlgmlUj+el9Rk48LYMPOI+Fcbg5w0ZF714HJ+32/xdwqANlDNuYxdF+pZQDitzd4eUDTEMQ==",
"requires": {
"prop-types": "^15.6.2",
- "tippy.js": "^5.0.1"
+ "tippy.js": "^5.0.2"
}
},
"@types/anymatch": {
@@ -1935,9 +1969,9 @@
}
},
"@types/jest": {
- "version": "24.0.18",
- "resolved": "https://registry.npmjs.org/@types/jest/-/jest-24.0.18.tgz",
- "integrity": "sha512-jcDDXdjTcrQzdN06+TSVsPPqxvsZA/5QkYfIZlq1JMw7FdP5AZylbOc+6B/cuDurctRe+MziUMtQ3xQdrbjqyQ==",
+ "version": "24.0.19",
+ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-24.0.19.tgz",
+ "integrity": "sha512-YYiqfSjocv7lk5H/T+v5MjATYjaTMsUkbDnjGqSMoO88jWdtJXJV4ST/7DKZcoMHMBvB2SeSfyOzZfkxXHR5xg==",
"dev": true,
"requires": {
"@types/jest-diff": "*"
@@ -2063,9 +2097,9 @@
"dev": true
},
"@types/react": {
- "version": "16.9.5",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.5.tgz",
- "integrity": "sha512-jQ12VMiFOWYlp+j66dghOWcmDDwhca0bnlcTxS4Qz/fh5gi6wpaZDthPEu/Gc/YlAuO87vbiUXL8qKstFvuOaA==",
+ "version": "16.9.9",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.9.tgz",
+ "integrity": "sha512-L+AudFJkDukk+ukInYvpoAPyJK5q1GanFOINOJnM0w6tUgITuWvJ4jyoBPFL7z4/L8hGLd+K/6xR5uUjXu0vVg==",
"requires": {
"@types/prop-types": "*",
"csstype": "^2.2.0"
@@ -2081,9 +2115,9 @@
}
},
"@types/react-dom": {
- "version": "16.9.1",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.1.tgz",
- "integrity": "sha512-1S/akvkKr63qIUWVu5IKYou2P9fHLb/P2VAwyxVV85JGaGZTcUniMiTuIqM3lXFB25ej6h+CYEQ27ERVwi6eGA==",
+ "version": "16.9.2",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.2.tgz",
+ "integrity": "sha512-hgPbBoI1aTSTvZwo8HYw35UaTldW6n2ETLvHAcfcg1FaOuBV3olmyCe5eMpx2WybWMBPv0MdU2t5GOcQhP+3zA==",
"dev": true,
"requires": {
"@types/react": "*"
@@ -2733,9 +2767,9 @@
},
"dependencies": {
"core-js": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.2.1.tgz",
- "integrity": "sha512-Qa5XSVefSVPRxy2XfUC13WbvqkxhkwB3ve+pgCQveNgYzbM/UxZeu1dcOX/xr4UmfUd+muuvsaxilQzCyUurMw==",
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.3.2.tgz",
+ "integrity": "sha512-S1FfZpeBchkhyoY76YAdFzKS4zz9aOK7EeFaNA2aJlyXyA+sgqz6xdxmLPGXEAf0nF44MVN1kSjrA9Kt3ATDQg==",
"dev": true
},
"node-fetch": {
@@ -2790,9 +2824,9 @@
},
"dependencies": {
"core-js": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.2.1.tgz",
- "integrity": "sha512-Qa5XSVefSVPRxy2XfUC13WbvqkxhkwB3ve+pgCQveNgYzbM/UxZeu1dcOX/xr4UmfUd+muuvsaxilQzCyUurMw==",
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.3.2.tgz",
+ "integrity": "sha512-S1FfZpeBchkhyoY76YAdFzKS4zz9aOK7EeFaNA2aJlyXyA+sgqz6xdxmLPGXEAf0nF44MVN1kSjrA9Kt3ATDQg==",
"dev": true
}
}
@@ -2963,9 +2997,9 @@
},
"dependencies": {
"commander": {
- "version": "2.20.1",
- "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.1.tgz",
- "integrity": "sha512-cCuLsMhJeWQ/ZpsFTbE765kvVfoeSddc4nU3up4fV+fDBcfUXnbITJ+JzhkdjzOqhURjZgujxaioam4RM9yGUg==",
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
}
}
@@ -5669,9 +5703,9 @@
}
},
"date-fns": {
- "version": "2.4.1",
- "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.4.1.tgz",
- "integrity": "sha512-2RhmH/sjDSCYW2F3ZQxOUx/I7PvzXpi89aQL2d3OAxSTwLx6NilATeUbe0menFE3Lu5lFkOFci36ivimwYHHxw=="
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.5.0.tgz",
+ "integrity": "sha512-I6Tkis01//nRcmvMQw/MRE1HAtcuA5Ie6jGPb8bJZJub7494LGOObqkV3ParnsSVviAjk5C8mNKDqYVBzCopWg=="
},
"date-now": {
"version": "0.1.4",
@@ -6093,9 +6127,9 @@
}
},
"dotenv": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.1.0.tgz",
- "integrity": "sha512-GUE3gqcDCaMltj2++g6bRQ5rBJWtkWTmqmD0fo1RnnMuUqHNCt2oTPeDnS9n6fKYvlhn7AeBkb38lymBtWBQdA==",
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz",
+ "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==",
"dev": true
},
"download": {
@@ -8332,20 +8366,20 @@
"integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM="
},
"husky": {
- "version": "3.0.8",
- "resolved": "https://registry.npmjs.org/husky/-/husky-3.0.8.tgz",
- "integrity": "sha512-HFOsgcyrX3qe/rBuqyTt+P4Gxn5P0seJmr215LAZ/vnwK3jWB3r0ck7swbzGRUbufCf9w/lgHPVbF/YXQALgfQ==",
+ "version": "3.0.9",
+ "resolved": "https://registry.npmjs.org/husky/-/husky-3.0.9.tgz",
+ "integrity": "sha512-Yolhupm7le2/MqC1VYLk/cNmYxsSsqKkTyBhzQHhPK1jFnC89mmmNVuGtLNabjDI6Aj8UNIr0KpRNuBkiC4+sg==",
"dev": true,
"requires": {
"chalk": "^2.4.2",
+ "ci-info": "^2.0.0",
"cosmiconfig": "^5.2.1",
"execa": "^1.0.0",
"get-stdin": "^7.0.0",
- "is-ci": "^2.0.0",
"opencollective-postinstall": "^2.0.2",
"pkg-dir": "^4.2.0",
"please-upgrade-node": "^3.2.0",
- "read-pkg": "^5.1.1",
+ "read-pkg": "^5.2.0",
"run-node": "^1.0.0",
"slash": "^3.0.0"
},
@@ -11156,18 +11190,18 @@
}
},
"nodemon": {
- "version": "1.19.3",
- "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.19.3.tgz",
- "integrity": "sha512-TBNKRmJykEbxpTniZBusqRrUTHIEqa2fpecbTQDQj1Gxjth7kKAPP296ztR0o5gPUWsiYbuEbt73/+XMYab1+w==",
+ "version": "1.19.4",
+ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.19.4.tgz",
+ "integrity": "sha512-VGPaqQBNk193lrJFotBU8nvWZPqEZY2eIzymy2jjY0fJ9qIsxA0sxQ8ATPl0gZC645gijYEc1jtZvpS8QWzJGQ==",
"dev": true,
"requires": {
- "chokidar": "^2.1.5",
- "debug": "^3.1.0",
+ "chokidar": "^2.1.8",
+ "debug": "^3.2.6",
"ignore-by-default": "^1.0.1",
"minimatch": "^3.0.4",
- "pstree.remy": "^1.1.6",
- "semver": "^5.5.0",
- "supports-color": "^5.2.0",
+ "pstree.remy": "^1.1.7",
+ "semver": "^5.7.1",
+ "supports-color": "^5.5.0",
"touch": "^3.1.0",
"undefsafe": "^2.0.2",
"update-notifier": "^2.5.0"
@@ -13275,12 +13309,9 @@
}
},
"react-content-loader": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/react-content-loader/-/react-content-loader-4.3.1.tgz",
- "integrity": "sha512-K8QZq3B+MQXuzbz10cujfHWr8wmhpSjZsBswFJJpBnp5L5jR+QBVRjroPfUtdXLcbdeNDD2j9VS00a1fs3jQjg==",
- "requires": {
- "react-native-svg": "9.6.4"
- }
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/react-content-loader/-/react-content-loader-4.3.2.tgz",
+ "integrity": "sha512-Af2RW2G57+mFRXsiSXROtgvz3KmPz0lATRHNUpJ57DyVw6SRzDRNRXo04I2xhcwmwVnXsfx4s2hsHrU+Lq5jRw=="
},
"react-dom": {
"version": "16.10.2",
@@ -13313,11 +13344,6 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.10.2.tgz",
"integrity": "sha512-INBT1QEgtcCCgvccr5/86CfD71fw9EPmDxgiJX4I2Ddr6ZsV6iFXsuby+qWJPtmNuMY0zByTsG4468P7nHuNWA=="
},
- "react-native-svg": {
- "version": "9.6.4",
- "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-9.6.4.tgz",
- "integrity": "sha512-6SlbGx0vlXHyDPQXSpX+8o6bNjxKFNJsISoboAkR7YWW6hdnkMg/HJXCgT6oJC0/ClKtSO7ZPrQcK4HR65kDNg=="
- },
"react-quill": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/react-quill/-/react-quill-1.3.3.tgz",
@@ -13517,9 +13543,9 @@
}
},
"recast": {
- "version": "0.18.3",
- "resolved": "https://registry.npmjs.org/recast/-/recast-0.18.3.tgz",
- "integrity": "sha512-J76CWndZodsOsvhpxhlDCp75qVPuohbqPmh9NYMVDkNDp3JbyB7UKeoKo3KoL63sA1MyPJljRMjilR6DnIP7EQ==",
+ "version": "0.18.5",
+ "resolved": "https://registry.npmjs.org/recast/-/recast-0.18.5.tgz",
+ "integrity": "sha512-sD1WJrpLQAkXGyQZyGzTM75WJvyAd98II5CHdK3IYbt/cZlU0UzCRVU11nUFNXX9fBVEt4E9ajkMjBlUlG+Oog==",
"dev": true,
"requires": {
"ast-types": "0.13.2",
@@ -15528,9 +15554,9 @@
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
},
"tippy.js": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-5.0.2.tgz",
- "integrity": "sha512-Zj7ihX2/uImDudNkfxw9jgcbtg9sUKT3QRmuH9WJtKkX6M96SwMG8FPdiObholc4SJP6wlnqk0nqByjXb8QZSA==",
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-5.0.3.tgz",
+ "integrity": "sha512-6OVy2/pWuzcphHdEAK/2GPOOsT75AY9D7Xhk9U6WHB/dT737avXtgW1K6ch8jrp81PxbXxHgdmeHRPBnqunwpQ==",
"requires": {
"popper.js": "^1.15.0"
}
@@ -15797,6 +15823,12 @@
}
}
},
+ "tslint-react-hooks": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/tslint-react-hooks/-/tslint-react-hooks-2.2.1.tgz",
+ "integrity": "sha512-bqIg2uZe+quJMfSOGc4OOZ4awo6TP1ejGDGS6IKg2WIrS0XnWfhUJ99i3B8rUpnZhuD4vRSvyYIbXPUmEqQxxQ==",
+ "dev": true
+ },
"tsutils": {
"version": "2.29.0",
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz",
@@ -16215,6 +16247,11 @@
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ=="
},
+ "use-debounce": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-3.1.0.tgz",
+ "integrity": "sha512-DEf3L/ZKkOSTARk/DHlC6KAAJKwMqpck8Zx06SM2Wr+LfU1TzhO8hZRzB/qbpSQqREYWQes24n1q9doeTMqF4g=="
+ },
"use-memo-one": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.1.tgz",
diff --git a/package.json b/package.json
index efd6cb957d..df50f8ee5c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "matters-web",
- "version": "2.0.1",
+ "version": "2.1.0",
"description": "codebase of Matters' website",
"author": "",
"engines": {
@@ -31,10 +31,9 @@
"gen:fragmentTypes": "node bin/buildFragmentTypes.js"
},
"dependencies": {
- "@apollo/react-testing": "^3.1.2",
"@matters/apollo-upload-client": "^11.1.0",
- "@sentry/browser": "^5.7.0",
- "@tippy.js/react": "^3.0.1",
+ "@sentry/browser": "^5.7.1",
+ "@tippy.js/react": "^3.1.0",
"@types/jump.js": "^1.0.2",
"apollo-cache-inmemory": "^1.6.3",
"apollo-cache-persist": "^0.1.1",
@@ -47,7 +46,7 @@
"apollo-utilities": "^1.3.2",
"body-scroll-lock": "^2.6.4",
"classnames": "^2.2.6",
- "date-fns": "^2.4.1",
+ "date-fns": "^2.5.0",
"express": "^4.17.1",
"formik": "^1.5.8",
"graphql": "^14.5.8",
@@ -64,29 +63,31 @@
"react": "^16.10.2",
"react-apollo": "^3.1.2",
"react-beautiful-dnd": "^11.0.5",
- "react-content-loader": "^4.3.1",
+ "react-content-loader": "^4.3.2",
"react-dom": "^16.10.2",
"react-quill": "^1.3.3",
"react-responsive": "^8.0.1",
"react-waypoint": "^9.0.2",
"subscriptions-transport-ws": "^0.9.16",
+ "use-debounce": "^3.1.0",
"validator": "^11.1.0"
},
"devDependencies": {
+ "@apollo/react-testing": "3.1.2",
"@testing-library/react": "^8.0.9",
"@types/body-scroll-lock": "^2.6.1",
"@types/classnames": "^2.2.9",
"@types/dotenv": "^6.1.1",
"@types/express": "^4.17.1",
"@types/helmet": "0.0.44",
- "@types/jest": "^24.0.18",
+ "@types/jest": "^24.0.19",
"@types/lodash": "^4.14.144",
"@types/next-server": "^8.1.2",
"@types/nprogress": "0.2.0",
"@types/quill": "^2.0.3",
- "@types/react": "^16.9.5",
+ "@types/react": "^16.9.9",
"@types/react-beautiful-dnd": "^11.0.3",
- "@types/react-dom": "^16.9.1",
+ "@types/react-dom": "^16.9.2",
"@types/react-responsive": "^8.0.1",
"@types/segment-analytics": "0.0.32",
"@types/styled-jsx": "^2.2.8",
@@ -97,8 +98,8 @@
"babel-plugin-dynamic-import-node": "^2.3.0",
"babel-plugin-module-resolver": "^3.2.0",
"babel-polyfill": "^6.26.0",
- "dotenv": "^8.1.0",
- "husky": "^3.0.8",
+ "dotenv": "^8.2.0",
+ "husky": "^3.0.9",
"identity-obj-proxy": "^3.0.0",
"imagemin-mozjpeg": "^8.0.0",
"imagemin-optipng": "^7.1.0",
@@ -108,7 +109,7 @@
"next-compose-plugins": "^2.2.0",
"next-offline": "^4.0.6",
"next-optimized-images": "^2.5.3",
- "nodemon": "^1.19.3",
+ "nodemon": "^1.19.4",
"npm-run-all": "^4.1.5",
"postcss-calc": "^7.0.1",
"postcss-color-function": "^4.1.0",
@@ -118,12 +119,13 @@
"postcss-preset-env": "^6.7.0",
"prettier": "^1.18.2",
"styled-jsx-plugin-postcss": "^2.0.1",
- "svg-sprite-loader": "^4.1.3",
+ "svg-sprite-loader": "4.1.3",
"ts-jest": "^24.1.0",
"ts-node": "^8.4.1",
"tslint": "^5.20.0",
"tslint-config-prettier": "^1.18.0",
"tslint-react": "^4.1.0",
+ "tslint-react-hooks": "^2.2.1",
"typescript": "^3.6.4"
},
"_moduleAliases": {
diff --git a/public/static/icons/block.svg b/public/static/icons/block.svg
new file mode 100644
index 0000000000..e3d5551948
--- /dev/null
+++ b/public/static/icons/block.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/static/icons/unblock.svg b/public/static/icons/unblock.svg
new file mode 100644
index 0000000000..c0891ee4fb
--- /dev/null
+++ b/public/static/icons/unblock.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/common/enums/analytics.ts b/src/common/enums/analytics.ts
index bdc6c5a3e7..f1b7b9a4c7 100644
--- a/src/common/enums/analytics.ts
+++ b/src/common/enums/analytics.ts
@@ -63,6 +63,7 @@ export const FEED_TYPE = {
FOLLOWER: 'follower',
APPRECIATOR: 'appreciator',
SEARCH_USER: 'search-user',
+ BLOCK_LIST: 'block-list',
// tags
TAGS: 'tags',
ALL_TAGS: 'all-tags',
diff --git a/src/common/enums/errorCode.ts b/src/common/enums/errorCode.ts
index 8e2d335df6..6501ae64a6 100644
--- a/src/common/enums/errorCode.ts
+++ b/src/common/enums/errorCode.ts
@@ -4,7 +4,9 @@ export const ERROR_CODES = {
NETWORK_ERROR: 'NETWORK_ERROR',
INTERNAL_SERVER_ERROR: 'INTERNAL_SERVER_ERROR',
BAD_USER_INPUT: 'BAD_USER_INPUT',
+
ACTION_LIMIT_EXCEEDED: 'ACTION_LIMIT_EXCEEDED',
+ ACTION_FAILED: 'ACTION_FAILED',
// Auth
UNAUTHENTICATED: 'UNAUTHENTICATED',
@@ -34,11 +36,6 @@ export const ERROR_CODES = {
USER_USERNAME_INVALID: 'USER_USERNAME_INVALID',
USER_USERNAME_EXISTS: 'USER_USERNAME_EXISTS',
USER_DISPLAYNAME_INVALID: 'USER_DISPLAYNAME_INVALID',
- USER_FOLLOW_FAILED: 'USER_FOLLOW_FAILED',
- USER_INVITE_FAILED: 'USER_INVITE_FAILED',
- USER_INVITE_STATE_INVALID: 'USER_INVITE_STATE_INVALID',
- USER_INVITE_EMAIL_REGISTERED: 'USER_INVITE_EMAIL_REGISTERED',
- USER_INVITE_EMAIL_INVITED: 'USER_INVITE_EMAIL_INVITED',
// Verification Code
CODE_INVALID: 'CODE_INVALID',
diff --git a/src/common/enums/route.ts b/src/common/enums/route.ts
index eddb47f703..a5bd70a02a 100644
--- a/src/common/enums/route.ts
+++ b/src/common/enums/route.ts
@@ -32,6 +32,7 @@ type ROUTE_KEY =
| 'ME_NOTIFICATIONS'
| 'ME_SETTINGS_ACCOUNT'
| 'ME_SETTINGS_NOTIFICATION'
+ | 'ME_SETTINGS_BLOCKED'
| 'ME_DRAFT_DETAIL'
// | 'EDITOR'
| 'AUTH_LOGIN'
@@ -210,6 +211,11 @@ export const ROUTES: Array<{
href: '/MeSettingsNotification',
as: '/me/settings/notification'
},
+ {
+ key: 'ME_SETTINGS_BLOCKED',
+ href: '/MeSettingsBlocked',
+ as: '/me/settings/blocked'
+ },
// Draft
{
diff --git a/src/common/enums/text.ts b/src/common/enums/text.ts
index a744dc5a36..49a249c996 100644
--- a/src/common/enums/text.ts
+++ b/src/common/enums/text.ts
@@ -121,6 +121,7 @@ export const TEXT = {
setting: '設定',
accountSetting: '帳戶設定',
notificationSetting: '通知設定',
+ blockedSetting: '封鎖用戶',
walletSetting: '錢包設定',
uiSetting: '介面設定',
userProfile: '個人簡介',
@@ -130,6 +131,11 @@ export const TEXT = {
articleFingerprint: '作品指紋',
copySuccess: '複製成功',
copy: '複製',
+ block: '封鎖',
+ blockUser: '封鎖用戶',
+ unblockUser: '取消封鎖',
+ blockSuccess: '封鎖成功',
+ unblockSuccess: '已取消封鎖。該用戶現在可以評論你的作品。',
pin: '喜歡回應',
unpin: '取消精選',
emptySearchResults: '沒有找到你搜索的內容',
@@ -161,11 +167,7 @@ export const TEXT = {
USER_USERNAME_INVALID: 'Matters ID 不正確',
USER_USERNAME_EXISTS: '該 Matters ID 已被其他使用者使用',
USER_DISPLAYNAME_INVALID: '姓名不正確',
- USER_FOLLOW_FAILED: '追蹤用戶失敗,請稍候重試',
- // USER_INVITE_FAILED: '',
- // USER_INVITE_STATE_INVALID: '',
- // USER_INVITE_EMAIL_REGISTERED: '',
- // USER_INVITE_EMAIL_INVITED: '',
+ ACTION_FAILED: '操作失敗,請稍候重試',
CODE_INVALID: '驗證碼不正確',
CODE_EXPIRED: '驗證碼已過期'
} as { [key: string]: string }
@@ -293,6 +295,7 @@ export const TEXT = {
setting: '设定',
accountSetting: '账户设定',
notificationSetting: '通知设定',
+ blockedSetting: '屏蔽用户',
walletSetting: '钱包设定',
uiSetting: '界面设定',
userProfile: '个人简介',
@@ -302,6 +305,11 @@ export const TEXT = {
articleFingerprint: '作品指纹',
copySuccess: '复制成功',
copy: '复制',
+ block: '屏蔽',
+ blockUser: '屏蔽用户',
+ unblockUser: '取消屏蔽',
+ blockSuccess: '屏蔽成功',
+ unblockSuccess: '已取消屏蔽。该用户现在可以评论你的作品。',
pin: '喜欢回应',
unpin: '取消精选',
emptySearchResults: '没有找到你搜寻的内容',
@@ -333,11 +341,7 @@ export const TEXT = {
USER_USERNAME_INVALID: 'Matters ID 不正确',
USER_USERNAME_EXISTS: '该 Matters ID 已被其他用户使用',
USER_DISPLAYNAME_INVALID: '姓名不正确',
- USER_FOLLOW_FAILED: '追踪用户失败,请稍候重试',
- // USER_INVITE_FAILED: '',
- // USER_INVITE_STATE_INVALID: '',
- // USER_INVITE_EMAIL_REGISTERED: '',
- // USER_INVITE_EMAIL_INVITED: '',
+ ACTION_FAILED: '操作失败,请稍候重试',
CODE_INVALID: '验证码不正确',
CODE_EXPIRED: '验证码已过期'
} as { [key: string]: string }
diff --git a/src/common/enums/time.ts b/src/common/enums/time.ts
index 3994da8556..5dd2cab18c 100644
--- a/src/common/enums/time.ts
+++ b/src/common/enums/time.ts
@@ -1 +1,5 @@
export const POLL_INTERVAL = 1000 * 10
+
+export const INPUT_DEBOUNCE = 300
+
+export const APPRECIATE_DEBOUNCE = 1000
diff --git a/src/common/utils/route.ts b/src/common/utils/route.ts
index 0cb8fb3141..515b597bf2 100644
--- a/src/common/utils/route.ts
+++ b/src/common/utils/route.ts
@@ -1,4 +1,3 @@
-import _get from 'lodash/get'
import Router, { NextRouter } from 'next/router'
import queryString from 'query-string'
diff --git a/src/components/Analytics/AnalyticsProvider.tsx b/src/components/Analytics/AnalyticsProvider.tsx
index 5beca88d54..0e669dfdcd 100644
--- a/src/components/Analytics/AnalyticsProvider.tsx
+++ b/src/components/Analytics/AnalyticsProvider.tsx
@@ -1,6 +1,6 @@
import getConfig from 'next/config'
import Router from 'next/router'
-import React, { FC, useEffect, useState } from 'react'
+import React, { useEffect, useState } from 'react'
import { analytics } from '~/common/utils'
@@ -8,8 +8,9 @@ const {
publicRuntimeConfig: { SEGMENT_KEY }
} = getConfig()
-export const AnalyticsProvider: FC = ({ children }) => {
+export const AnalyticsProvider: React.FC = ({ children }) => {
const [sessionStarted, setSessionStarted] = useState(false)
+
useEffect(() => {
// injects analytics var into global scope
// ref: https://github.com/segmentio/analytics-react#%EF%B8%8F-step-1-copy-the-snippet
@@ -77,7 +78,7 @@ export const AnalyticsProvider: FC = ({ children }) => {
analytics.load(SEGMENT_KEY || '3gE20MjzN9qncFqlKV0pDvNO7Cp2gWU3')
}
})()
- })
+ }, [])
useEffect(() => {
// initial
@@ -89,7 +90,7 @@ export const AnalyticsProvider: FC = ({ children }) => {
Router.events.on('routeChangeComplete', (path: string) => {
analytics.trackPage({ path })
})
- })
+ }, [])
return <>{children}>
}
diff --git a/src/components/ArticleDigest/Actions/Appreciation.tsx b/src/components/ArticleDigest/Actions/Appreciation.tsx
index c1f517fef3..ab1d0a02ec 100644
--- a/src/components/ArticleDigest/Actions/Appreciation.tsx
+++ b/src/components/ArticleDigest/Actions/Appreciation.tsx
@@ -1,5 +1,4 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
import { Icon, TextIcon } from '~/components'
@@ -27,7 +26,7 @@ const Appreciation = ({
icon={}
color="grey"
weight="medium"
- text={numAbbr(_get(article, 'appreciationsReceivedTotal', 0))}
+ text={numAbbr((article && article.appreciationsReceivedTotal) || 0)}
size={size === 'small' ? 'sm' : 'xs'}
spacing="xtight"
/>
diff --git a/src/components/ArticleDigest/Actions/CommentCount.tsx b/src/components/ArticleDigest/Actions/CommentCount.tsx
index be284884ef..9463c4dea2 100644
--- a/src/components/ArticleDigest/Actions/CommentCount.tsx
+++ b/src/components/ArticleDigest/Actions/CommentCount.tsx
@@ -1,5 +1,4 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
import Link from 'next/link'
import { Icon, TextIcon } from '~/components'
@@ -65,7 +64,7 @@ const CommentCount = ({
}
color="grey"
weight="medium"
- text={numAbbr(_get(article, 'commentCount', 0))}
+ text={numAbbr(article.commentCount || 0)}
size={size === 'default' ? 'sm' : 'xs'}
spacing="xxtight"
/>
diff --git a/src/components/ArticleDigest/Actions/ResponseCount.tsx b/src/components/ArticleDigest/Actions/ResponseCount.tsx
index 94f258d92f..5fad06bd5b 100644
--- a/src/components/ArticleDigest/Actions/ResponseCount.tsx
+++ b/src/components/ArticleDigest/Actions/ResponseCount.tsx
@@ -67,7 +67,7 @@ const ResponseCount = ({
}
color="grey"
weight="medium"
- text={numAbbr(_get(article, 'responseCount', 0))}
+ text={numAbbr(article.responseCount || 0)}
size={size === 'small' ? 'sm' : 'xs'}
spacing="xxtight"
/>
diff --git a/src/components/ArticleDigest/Actions/TopicScore.tsx b/src/components/ArticleDigest/Actions/TopicScore.tsx
index 8cd162137b..fcf4a8188d 100644
--- a/src/components/ArticleDigest/Actions/TopicScore.tsx
+++ b/src/components/ArticleDigest/Actions/TopicScore.tsx
@@ -1,5 +1,4 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
import { Icon, TextIcon, Translate } from '~/components'
diff --git a/src/components/ArticleDigest/Actions/index.tsx b/src/components/ArticleDigest/Actions/index.tsx
index a3db2d67e9..86a1f2da33 100644
--- a/src/components/ArticleDigest/Actions/index.tsx
+++ b/src/components/ArticleDigest/Actions/index.tsx
@@ -154,9 +154,11 @@ const Actions = ({
)}
+
{hasDateTime && 'createdAt' in article && isResponseMode && (
)}
+
)
diff --git a/src/components/ArticleDigest/DropdownActions/ArchiveButton.tsx b/src/components/ArticleDigest/DropdownActions/ArchiveButton.tsx
index 864b361766..add12ae554 100644
--- a/src/components/ArticleDigest/DropdownActions/ArchiveButton.tsx
+++ b/src/components/ArticleDigest/DropdownActions/ArchiveButton.tsx
@@ -1,7 +1,8 @@
import gql from 'graphql-tag'
import { Icon, TextIcon, Translate } from '~/components'
-import { Mutation } from '~/components/GQL'
+import { useMutation } from '~/components/GQL'
+import { ArchiveArticle } from '~/components/GQL/mutations/__generated__/ArchiveArticle'
import ARCHIVE_ARTICLE from '~/components/GQL/mutations/archiveArticle'
import updateUserArticles from '~/components/GQL/updates/userArticles'
@@ -30,51 +31,49 @@ const ArchiveButton = ({
article: ArchiveButtonArticle
hideDropdown: () => void
}) => {
+ const [archiveArticle] = useMutation(ARCHIVE_ARTICLE, {
+ variables: { id: article.id },
+ optimisticResponse: {
+ archiveArticle: {
+ id: article.id,
+ state: 'archived' as any,
+ sticky: false,
+ __typename: 'Article'
+ }
+ },
+ update: cache => {
+ updateUserArticles({
+ cache,
+ articleId: article.id,
+ userName: article.author.userName,
+ type: 'archive'
+ })
+ }
+ })
+
return (
- {
- updateUserArticles({
- cache,
- articleId: article.id,
- userName: article.author.userName,
- type: 'archive'
- })
+
+
+ }
+ spacing="tight"
+ >
+
+
+
+
+
)
}
diff --git a/src/components/ArticleDigest/DropdownActions/StickyButton.tsx b/src/components/ArticleDigest/DropdownActions/StickyButton.tsx
index 47c3ac254e..4f6cdce3f9 100644
--- a/src/components/ArticleDigest/DropdownActions/StickyButton.tsx
+++ b/src/components/ArticleDigest/DropdownActions/StickyButton.tsx
@@ -1,13 +1,14 @@
import gql from 'graphql-tag'
import { Icon, TextIcon, Translate } from '~/components'
-import { Mutation } from '~/components/GQL'
+import { useMutation } from '~/components/GQL'
import updateUserArticles from '~/components/GQL/updates/userArticles'
import ICON_PIN_TO_TOP from '~/static/icons/pin-to-top.svg?sprite'
import ICON_UNSTICKY from '~/static/icons/unsticky.svg?sprite'
import { StickyButtonArticle } from './__generated__/StickyButtonArticle'
+import { UpdateArticleInfo } from './__generated__/UpdateArticleInfo'
import styles from './styles.css'
const UPDATE_ARTICLE_INFO = gql`
@@ -69,39 +70,37 @@ const StickyButton = ({
article: StickyButtonArticle
hideDropdown: () => void
}) => {
+ const [update] = useMutation(UPDATE_ARTICLE_INFO, {
+ variables: { id: article.id, sticky: !article.sticky },
+ optimisticResponse: {
+ updateArticleInfo: {
+ id: article.id,
+ sticky: !article.sticky,
+ __typename: 'Article'
+ }
+ },
+ update: cache => {
+ updateUserArticles({
+ cache,
+ articleId: article.id,
+ userName: article.author.userName,
+ type: article.sticky ? 'unsticky' : 'sticky'
+ })
+ }
+ })
+
return (
- {
- updateUserArticles({
- cache,
- articleId: article.id,
- userName: article.author.userName,
- type: article.sticky ? 'unsticky' : 'sticky'
- })
+ {
+ update()
+ hideDropdown()
}}
>
- {(update: any) => (
- {
- update()
- hideDropdown()
- }}
- >
- {article.sticky ? : }
-
-
- )}
-
+ {article.sticky ? : }
+
+
+
)
}
diff --git a/src/components/ArticleDigest/DropdownActions/index.tsx b/src/components/ArticleDigest/DropdownActions/index.tsx
index aa4e345ed1..8ce2317c96 100644
--- a/src/components/ArticleDigest/DropdownActions/index.tsx
+++ b/src/components/ArticleDigest/DropdownActions/index.tsx
@@ -75,6 +75,7 @@ const DropdownActions = ({ article }: { article: DropdownActionsArticle }) => {
/>
+
>
)
diff --git a/src/components/ArticleDigest/RelatedDigest/index.tsx b/src/components/ArticleDigest/RelatedDigest/index.tsx
index 31280e849e..8a6e320746 100644
--- a/src/components/ArticleDigest/RelatedDigest/index.tsx
+++ b/src/components/ArticleDigest/RelatedDigest/index.tsx
@@ -81,6 +81,7 @@ const RelatedDigest = ({
)}
+
@@ -91,6 +92,7 @@ const RelatedDigest = ({
+
{!cover && (
@@ -98,10 +100,12 @@ const RelatedDigest = ({
)}
+
+
)
diff --git a/src/components/Avatar/index.tsx b/src/components/Avatar/index.tsx
index 4277748dd2..550a3c4124 100644
--- a/src/components/Avatar/index.tsx
+++ b/src/components/Avatar/index.tsx
@@ -45,6 +45,7 @@ export const Avatar = ({
style={{ backgroundImage: `url(${source})` }}
aria-hidden="true"
/>
+
>
)
diff --git a/src/components/Button/BlockUser/Dropdown/index.tsx b/src/components/Button/BlockUser/Dropdown/index.tsx
new file mode 100644
index 0000000000..9d27016494
--- /dev/null
+++ b/src/components/Button/BlockUser/Dropdown/index.tsx
@@ -0,0 +1,127 @@
+import { useContext } from 'react'
+
+import { Icon, TextIcon, Translate } from '~/components'
+import { useMutation } from '~/components/GQL'
+import { BlockUser } from '~/components/GQL/fragments/__generated__/BlockUser'
+import userFragments from '~/components/GQL/fragments/user'
+import { UnblockUser } from '~/components/GQL/mutations/__generated__/UnblockUser'
+import UNBLOCK_USER from '~/components/GQL/mutations/unblockUser'
+import { LanguageContext } from '~/components/Language'
+import BlocKUserModal from '~/components/Modal/BlockUserModal'
+import { ModalInstance, ModalSwitch } from '~/components/ModalManager'
+
+import { ADD_TOAST, TEXT } from '~/common/enums'
+import { translate } from '~/common/utils'
+import ICON_BLOCK from '~/static/icons/block.svg?sprite'
+import ICON_UNBLOCK from '~/static/icons/unblock.svg?sprite'
+
+import styles from './styles.css'
+
+const fragments = {
+ user: userFragments.block
+}
+
+const TextIconBlock = () => (
+ }
+ spacing="tight"
+ >
+
+
+)
+
+const TextIconUnblock = () => (
+
+ }
+ spacing="tight"
+ >
+
+
+)
+
+const BlockUserButton = ({
+ user,
+ isShown,
+ hideDropdown
+}: {
+ user: BlockUser
+ isShown: boolean
+ hideDropdown: () => void
+}) => {
+ const { lang } = useContext(LanguageContext)
+ const [unblockUser] = useMutation(UNBLOCK_USER, {
+ variables: { id: user.id },
+ optimisticResponse: {
+ unblockUser: {
+ id: user.id,
+ isBlocked: false,
+ __typename: 'User'
+ }
+ }
+ })
+
+ return (
+ <>
+ {user.isBlocked && (
+ {
+ await unblockUser()
+ window.dispatchEvent(
+ new CustomEvent(ADD_TOAST, {
+ detail: {
+ color: 'green',
+ content: translate({
+ zh_hant: TEXT.zh_hant.unblockSuccess,
+ zh_hans: TEXT.zh_hans.unblockSuccess,
+ lang
+ })
+ }
+ })
+ )
+ hideDropdown()
+ }}
+ >
+
+
+
+
+ )}
+
+ {!user.isBlocked && (
+
+ {(open: any) => (
+ {
+ open()
+ hideDropdown()
+ }}
+ >
+
+
+
+
+ )}
+
+ )}
+
+ {isShown && (
+
+ {(props: ModalInstanceProps) => (
+
+ )}
+
+ )}
+ >
+ )
+}
+
+BlockUserButton.fragments = fragments
+
+export default BlockUserButton
diff --git a/src/components/Button/BlockUser/Dropdown/styles.css b/src/components/Button/BlockUser/Dropdown/styles.css
new file mode 100644
index 0000000000..5dbf2fe43e
--- /dev/null
+++ b/src/components/Button/BlockUser/Dropdown/styles.css
@@ -0,0 +1,3 @@
+button {
+ font-size: var(--font-size-sm);
+}
diff --git a/src/components/Button/BlockUser/Unblock/index.tsx b/src/components/Button/BlockUser/Unblock/index.tsx
new file mode 100644
index 0000000000..7d27421afd
--- /dev/null
+++ b/src/components/Button/BlockUser/Unblock/index.tsx
@@ -0,0 +1,77 @@
+import gql from 'graphql-tag'
+import { useContext } from 'react'
+
+import { Button, Translate } from '~/components'
+import { useMutation } from '~/components/GQL'
+import { UnblockUser } from '~/components/GQL/mutations/__generated__/UnblockUser'
+import UNBLOCK_USER from '~/components/GQL/mutations/unblockUser'
+import { LanguageContext } from '~/components/Language'
+
+import { ADD_TOAST, ANALYTICS_EVENTS, TEXT } from '~/common/enums'
+import { analytics, translate } from '~/common/utils'
+
+import { UnblockButtonUser } from './__generated__/UnblockButtonUser'
+
+const fragments = {
+ user: gql`
+ fragment UnblockButtonUser on User {
+ id
+ isBlocked
+ }
+ `
+}
+
+const Unblock = ({
+ user,
+ size = 'small'
+}: {
+ user: UnblockButtonUser
+ size?: 'small' | 'default'
+}) => {
+ const { lang } = useContext(LanguageContext)
+ const [unblockUser] = useMutation(UNBLOCK_USER, {
+ variables: { id: user.id },
+ optimisticResponse: {
+ unblockUser: {
+ id: user.id,
+ isBlocked: false,
+ __typename: 'User'
+ }
+ }
+ })
+
+ return (
+ {
+ await unblockUser()
+ window.dispatchEvent(
+ new CustomEvent(ADD_TOAST, {
+ detail: {
+ color: 'green',
+ content: translate({
+ zh_hant: TEXT.zh_hant.unblockSuccess,
+ zh_hans: TEXT.zh_hans.unblockSuccess,
+ lang
+ })
+ }
+ })
+ )
+ analytics.trackEvent(ANALYTICS_EVENTS.UNFOLLOW_USER, {
+ id: user.id
+ })
+ }}
+ bgColor="green"
+ >
+
+
+ )
+}
+
+Unblock.fragments = fragments
+
+export default Unblock
diff --git a/src/components/Button/Bookmark/Subscribe.tsx b/src/components/Button/Bookmark/Subscribe.tsx
index 099a66b1bc..f6b2799d7a 100644
--- a/src/components/Button/Bookmark/Subscribe.tsx
+++ b/src/components/Button/Bookmark/Subscribe.tsx
@@ -1,12 +1,13 @@
import gql from 'graphql-tag'
-import { Mutation } from '~/components/GQL'
+import { useMutation } from '~/components/GQL'
import { Icon } from '~/components/Icon'
import ICON_BOOKMARK_REGULAR_INACTIVE from '~/static/icons/bookmark-regular-inactive.svg?sprite'
import ICON_BOOKMARK_SM_INACTIVE from '~/static/icons/bookmark-small-inactive.svg?sprite'
import { BookmarkArticle } from './__generated__/BookmarkArticle'
+import { SubscribeArticle } from './__generated__/SubscribeArticle'
const SUBSCRIBE_ARTICLE = gql`
mutation SubscribeArticle($id: ID!) {
@@ -25,41 +26,40 @@ const Subscribe = ({
article: BookmarkArticle
size: 'xsmall' | 'small' | 'default'
disabled?: boolean
-}) => (
- {
+ const [subscribe] = useMutation(SUBSCRIBE_ARTICLE, {
+ variables: { id: article.id },
+ optimisticResponse: {
subscribeArticle: {
id: article.id,
subscribed: true,
__typename: 'Article'
}
- }}
- >
- {(subscribe: any, { data }: any) => (
- subscribe()}
- disabled={disabled}
- >
-
-
- )}
-
-)
+ }
+ })
+
+ return (
+ subscribe()}
+ disabled={disabled}
+ >
+
+
+ )
+}
export default Subscribe
diff --git a/src/components/Button/Bookmark/Unsubscribe.tsx b/src/components/Button/Bookmark/Unsubscribe.tsx
index cbe2258eeb..e6eab9dc57 100644
--- a/src/components/Button/Bookmark/Unsubscribe.tsx
+++ b/src/components/Button/Bookmark/Unsubscribe.tsx
@@ -1,12 +1,13 @@
import gql from 'graphql-tag'
import { Icon } from '~/components'
-import { Mutation } from '~/components/GQL'
+import { useMutation } from '~/components/GQL'
import ICON_BOOKMARK_REGULAR_ACTIVE from '~/static/icons/bookmark-regular-active.svg?sprite'
import ICON_BOOKMARK_SM_ACTIVE from '~/static/icons/bookmark-small-active.svg?sprite'
import { BookmarkArticle } from './__generated__/BookmarkArticle'
+import { UnsubscribeArticle } from './__generated__/UnsubscribeArticle'
const UNSUBSCRIBE_ARTICLE = gql`
mutation UnsubscribeArticle($id: ID!) {
@@ -25,41 +26,40 @@ const Unsubscribe = ({
article: BookmarkArticle
size: 'xsmall' | 'small' | 'default'
disabled?: boolean
-}) => (
- {
+ const [unsubscribe] = useMutation(UNSUBSCRIBE_ARTICLE, {
+ variables: { id: article.id },
+ optimisticResponse: {
unsubscribeArticle: {
id: article.id,
subscribed: false,
__typename: 'Article'
}
- }}
- >
- {(unsubscribe: any, { data }: any) => (
- unsubscribe()}
- disabled={disabled}
- >
-
-
- )}
-
-)
+ }
+ })
+
+ return (
+ unsubscribe()}
+ disabled={disabled}
+ >
+
+
+ )
+}
export default Unsubscribe
diff --git a/src/components/Button/Follow/Follow.tsx b/src/components/Button/Follow/Follow.tsx
index dccb6d34fb..b280553b9c 100644
--- a/src/components/Button/Follow/Follow.tsx
+++ b/src/components/Button/Follow/Follow.tsx
@@ -2,7 +2,7 @@ import gql from 'graphql-tag'
import _get from 'lodash/get'
import { Button, Icon, Translate } from '~/components'
-import { Mutation } from '~/components/GQL'
+import { useMutation } from '~/components/GQL'
import updateUserFollowerCount from '~/components/GQL/updates/userFollowerCount'
import updateViewerFolloweeCount from '~/components/GQL/updates/viewerFolloweeCount'
@@ -11,6 +11,7 @@ import { analytics } from '~/common/utils'
import ICON_ADD from '~/static/icons/add.svg?sprite'
import { FollowButtonUser } from './__generated__/FollowButtonUser'
+import { FollowUser } from './__generated__/FollowUser'
const FOLLOW_USER = gql`
mutation FollowUser($id: ID!) {
@@ -28,53 +29,49 @@ const Follow = ({
}: {
user: FollowButtonUser
size?: 'small' | 'default'
-}) => (
- {
+ const [follow] = useMutation(FOLLOW_USER, {
+ variables: { id: user.id },
+ optimisticResponse: {
followUser: {
id: user.id,
isFollowee: true,
isFollower: user.isFollower,
__typename: 'User'
}
- }}
- update={(cache: any) => {
+ },
+ update: cache => {
const userName = _get(user, 'userName', null)
updateUserFollowerCount({ cache, type: 'increment', userName })
updateViewerFolloweeCount({ cache, type: 'increment' })
- }}
- >
- {(follow: any, { data }: any) => (
-
- }
- style={size === 'small' ? { width: '4rem' } : { width: '5.5rem' }}
- onClick={() => {
- follow()
- analytics.trackEvent(ANALYTICS_EVENTS.FOLLOW_USER, { id: user.id })
- }}
- bgColor="transparent"
- outlineColor="green"
- >
-
-
- )}
-
-)
+ }
+ style={size === 'small' ? { width: '4rem' } : { width: '5.5rem' }}
+ onClick={() => {
+ follow()
+ analytics.trackEvent(ANALYTICS_EVENTS.FOLLOW_USER, { id: user.id })
+ }}
+ bgColor="transparent"
+ outlineColor="green"
+ >
+
+
+ )
+}
export default Follow
diff --git a/src/components/Button/Follow/Unfollow.tsx b/src/components/Button/Follow/Unfollow.tsx
index e4badef3a5..7a3172557b 100644
--- a/src/components/Button/Follow/Unfollow.tsx
+++ b/src/components/Button/Follow/Unfollow.tsx
@@ -3,7 +3,7 @@ import _get from 'lodash/get'
import { useState } from 'react'
import { Button, Translate } from '~/components'
-import { Mutation } from '~/components/GQL'
+import { useMutation } from '~/components/GQL'
import updateUserFollowerCount from '~/components/GQL/updates/userFollowerCount'
import updateViewerFolloweeCount from '~/components/GQL/updates/viewerFolloweeCount'
@@ -11,6 +11,7 @@ import { ANALYTICS_EVENTS, TEXT } from '~/common/enums'
import { analytics } from '~/common/utils'
import { FollowButtonUser } from './__generated__/FollowButtonUser'
+import { UnfollowUser } from './__generated__/UnfollowUser'
const UNFOLLOW_USER = gql`
mutation UnfollowUser($id: ID!) {
@@ -30,53 +31,49 @@ const Unfollow = ({
size?: 'small' | 'default'
}) => {
const [hover, setHover] = useState(false)
+ const [unfollow] = useMutation(UNFOLLOW_USER, {
+ variables: { id: user.id },
+ optimisticResponse: {
+ unfollowUser: {
+ id: user.id,
+ isFollowee: false,
+ isFollower: user.isFollower,
+ __typename: 'User'
+ }
+ },
+ update: cache => {
+ const userName = _get(user, 'userName', null)
+ updateUserFollowerCount({ cache, type: 'decrement', userName })
+ updateViewerFolloweeCount({ cache, type: 'decrement' })
+ }
+ })
return (
- {
- const userName = _get(user, 'userName', null)
- updateUserFollowerCount({ cache, type: 'decrement', userName })
- updateViewerFolloweeCount({ cache, type: 'decrement' })
+ {
+ unfollow()
+ analytics.trackEvent(ANALYTICS_EVENTS.UNFOLLOW_USER, {
+ id: user.id
+ })
}}
+ bgColor={hover ? 'red' : 'green'}
+ onMouseEnter={() => setHover(true)}
+ onMouseLeave={() => setHover(false)}
>
- {(unfollow: any, { data }: any) => (
- {
- unfollow()
- analytics.trackEvent(ANALYTICS_EVENTS.UNFOLLOW_USER, {
- id: user.id
- })
- }}
- bgColor={hover ? 'red' : 'green'}
- onMouseEnter={() => setHover(true)}
- onMouseLeave={() => setHover(false)}
- >
- {hover ? (
-
- ) : (
-
- )}
-
+ {hover ? (
+
+ ) : (
+
)}
-
+
)
}
diff --git a/src/components/ShareButton/Douban.tsx b/src/components/Button/Share/Douban.tsx
similarity index 97%
rename from src/components/ShareButton/Douban.tsx
rename to src/components/Button/Share/Douban.tsx
index 71be0583e2..f6df476aac 100644
--- a/src/components/ShareButton/Douban.tsx
+++ b/src/components/Button/Share/Douban.tsx
@@ -1,4 +1,3 @@
-import _get from 'lodash/get'
import queryString from 'query-string'
import { Icon } from '~/components/Icon'
diff --git a/src/components/ShareButton/Email.tsx b/src/components/Button/Share/Email.tsx
similarity index 97%
rename from src/components/ShareButton/Email.tsx
rename to src/components/Button/Share/Email.tsx
index 0b8a78bd9e..43ff24c24d 100644
--- a/src/components/ShareButton/Email.tsx
+++ b/src/components/Button/Share/Email.tsx
@@ -1,4 +1,3 @@
-import _get from 'lodash/get'
import queryString from 'query-string'
import { Icon } from '~/components/Icon'
diff --git a/src/components/ShareButton/Facebook.tsx b/src/components/Button/Share/Facebook.tsx
similarity index 97%
rename from src/components/ShareButton/Facebook.tsx
rename to src/components/Button/Share/Facebook.tsx
index 3aef327374..c41870858b 100644
--- a/src/components/ShareButton/Facebook.tsx
+++ b/src/components/Button/Share/Facebook.tsx
@@ -1,4 +1,3 @@
-import _get from 'lodash/get'
import queryString from 'query-string'
import { Icon } from '~/components/Icon'
diff --git a/src/components/ShareButton/LINE.tsx b/src/components/Button/Share/LINE.tsx
similarity index 97%
rename from src/components/ShareButton/LINE.tsx
rename to src/components/Button/Share/LINE.tsx
index 80a181ba4a..3c8915ae6e 100644
--- a/src/components/ShareButton/LINE.tsx
+++ b/src/components/Button/Share/LINE.tsx
@@ -1,4 +1,3 @@
-import _get from 'lodash/get'
import queryString from 'query-string'
import { Icon } from '~/components/Icon'
diff --git a/src/components/ShareButton/ShareModal.tsx b/src/components/Button/Share/ShareModal.tsx
similarity index 98%
rename from src/components/ShareButton/ShareModal.tsx
rename to src/components/Button/Share/ShareModal.tsx
index 41dd7d07bc..09ac350fde 100644
--- a/src/components/ShareButton/ShareModal.tsx
+++ b/src/components/Button/Share/ShareModal.tsx
@@ -1,5 +1,3 @@
-import _get from 'lodash/get'
-
import { Icon } from '~/components/Icon'
import { Translate } from '~/components/Language'
import { Modal } from '~/components/Modal'
diff --git a/src/components/ShareButton/Telegram.tsx b/src/components/Button/Share/Telegram.tsx
similarity index 97%
rename from src/components/ShareButton/Telegram.tsx
rename to src/components/Button/Share/Telegram.tsx
index e83ff6ac81..043f060157 100644
--- a/src/components/ShareButton/Telegram.tsx
+++ b/src/components/Button/Share/Telegram.tsx
@@ -1,4 +1,3 @@
-import _get from 'lodash/get'
import queryString from 'query-string'
import { Icon } from '~/components/Icon'
diff --git a/src/components/ShareButton/Twitter.tsx b/src/components/Button/Share/Twitter.tsx
similarity index 97%
rename from src/components/ShareButton/Twitter.tsx
rename to src/components/Button/Share/Twitter.tsx
index b9728f90ad..a8dad8d6b1 100644
--- a/src/components/ShareButton/Twitter.tsx
+++ b/src/components/Button/Share/Twitter.tsx
@@ -1,4 +1,3 @@
-import _get from 'lodash/get'
import queryString from 'query-string'
import { Icon } from '~/components/Icon'
diff --git a/src/components/ShareButton/WeChat.tsx b/src/components/Button/Share/WeChat.tsx
similarity index 94%
rename from src/components/ShareButton/WeChat.tsx
rename to src/components/Button/Share/WeChat.tsx
index cbc75a7abc..10b903cc49 100644
--- a/src/components/ShareButton/WeChat.tsx
+++ b/src/components/Button/Share/WeChat.tsx
@@ -1,5 +1,3 @@
-import _get from 'lodash/get'
-
import { Icon } from '~/components/Icon'
import { Translate } from '~/components/Language'
import { TextIcon } from '~/components/TextIcon'
diff --git a/src/components/ShareButton/Weibo.tsx b/src/components/Button/Share/Weibo.tsx
similarity index 97%
rename from src/components/ShareButton/Weibo.tsx
rename to src/components/Button/Share/Weibo.tsx
index a09ad900fa..985a3c65ea 100644
--- a/src/components/ShareButton/Weibo.tsx
+++ b/src/components/Button/Share/Weibo.tsx
@@ -1,4 +1,3 @@
-import _get from 'lodash/get'
import queryString from 'query-string'
import { Icon } from '~/components/Icon'
diff --git a/src/components/ShareButton/WhatsApp.tsx b/src/components/Button/Share/WhatsApp.tsx
similarity index 97%
rename from src/components/ShareButton/WhatsApp.tsx
rename to src/components/Button/Share/WhatsApp.tsx
index 7b3cce444d..250f280670 100644
--- a/src/components/ShareButton/WhatsApp.tsx
+++ b/src/components/Button/Share/WhatsApp.tsx
@@ -1,4 +1,3 @@
-import _get from 'lodash/get'
import queryString from 'query-string'
import { Icon } from '~/components/Icon'
diff --git a/src/components/ShareButton/index.tsx b/src/components/Button/Share/index.tsx
similarity index 100%
rename from src/components/ShareButton/index.tsx
rename to src/components/Button/Share/index.tsx
diff --git a/src/components/ShareButton/styles.css b/src/components/Button/Share/styles.css
similarity index 100%
rename from src/components/ShareButton/styles.css
rename to src/components/Button/Share/styles.css
diff --git a/src/components/Button/Shuffle/index.tsx b/src/components/Button/Shuffle/index.tsx
index d2200cedad..b1c4d87869 100644
--- a/src/components/Button/Shuffle/index.tsx
+++ b/src/components/Button/Shuffle/index.tsx
@@ -18,6 +18,7 @@ export const ShuffleButton = ({ onClick }: { onClick: () => void }) => (
>
+
)
diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx
index 9bca09c68c..40e73bbf95 100644
--- a/src/components/Button/index.tsx
+++ b/src/components/Button/index.tsx
@@ -169,57 +169,53 @@ export const Button: React.FC = ({
// anchor
if (is === 'anchor') {
return (
- <>
-
- {icon}
- {children && {children}}
-
+
+ {icon}
+ {children && {children}}
+
- >
+
)
}
// link
if (is === 'link') {
return (
- <>
-
-
- {icon}
- {children && {children}}
-
-
-
- >
+
+
+ {icon}
+ {children && {children}}
+
+
+
+
)
}
// span
if (is === 'span') {
return (
- <>
-
- {icon}
- {children && {children}}
-
+
+ {icon}
+ {children && {children}}
+
- >
+
)
}
// button
return (
- <>
-
- {icon}
- {children && {children}}
-
+
+ {icon}
+ {children && {children}}
+
- >
+
)
}
diff --git a/src/components/CollectionEditor/CollectForm.tsx b/src/components/CollectionEditor/CollectForm.tsx
index 459026c357..c270a84ab6 100644
--- a/src/components/CollectionEditor/CollectForm.tsx
+++ b/src/components/CollectionEditor/CollectForm.tsx
@@ -1,10 +1,9 @@
import _debounce from 'lodash/debounce'
-import _get from 'lodash/get'
-import { FC, useContext, useRef, useState } from 'react'
-import { QueryResult } from 'react-apollo'
+import { useContext, useRef, useState } from 'react'
+import { useQuery } from 'react-apollo'
+import { useDebounce } from 'use-debounce'
import ArticleList from '~/components/Dropdown/ArticleList'
-import { Query } from '~/components/GQL'
import {
SearchArticles,
SearchArticles_search_edges_node_Article
@@ -13,6 +12,7 @@ import SEARCH_ARTICLES from '~/components/GQL/queries/searchArticles'
import { LanguageContext } from '~/components/Language'
import { Dropdown, PopperInstance } from '~/components/Popper'
+import { INPUT_DEBOUNCE } from '~/common/enums'
import { translate } from '~/common/utils'
import styles from './styles.css'
@@ -21,16 +21,24 @@ interface Props {
onAdd: (article: SearchArticles_search_edges_node_Article) => void
}
-const debouncedSetSearch = _debounce((value, setSearch) => {
- setSearch(value)
-}, 300)
-
-const CollectForm: FC = ({ onAdd }) => {
+const CollectForm: React.FC = ({ onAdd }) => {
const { lang } = useContext(LanguageContext)
const [search, setSearch] = useState('')
+ const [debouncedSearch] = useDebounce(search, INPUT_DEBOUNCE)
const [instance, setInstance] = useState(null)
const inputNode: React.RefObject | null = useRef(null)
+ // query
+ const { loading, data } = useQuery(SEARCH_ARTICLES, {
+ variables: { search: debouncedSearch },
+ skip: !debouncedSearch
+ })
+ const articles = ((data && data.search.edges) || [])
+ .filter(({ node }) => node.__typename === 'Article')
+ .map(({ node }) => node) as SearchArticles_search_edges_node_Article[]
+
+ // dropdown
+ const isShowDropdown = (articles && articles.length) || loading
const hideDropdown = () => {
if (instance) {
instance.hide()
@@ -38,74 +46,59 @@ const CollectForm: FC = ({ onAdd }) => {
}
const showDropdown = () => {
if (instance) {
- setTimeout(() => {
- instance.show()
- }, 100) // unknown bug, needs set a timeout
+ instance.show()
}
}
- return (
-
- {({ data, loading }: QueryResult & { data: SearchArticles }) => {
- const articles = _get(data, 'search.edges', []).map(
- ({ node }: { node: SearchArticles_search_edges_node_Article }) => node
- )
- const isShowDropdown = (articles && articles.length) || loading
-
- if (isShowDropdown) {
- showDropdown()
- } else {
- hideDropdown()
- }
+ if (isShowDropdown) {
+ showDropdown()
+ } else {
+ hideDropdown()
+ }
- return (
- <>
- {
- onAdd(article)
- setSearch('')
- hideDropdown()
- if (inputNode && inputNode.current) {
- inputNode.current.value = ''
- }
- }}
- />
+ return (
+ <>
+ {
+ onAdd(article)
+ setSearch('')
+ hideDropdown()
+ if (inputNode && inputNode.current) {
+ inputNode.current.value = ''
}
- >
- {
- const value = event.target.value
- debouncedSetSearch(value, setSearch)
- }}
- onFocus={() => {
- if (isShowDropdown) {
- showDropdown()
- }
- }}
- />
-
+ }}
+ />
+ }
+ >
+ {
+ const value = event.target.value
+ setSearch(value)
+ }}
+ onFocus={() => {
+ if (isShowDropdown) {
+ showDropdown()
+ }
+ }}
+ />
+
-
- >
- )
- }}
-
+
+ >
)
}
diff --git a/src/components/CommentDigest/Content/index.tsx b/src/components/CommentDigest/Content/index.tsx
index 74ae1db007..41d7a375ee 100644
--- a/src/components/CommentDigest/Content/index.tsx
+++ b/src/components/CommentDigest/Content/index.tsx
@@ -21,6 +21,7 @@ const CommentContent = ({
__html: content || ''
}}
/>
+
>
@@ -34,6 +35,7 @@ const CommentContent = ({
zh_hant="此評論因違反用戶協定而被隱藏"
zh_hans="此评论因违反用户协定而被隐藏"
/>
+
)
@@ -46,6 +48,7 @@ const CommentContent = ({
zh_hant={TEXT.zh_hant.commentDeleted}
zh_hans={TEXT.zh_hans.commentDeleted}
/>
+
)
diff --git a/src/components/CommentDigest/DropdownActions/DeleteButton.tsx b/src/components/CommentDigest/DropdownActions/DeleteButton.tsx
index caed296e63..fda59e1d34 100644
--- a/src/components/CommentDigest/DropdownActions/DeleteButton.tsx
+++ b/src/components/CommentDigest/DropdownActions/DeleteButton.tsx
@@ -1,12 +1,12 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
import { Icon, TextIcon, Translate } from '~/components'
-import { Mutation } from '~/components/GQL'
+import { useMutation } from '~/components/GQL'
import { TEXT } from '~/common/enums'
import ICON_REMOVE from '~/static/icons/remove.svg?sprite'
+import { DeleteComment } from './__generated__/DeleteComment'
import styles from './styles.css'
const DELETE_COMMENT = gql`
@@ -22,45 +22,43 @@ const DeleteButton: React.FC<{
commentId: string
hideDropdown: () => void
}> = ({ commentId, hideDropdown }) => {
+ const [deleteComment] = useMutation(DELETE_COMMENT, {
+ variables: { id: commentId },
+ optimisticResponse: {
+ deleteComment: {
+ id: commentId,
+ state: 'archived' as any,
+ __typename: 'Comment'
+ }
+ }
+ })
+
return (
- {
+ deleteComment()
+ hideDropdown()
}}
>
- {(deleteComment: any) => (
- {
- deleteComment()
- hideDropdown()
- }}
- >
-
- }
- spacing="tight"
- >
-
-
-
-
- )}
-
+
+ }
+ spacing="tight"
+ >
+
+
+
+
+
)
}
diff --git a/src/components/CommentDigest/DropdownActions/EditButton.tsx b/src/components/CommentDigest/DropdownActions/EditButton.tsx
index e97c0c5cb1..0350446554 100644
--- a/src/components/CommentDigest/DropdownActions/EditButton.tsx
+++ b/src/components/CommentDigest/DropdownActions/EditButton.tsx
@@ -32,6 +32,7 @@ const EditButton = ({
>
+
)
diff --git a/src/components/CommentDigest/DropdownActions/PinButton.tsx b/src/components/CommentDigest/DropdownActions/PinButton.tsx
index bfa9e4a623..652874e889 100644
--- a/src/components/CommentDigest/DropdownActions/PinButton.tsx
+++ b/src/components/CommentDigest/DropdownActions/PinButton.tsx
@@ -1,14 +1,15 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
import { Icon, TextIcon, Translate } from '~/components'
-import { Mutation } from '~/components/GQL'
+import { useMutation } from '~/components/GQL'
import { TEXT } from '~/common/enums'
import ICON_PIN_TO_TOP from '~/static/icons/pin-to-top.svg?sprite'
import ICON_UNPIN from '~/static/icons/unpin.svg?sprite'
import { PinButtonComment } from './__generated__/PinButtonComment'
+import { PinComment } from './__generated__/PinComment'
+import { UnpinComment } from './__generated__/UnpinComment'
import styles from './styles.css'
const PIN_COMMENT = gql`
@@ -86,62 +87,62 @@ const PinButton = ({
hideDropdown: () => void
}) => {
const canPin = comment.article.pinCommentLeft > 0
+ const [unpinComment] = useMutation(UNPIN_COMMENT, {
+ variables: { id: comment.id },
+ optimisticResponse: {
+ unpinComment: {
+ id: comment.id,
+ pinned: false,
+ article: {
+ ...comment.article
+ },
+ __typename: 'Comment'
+ }
+ }
+ })
+ const [pinComment] = useMutation(PIN_COMMENT, {
+ variables: { id: comment.id },
+ optimisticResponse: {
+ pinComment: {
+ id: comment.id,
+ pinned: true,
+ article: {
+ ...comment.article
+ },
+ __typename: 'Comment'
+ }
+ }
+ })
if (comment.pinned) {
return (
- {
+ unpinComment()
+ hideDropdown()
}}
>
- {(unpinComment: any) => (
- {
- unpinComment()
- hideDropdown()
- }}
- >
-
-
-
- )}
-
+
+
+
+
)
}
return (
- {
+ pinComment()
+ hideDropdown()
}}
+ disabled={!canPin}
>
- {(pinComment: any) => (
- {
- pinComment()
- hideDropdown()
- }}
- disabled={!canPin}
- >
-
-
-
- )}
-
+
+
+
+
)
}
diff --git a/src/components/CommentDigest/DropdownActions/ReportButton.tsx b/src/components/CommentDigest/DropdownActions/ReportButton.tsx
index 24f1fd865c..f07a3550e1 100644
--- a/src/components/CommentDigest/DropdownActions/ReportButton.tsx
+++ b/src/components/CommentDigest/DropdownActions/ReportButton.tsx
@@ -31,6 +31,7 @@ const EditButton = ({
zh_hans={TEXT.zh_hans.report}
/>
+
)
diff --git a/src/components/CommentDigest/DropdownActions/index.tsx b/src/components/CommentDigest/DropdownActions/index.tsx
index de0be77ce2..7a5b4a083a 100644
--- a/src/components/CommentDigest/DropdownActions/index.tsx
+++ b/src/components/CommentDigest/DropdownActions/index.tsx
@@ -2,6 +2,7 @@ import gql from 'graphql-tag'
import { useContext, useState } from 'react'
import { Dropdown, Icon, Menu, PopperInstance } from '~/components'
+import BlockUserButton from '~/components/Button/BlockUser/Dropdown'
import { ViewerContext } from '~/components/Viewer'
import ICON_MORE_SMALL from '~/static/icons/more-small.svg?sprite'
@@ -19,6 +20,7 @@ const fragments = {
state
author {
id
+ ...BlockUser
}
parentComment {
id
@@ -33,50 +35,10 @@ const fragments = {
...PinButtonComment
}
${PinButton.fragments.comment}
+ ${BlockUserButton.fragments.user}
`
}
-const DropdownContent: React.FC<{
- comment: DropdownActionsComment
- hideDropdown: () => void
- editComment?: () => void
- isShowPinButton: boolean
- isShowEditButton: boolean
- isShowDeleteButton: boolean
-}> = ({
- comment,
- editComment,
- hideDropdown,
- isShowPinButton,
- isShowEditButton,
- isShowDeleteButton
-}) => {
- return (
-
- )
-}
-
const DropdownActions = ({
comment,
editComment
@@ -84,6 +46,7 @@ const DropdownActions = ({
comment: DropdownActionsComment
editComment?: () => void
}) => {
+ const [shown, setShown] = useState(false)
const [instance, setInstance] = useState(null)
const hideDropdown = () => {
if (!instance) {
@@ -100,12 +63,17 @@ const DropdownActions = ({
const isCommentAuthor = viewer.id === comment.author.id
const isActive = comment.state === 'active'
const isDescendantComment = comment.parentComment
+
const isShowPinButton = isArticleAuthor && isActive && !isDescendantComment
const isShowEditButton = isCommentAuthor && !!editComment && isActive
const isShowDeleteButton = isCommentAuthor && isActive
+ const isShowBlockUserButton = !isCommentAuthor
if (
- (!isShowPinButton && !isShowEditButton && !isShowDeleteButton) ||
+ (!isShowPinButton &&
+ !isShowEditButton &&
+ !isShowDeleteButton &&
+ !isShowBlockUserButton) ||
viewer.isInactive
) {
return null
@@ -114,17 +82,47 @@ const DropdownActions = ({
return (
+
}
trigger="click"
onCreate={setInstance}
+ onShown={() => setShown(true)}
placement="bottom-end"
zIndex={301}
>
diff --git a/src/components/CommentDigest/FeedDigest/index.tsx b/src/components/CommentDigest/FeedDigest/index.tsx
index 52ce881496..6c46e76cd4 100644
--- a/src/components/CommentDigest/FeedDigest/index.tsx
+++ b/src/components/CommentDigest/FeedDigest/index.tsx
@@ -1,11 +1,11 @@
import classNames from 'classnames'
-import _get from 'lodash/get'
import { useState } from 'react'
import CommentForm from '~/components/Form/CommentForm'
import {
FeedDigestComment,
- FeedDigestComment_comments_edges_node
+ FeedDigestComment_comments_edges_node,
+ FeedDigestComment_comments_edges_node_replyTo_author
} from '~/components/GQL/fragments/__generated__/FeedDigestComment'
import commentFragments from '~/components/GQL/fragments/comment'
import { Icon } from '~/components/Icon'
@@ -28,11 +28,18 @@ const fragments = {
comment: commentFragments.feed
}
-const ReplyTo = ({ user, inArticle }: { user: any; inArticle: boolean }) => (
+const ReplyTo = ({
+ user,
+ inArticle
+}: {
+ user: FeedDigestComment_comments_edges_node_replyTo_author
+ inArticle: boolean
+}) => (
+
(
spacing="xxtight"
hasUserName={inArticle}
/>
+
)
@@ -52,6 +60,7 @@ const PinnedLabel = () => (
zh_hans={TEXT.zh_hant.authorRecommend}
/>
+
)
@@ -59,6 +68,7 @@ const PinnedLabel = () => (
const CancelEditButton = ({ onClick }: { onClick: () => void }) => (
onClick()}>
+
)
@@ -111,10 +121,11 @@ const DescendantComment = ({
setEdit(false)}
extraButton={ setEdit(false)} />}
+ blocked={comment.article.author.isBlocking}
defaultExpand={edit}
+ defaultContent={comment.content}
/>
)}
{!edit && (
@@ -149,9 +160,10 @@ const FeedDigest = ({
} & FooterActionsControls) => {
const [edit, setEdit] = useState(false)
const { state, content, author, replyTo, parentComment, pinned } = comment
- const descendantComments = _get(comment, 'comments.edges', []).filter(
- ({ node }: { node: any }) => node.state === 'active'
- )
+ const descendantComments = (
+ (comment.comments && comment.comments.edges) ||
+ []
+ ).filter(({ node }) => node.state === 'active')
const restDescendantCommentCount =
descendantComments.length - COLLAPSE_DESCENDANT_COUNT
const [expand, setExpand] = useState(
@@ -191,9 +203,10 @@ const FeedDigest = ({
setEdit(false)}
extraButton={ setEdit(false)} />}
+ blocked={comment.article.author.isBlocking}
+ defaultContent={comment.content}
defaultExpand={edit}
/>
)}
@@ -211,7 +224,7 @@ const FeedDigest = ({
{descendantComments
.slice(0, expand ? undefined : COLLAPSE_DESCENDANT_COUNT)
- .map(({ node, cursor }: { node: any; cursor: any }) => (
+ .map(({ node, cursor }) => (
-
{
+ const viewer = useContext(ViewerContext)
+ const [unvote] = useMutation(UNVOTE_COMMENT, {
+ variables: { id: comment.id },
+ optimisticResponse: {
+ unvoteComment: {
+ id: comment.id,
+ upvotes: comment.upvotes,
+ downvotes: comment.downvotes - 1,
+ myVote: null,
+ __typename: 'Comment'
+ }
+ }
+ })
+ const [downvote] = useMutation(VOTE_COMMENT, {
+ variables: { id: comment.id, vote: 'down' },
+ optimisticResponse: {
+ voteComment: {
+ id: comment.id,
+ upvotes:
+ comment.myVote === 'up' ? comment.upvotes - 1 : comment.upvotes,
+ downvotes: comment.downvotes + 1,
+ myVote: 'down' as any,
+ __typename: 'Comment'
+ }
+ }
+ })
+
if (comment.myVote === 'down') {
return (
-
- {(unvote: any, { data }: any) => (
- unvote()} disabled={disabled}>
+
+ {(open: any) => (
+ {
+ if (viewer.shouldSetupLikerID) {
+ open()
+ } else {
+ unvote()
+ }
+ }}
+ disabled={disabled}
+ >
}
color="grey"
@@ -74,27 +103,24 @@ const DownvoteButton = ({
/>
)}
-
+
)
}
return (
-
- {(downvote: any, { data }: any) => (
- downvote()} disabled={disabled}>
+
+ {(open: any) => (
+ {
+ if (viewer.shouldSetupLikerID) {
+ open()
+ } else {
+ downvote()
+ }
+ }}
+ disabled={disabled}
+ >
}
color="grey"
@@ -105,7 +131,7 @@ const DownvoteButton = ({
/>
)}
-
+
)
}
diff --git a/src/components/CommentDigest/FooterActions/UpvoteButton.tsx b/src/components/CommentDigest/FooterActions/UpvoteButton.tsx
index 0f276e994c..b836b6d9f4 100644
--- a/src/components/CommentDigest/FooterActions/UpvoteButton.tsx
+++ b/src/components/CommentDigest/FooterActions/UpvoteButton.tsx
@@ -1,12 +1,16 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
+import { useContext } from 'react'
import { Icon, TextIcon } from '~/components'
-import { Mutation } from '~/components/GQL'
+import { useMutation } from '~/components/GQL'
+import { UnvoteComment } from '~/components/GQL/mutations/__generated__/UnvoteComment'
+import { VoteComment } from '~/components/GQL/mutations/__generated__/VoteComment'
import {
UNVOTE_COMMENT,
VOTE_COMMENT
} from '~/components/GQL/mutations/voteComment'
+import { ModalSwitch } from '~/components/ModalManager'
+import { ViewerContext } from '~/components/Viewer'
import { numAbbr } from '~/common/utils'
import ICON_LIKE_ACTIVE from '~/static/icons/like-active.svg?sprite'
@@ -47,23 +51,48 @@ const UpvoteButton = ({
comment: UpvoteComment
disabled?: boolean
}) => {
+ const viewer = useContext(ViewerContext)
+ const [unvote] = useMutation(UNVOTE_COMMENT, {
+ variables: { id: comment.id },
+ optimisticResponse: {
+ unvoteComment: {
+ id: comment.id,
+ upvotes: comment.upvotes - 1,
+ downvotes: comment.downvotes,
+ myVote: null,
+ __typename: 'Comment'
+ }
+ }
+ })
+ const [upvote] = useMutation(VOTE_COMMENT, {
+ variables: { id: comment.id, vote: 'up' },
+ optimisticResponse: {
+ voteComment: {
+ id: comment.id,
+ upvotes: comment.upvotes + 1,
+ downvotes:
+ comment.myVote === 'down' ? comment.downvotes - 1 : comment.downvotes,
+ myVote: 'up' as any,
+ __typename: 'Comment'
+ }
+ }
+ })
+
if (comment.myVote === 'up') {
return (
-
- {(unvote: any, { data }: any) => (
- unvote()} disabled={disabled}>
+
+ {(open: any) => (
+ {
+ if (viewer.shouldSetupLikerID) {
+ open()
+ } else {
+ unvote()
+ }
+ }}
+ disabled={disabled}
+ >
}
color="grey"
@@ -74,29 +103,24 @@ const UpvoteButton = ({
/>
)}
-
+
)
}
return (
-
- {(upvote: any, { data }: any) => (
- upvote()} disabled={disabled}>
+
+ {(open: any) => (
+ {
+ if (viewer.shouldSetupLikerID) {
+ open()
+ } else {
+ upvote()
+ }
+ }}
+ disabled={disabled}
+ >
}
color="grey"
@@ -107,7 +131,7 @@ const UpvoteButton = ({
/>
)}
-
+
)
}
diff --git a/src/components/CommentDigest/FooterActions/index.tsx b/src/components/CommentDigest/FooterActions/index.tsx
index 6f9a13d860..3316b9e58a 100644
--- a/src/components/CommentDigest/FooterActions/index.tsx
+++ b/src/components/CommentDigest/FooterActions/index.tsx
@@ -1,11 +1,11 @@
import gql from 'graphql-tag'
import jump from 'jump.js'
-import _get from 'lodash/get'
import { useRouter } from 'next/router'
import { useContext, useState } from 'react'
import { DateTime, Icon } from '~/components'
import CommentForm from '~/components/Form/CommentForm'
+import { ModalSwitch } from '~/components/ModalManager'
import { ViewerContext } from '~/components/Viewer'
import { PATHS } from '~/common/enums'
@@ -38,7 +38,9 @@ const fragments = {
slug
mediaHash
author {
+ id
userName
+ isBlocking
}
}
parentComment {
@@ -73,6 +75,7 @@ const FooterActions: React.FC & {
const { parentComment, id } = comment
const { slug, mediaHash, author } = comment.article
+ const isBlockedByAuthor = author.isBlocking
const fragment =
parentComment && parentComment.id ? `${parentComment.id}-${id}` : id
const commentPath =
@@ -110,20 +113,31 @@ const FooterActions: React.FC & {
{hasForm && (
<>
- {
- setShowForm(!showForm)
- }}
- disabled={!isActive || viewer.isInactive}
- >
-
-
+
+
+ {(open: any) => (
+ {
+ if (viewer.shouldSetupLikerID) {
+ open()
+ } else {
+ setShowForm(!showForm)
+ }
+ }}
+ disabled={
+ !isActive || viewer.isInactive || isBlockedByAuthor
+ }
+ >
+
+
+ )}
+
>
)}
@@ -152,9 +166,12 @@ const FooterActions: React.FC & {
)}
diff --git a/src/components/DateTime/index.tsx b/src/components/DateTime/index.tsx
index 830a074382..3ba859a1c3 100644
--- a/src/components/DateTime/index.tsx
+++ b/src/components/DateTime/index.tsx
@@ -33,6 +33,7 @@ export const DateTime: React.FC = ({
{({ lang }) => (
)}
diff --git a/src/components/DraftDigest/Components/DeleteButton.tsx b/src/components/DraftDigest/Components/DeleteButton.tsx
index de094f329b..64b65a8415 100644
--- a/src/components/DraftDigest/Components/DeleteButton.tsx
+++ b/src/components/DraftDigest/Components/DeleteButton.tsx
@@ -1,10 +1,13 @@
import gql from 'graphql-tag'
import { Translate } from '~/components'
-import { Mutation } from '~/components/GQL'
+import { useMutation } from '~/components/GQL'
import { TEXT } from '~/common/enums'
+import { DeleteDraft } from './__generated__/DeleteDraft'
+import { ViewerDrafts } from './__generated__/ViewerDrafts'
+
const DELETE_DRAFT = gql`
mutation DeleteDraft($id: ID!) {
deleteDraft(input: { id: $id })
@@ -27,53 +30,47 @@ const ME_DRADTS = gql`
`
const DeleteButton = ({ id }: { id: string }) => {
- return (
- {
- try {
- const data = cache.readQuery({ query: ME_DRADTS })
+ const [deleteDraft] = useMutation(DELETE_DRAFT, {
+ variables: { id },
+ update: cache => {
+ try {
+ const data = cache.readQuery({ query: ME_DRADTS })
- if (
- !data ||
- !data.viewer ||
- !data.viewer.drafts ||
- !data.viewer.drafts.edges
- ) {
- return
- }
+ if (
+ !data ||
+ !data.viewer ||
+ !data.viewer.drafts ||
+ !data.viewer.drafts.edges
+ ) {
+ return
+ }
- const edges = data.viewer.drafts.edges.filter(
- ({ node }: { node: any }) => node.id !== id
- )
+ const edges = data.viewer.drafts.edges.filter(
+ ({ node }) => node.id !== id
+ )
- cache.writeQuery({
- query: ME_DRADTS,
- data: {
- viewer: {
- ...data.viewer,
- drafts: {
- ...data.viewer.drafts,
- edges
- }
+ cache.writeQuery({
+ query: ME_DRADTS,
+ data: {
+ viewer: {
+ ...data.viewer,
+ drafts: {
+ ...data.viewer.drafts,
+ edges
}
}
- })
- } catch (e) {
- console.error(e)
- }
- }}
- >
- {(deleteDraft: any) => (
- deleteDraft()}>
-
-
- )}
-
+ }
+ })
+ } catch (e) {
+ console.error(e)
+ }
+ }
+ })
+
+ return (
+ deleteDraft()}>
+
+
)
}
diff --git a/src/components/DraftDigest/Components/PendingState.tsx b/src/components/DraftDigest/Components/PendingState.tsx
index 04bc6fc452..dbc61b3629 100644
--- a/src/components/DraftDigest/Components/PendingState.tsx
+++ b/src/components/DraftDigest/Components/PendingState.tsx
@@ -1,4 +1,4 @@
-import { Query, QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import { Icon, TextIcon, Translate } from '~/components'
import { DraftPublishState } from '~/components/GQL/queries/__generated__/DraftPublishState'
@@ -19,69 +19,63 @@ const PendingState = ({ draft }: { draft: FeedDigestDraft }) => {
} = useCountdown({ timeLeft: Date.parse(scheduledAt) - Date.now() })
const isPublishing = !scheduledAt || !timeLeft || timeLeft <= 0
- return (
-
- {({ data }: QueryResult & { data: DraftPublishState }) => {
- if (
- data &&
- data.node &&
- data.node.publishState === 'published' &&
- process.browser
- ) {
- window.dispatchEvent(
- new CustomEvent(ADD_TOAST, {
- detail: {
- color: 'green',
- content: (
-
- )
- }
- })
+ const { data } = useQuery(DRAFT_PUBLISH_STATE, {
+ variables: { id: draft.id },
+ pollInterval: 1000 * 5,
+ errorPolicy: 'none',
+ fetchPolicy: 'network-only',
+ skip: !process.browser || !isPublishing
+ })
+
+ if (
+ data &&
+ data.node &&
+ data.node.__typename === 'Draft' &&
+ data.node.publishState === 'published' &&
+ process.browser
+ ) {
+ window.dispatchEvent(
+ new CustomEvent(ADD_TOAST, {
+ detail: {
+ color: 'green',
+ content: (
+
)
}
+ })
+ )
+ }
- return (
-
- }
- size="sm"
- color="green"
- weight="medium"
- >
- {isPublishing ? (
-
- ) : (
-
- )}
-
- )
- }}
-
+ return (
+
+ }
+ size="sm"
+ color="green"
+ weight="medium"
+ >
+ {isPublishing ? (
+
+ ) : (
+
+ )}
+
)
}
diff --git a/src/components/DraftDigest/Components/RecallButton.tsx b/src/components/DraftDigest/Components/RecallButton.tsx
index dfd102719b..da7411ba09 100644
--- a/src/components/DraftDigest/Components/RecallButton.tsx
+++ b/src/components/DraftDigest/Components/RecallButton.tsx
@@ -1,10 +1,12 @@
import gql from 'graphql-tag'
import { Translate } from '~/components'
-import { Mutation } from '~/components/GQL'
+import { useMutation } from '~/components/GQL'
import { TEXT } from '~/common/enums'
+import { RecallPublish } from './__generated__/RecallPublish'
+
const RECALL_PUBLISH = gql`
mutation RecallPublish($id: ID!) {
recallPublish(input: { id: $id }) {
@@ -16,30 +18,27 @@ const RECALL_PUBLISH = gql`
`
const RecallButton = ({ id, text }: { id: string; text?: React.ReactNode }) => {
+ const [recall] = useMutation(RECALL_PUBLISH, {
+ variables: { id },
+ optimisticResponse: {
+ recallPublish: {
+ id,
+ scheduledAt: null,
+ publishState: 'unpublished' as any,
+ __typename: 'Draft'
+ }
+ }
+ })
+
return (
-
- {(recall: any) => (
- recall()}>
- {text || (
-
- )}
-
+ recall()}>
+ {text || (
+
)}
-
+
)
}
diff --git a/src/components/DraftDigest/Components/RetryButton.tsx b/src/components/DraftDigest/Components/RetryButton.tsx
index b947b30745..9512ec0f10 100644
--- a/src/components/DraftDigest/Components/RetryButton.tsx
+++ b/src/components/DraftDigest/Components/RetryButton.tsx
@@ -1,10 +1,12 @@
import gql from 'graphql-tag'
import { Translate } from '~/components'
-import { Mutation } from '~/components/GQL'
+import { useMutation } from '~/components/GQL'
import { TEXT } from '~/common/enums'
+import { RetryPublish } from './__generated__/RetryPublish'
+
const RETRY_PUBLISH = gql`
mutation RetryPublish($id: ID!) {
retryPublish: publishArticle(input: { id: $id }) {
@@ -16,31 +18,22 @@ const RETRY_PUBLISH = gql`
`
const RetryButton = ({ id }: { id: string }) => {
+ const [retry] = useMutation(RETRY_PUBLISH, {
+ variables: { id },
+ optimisticResponse: {
+ retryPublish: {
+ id,
+ scheduledAt: new Date(Date.now() + 1000).toISOString(),
+ publishState: 'pending' as any,
+ __typename: 'Draft'
+ }
+ }
+ })
+
return (
-
- {(retry: any) => (
-
- retry({
- optimisticResponse: {
- retryPublish: {
- id,
- scheduledAt: new Date(Date.now() + 1000).toISOString(),
- publishState: 'pending',
- __typename: 'Draft'
- }
- }
- })
- }
- >
-
-
- )}
-
+ retry()}>
+
+
)
}
diff --git a/src/components/Drawer/index.tsx b/src/components/Drawer/index.tsx
deleted file mode 100644
index a62edc018e..0000000000
--- a/src/components/Drawer/index.tsx
+++ /dev/null
@@ -1,88 +0,0 @@
-// import { disableBodyScroll, enableBodyScroll } from 'body-scroll-lock'
-import React, { useState } from 'react'
-
-import { dom } from '~/common/utils'
-
-import styles from './styles.css'
-
-export const DrawerContext = React.createContext({
- opened: false,
- open: () => {
- // Do nothing
- },
- close: () => {
- // Do nothing
- }
-})
-
-export const DrawerConsumer = DrawerContext.Consumer
-
-export const DrawerProvider = ({ children }: { children: React.ReactNode }) => {
- const [opened, setOpened] = useState(false)
-
- return (
- setOpened(true),
- close: () => setOpened(false)
- }}
- >
- {children}
-
- )
-}
-
-interface DrawerState {
- width: null | undefined | number
-}
-
-export class Drawer extends React.Component<{}, DrawerState> {
- public state = { width: null }
-
- public componentDidMount() {
- window.addEventListener('resize', this.handleResize)
- this.handleResize()
- }
-
- public componentWillUnmount() {
- window.removeEventListener('resize', this.handleResize)
- }
-
- public handleResize = () => {
- this.setState({ width: this.calcWidth() })
- }
-
- public calcWidth = () => {
- if (!process.browser) {
- return null
- }
-
- try {
- return dom.getWindowWidth() - dom.offset(dom.$('#drawer-calc-hook')).left
- } catch (e) {
- return null
- }
- }
-
- public render() {
- const { width } = this.state
-
- return (
-
- {({ opened }) => {
- return (
-
- )
- }}
-
- )
- }
-}
diff --git a/src/components/Drawer/styles.css b/src/components/Drawer/styles.css
deleted file mode 100644
index 817615bab3..0000000000
--- a/src/components/Drawer/styles.css
+++ /dev/null
@@ -1,42 +0,0 @@
-aside {
- @mixin all-transition;
-
- position: fixed;
- top: 0;
- bottom: 0;
- right: 0;
-
- padding: var(--spacing-loose) var(--spacing-tight);
-
- box-shadow: -8px 0 24px 0 rgba(0, 0, 0, 0.08);
- background-color: var(--color-white);
- transform: translateX(100%);
- opacity: 0;
- overflow: auto;
- -webkit-overflow-scrolling: touch;
- z-index: 110;
-
- @media (--lg-down) {
- width: 30rem !important;
- }
-
- @media (--sm-down) {
- width: 100% !important;
- }
-
- @media (--sm-up) {
- margin-top: var(--global-header-height); /* fallback */
- margin-top: calc(var(--global-header-height) + 1px);
- padding: var(--spacing-loose) var(--spacing-default);
- z-index: 80;
- }
-
- @supports (position: sticky) {
- padding-top: 0;
- }
-
- &.opened {
- transform: translateX(0%);
- opacity: 1;
- }
-}
diff --git a/src/components/Dropdown/ArticleList/index.tsx b/src/components/Dropdown/ArticleList/index.tsx
index 40de116def..bb38dfd4e1 100644
--- a/src/components/Dropdown/ArticleList/index.tsx
+++ b/src/components/Dropdown/ArticleList/index.tsx
@@ -25,28 +25,27 @@ const DropdownArticleList = ({
}
return (
- <>
-
-
- >
+
+
+
+
+
+ ))}
+
)
}
diff --git a/src/components/Dropdown/UserList/index.tsx b/src/components/Dropdown/UserList/index.tsx
index 34571de7ae..1df8d7d9b0 100644
--- a/src/components/Dropdown/UserList/index.tsx
+++ b/src/components/Dropdown/UserList/index.tsx
@@ -45,6 +45,7 @@ const DropdownUserList = ({
))}
+
>
)
diff --git a/src/components/Editor/CommentEditor/index.tsx b/src/components/Editor/CommentEditor/index.tsx
index 72e48bb32f..185aedbc27 100644
--- a/src/components/Editor/CommentEditor/index.tsx
+++ b/src/components/Editor/CommentEditor/index.tsx
@@ -1,21 +1,20 @@
+import { QueryLazyOptions } from '@apollo/react-hooks'
import classNames from 'classnames'
-import _debounce from 'lodash/debounce'
-import _get from 'lodash/get'
-import React from 'react'
-import { QueryResult } from 'react-apollo'
+import React, { useContext } from 'react'
+import { QueryResult, useLazyQuery } from 'react-apollo'
import ReactQuill, { Quill } from 'react-quill'
+import { useDebouncedCallback } from 'use-debounce/lib'
import UserList from '~/components/Dropdown/UserList'
-import { Query } from '~/components/GQL'
import {
SearchUsers,
SearchUsers_search_edges_node_User
} from '~/components/GQL/queries/__generated__/SearchUsers'
import SEARCH_USERS from '~/components/GQL/queries/searchUsers'
-import { LanguageConsumer } from '~/components/Language'
+import { LanguageContext } from '~/components/Language'
import { Spinner } from '~/components/Spinner'
-import { TEXT } from '~/common/enums'
+import { INPUT_DEBOUNCE, TEXT } from '~/common/enums'
import contentStyles from '~/common/styles/utils/content.comment.css'
import bubbleStyles from '~/common/styles/vendors/quill.bubble.css'
import { translate } from '~/common/utils'
@@ -29,6 +28,10 @@ interface Props {
handleChange: (props: any) => any
handleBlur?: (props: any) => any
lang: Language
+ searchUsers: {
+ query: (options?: QueryLazyOptions> | undefined) => void
+ result: QueryResult>
+ }
}
interface State {
@@ -93,7 +96,17 @@ class CommentEditor extends React.Component {
}
onMentionChange = (search: string) => {
- this.setState({ search })
+ const { searchUsers } = this.props
+ const prevSearch = this.state.search
+
+ this.setState({ search }, () => {
+ // toggle search users for mention
+ if (prevSearch !== search) {
+ searchUsers.query({
+ variables: { search }
+ })
+ }
+ })
}
onMentionModuleInit = (instance: any) => {
@@ -101,8 +114,8 @@ class CommentEditor extends React.Component {
}
render() {
- const { focus, search, mentionInstance } = this.state
- const { content, expand, handleChange, lang } = this.props
+ const { content, expand, handleChange, lang, searchUsers } = this.props
+ const { focus, mentionInstance } = this.state
const containerClasses = classNames({
container: true,
focus
@@ -113,6 +126,11 @@ class CommentEditor extends React.Component {
lang
})
+ const { data, loading } = searchUsers.result
+ const users = ((data && data.search.edges) || []).map(
+ ({ node }) => node
+ ) as SearchUsers_search_edges_node_User[]
+
if (!expand) {
return (
<>
@@ -121,81 +139,82 @@ class CommentEditor extends React.Component {
placeholder={placeholder}
aria-label={placeholder}
/>
+
>
)
}
return (
-
- {({ data, loading }: QueryResult & { data: SearchUsers }) => {
- const users = _get(data, 'search.edges', []).map(
- ({ node }: { node: SearchUsers_search_edges_node_User }) => node
- )
-
- return (
- <>
-
-
-
-
-
- >
- )
- }}
-
+
)
}
}
-export default (props: Omit) => (
-
- {({ lang }) => }
-
-)
+const CommentEditorWrap = (props: Omit) => {
+ const { lang } = useContext(LanguageContext)
+ const [search, result] = useLazyQuery(SEARCH_USERS)
+ const [debouncedSearch] = useDebouncedCallback(search, INPUT_DEBOUNCE)
+
+ return (
+
+ )
+}
+
+export default CommentEditorWrap
diff --git a/src/components/Editor/SideToolbar/UploadAudioButton.tsx b/src/components/Editor/SideToolbar/UploadAudioButton.tsx
index 79537d397a..dde0b21cd7 100644
--- a/src/components/Editor/SideToolbar/UploadAudioButton.tsx
+++ b/src/components/Editor/SideToolbar/UploadAudioButton.tsx
@@ -129,12 +129,14 @@ const UploadAudioButton = ({
aria-label="新增音頻"
onChange={(event: any) => handleUploadChange(event)}
/>
+
+
diff --git a/src/components/Editor/SideToolbar/UploadImageButton.tsx b/src/components/Editor/SideToolbar/UploadImageButton.tsx
index d782acc994..c8afe14997 100644
--- a/src/components/Editor/SideToolbar/UploadImageButton.tsx
+++ b/src/components/Editor/SideToolbar/UploadImageButton.tsx
@@ -114,12 +114,14 @@ const UploadImageButton = ({
aria-label="新增圖片"
onChange={(event: any) => handleUploadChange(event)}
/>
+
+
)
diff --git a/src/components/Editor/SideToolbar/index.tsx b/src/components/Editor/SideToolbar/index.tsx
index bf9f8c8f17..7f23a2751d 100644
--- a/src/components/Editor/SideToolbar/index.tsx
+++ b/src/components/Editor/SideToolbar/index.tsx
@@ -69,6 +69,7 @@ const SideToolbar = ({
upload={upload}
/>
+
)
diff --git a/src/components/Editor/index.tsx b/src/components/Editor/index.tsx
index db77f77bb9..d931d290aa 100644
--- a/src/components/Editor/index.tsx
+++ b/src/components/Editor/index.tsx
@@ -1,21 +1,22 @@
+import { QueryLazyOptions } from '@apollo/react-hooks'
import classNames from 'classnames'
import _debounce from 'lodash/debounce'
-import _get from 'lodash/get'
import _includes from 'lodash/includes'
-import React from 'react'
-import { QueryResult } from 'react-apollo'
+import React, { useContext } from 'react'
+import { QueryResult, useLazyQuery } from 'react-apollo'
import ReactQuill, { Quill } from 'react-quill'
+import { useDebouncedCallback } from 'use-debounce/lib'
import UserList from '~/components/Dropdown/UserList'
-import { Query } from '~/components/GQL'
import {
SearchUsers,
SearchUsers_search_edges_node_User
} from '~/components/GQL/queries/__generated__/SearchUsers'
import SEARCH_USERS from '~/components/GQL/queries/searchUsers'
-import { LanguageConsumer } from '~/components/Language'
+import { LanguageContext } from '~/components/Language'
import { Spinner } from '~/components/Spinner'
+import { INPUT_DEBOUNCE } from '~/common/enums'
import contentStyles from '~/common/styles/utils/content.article.css'
import bubbleStyles from '~/common/styles/vendors/quill.bubble.css'
import { initAudioPlayers, translate, trimLineBreaks } from '~/common/utils'
@@ -35,6 +36,10 @@ interface Props {
draft: EditorDraft
lang: Language
upload: DraftAssetUpload
+ searchUsers: {
+ query: (options?: QueryLazyOptions> | undefined) => void
+ result: QueryResult>
+ }
}
interface State {
@@ -65,7 +70,7 @@ class Editor extends React.Component {
mentionInstance: null
}
- this.saveDraft = _debounce(this.saveDraft.bind(this), 300)
+ this.saveDraft = _debounce(this.saveDraft.bind(this), INPUT_DEBOUNCE)
}
componentDidMount() {
@@ -74,7 +79,7 @@ class Editor extends React.Component {
initAudioPlayers()
}
- componentDidUpdate(prevProps: Props) {
+ componentDidUpdate(prevProps: Props, prevState: State) {
this.attachQuillRefs()
this.resetLinkInputPlaceholder()
initAudioPlayers()
@@ -179,7 +184,17 @@ class Editor extends React.Component {
}
onMentionChange = (search: string) => {
- this.setState({ search })
+ const { searchUsers } = this.props
+ const prevSearch = this.state.search
+
+ this.setState({ search }, () => {
+ // toggle search users for mention
+ if (prevSearch !== search) {
+ searchUsers.query({
+ variables: { search }
+ })
+ }
+ })
}
onMentionModuleInit = (instance: any) => {
@@ -194,8 +209,8 @@ class Editor extends React.Component {
}
render() {
- const { draft, onSave, lang, upload } = this.props
- const { search, mentionInstance } = this.state
+ const { draft, onSave, lang, upload, searchUsers } = this.props
+ const { mentionInstance } = this.state
const isPending = draft.publishState === 'pending'
const isPublished = draft.publishState === 'published'
const containerClasses = classNames({
@@ -203,94 +218,99 @@ class Editor extends React.Component {
'u-area-disable': isPending || isPublished
})
+ const { data, loading } = searchUsers.result
+ const users = ((data && data.search.edges) || []).map(
+ ({ node }) => node
+ ) as SearchUsers_search_edges_node_User[]
+
if (this.quill) {
this.quill.clipboard.addMatcher('IMG', createImageMatcher(upload))
}
return (
-
- {({ data, loading }: QueryResult & { data: SearchUsers }) => {
- const users = _get(data, 'search.edges', []).map(
- ({ node }: { node: SearchUsers_search_edges_node_User }) => node
- )
-
- return (
- <>
-
-
-
-
-
- {loading && }
- {!loading && (
- {
- mentionInstance.insertMention({
- id: user.id,
- displayName: user.displayName,
- userName: user.userName
- })
- }}
- />
- )}
-
-
-
-
-
-
- >
- )
- }}
-
+
+
+
+
+
+ {loading && }
+ {!loading && (
+ {
+ mentionInstance.insertMention({
+ id: user.id,
+ displayName: user.displayName,
+ userName: user.userName
+ })
+ }}
+ />
+ )}
+
+
+
+
+
+
)
}
}
-export default (props: Omit) => (
-
- {({ lang }) => }
-
-)
+const EditorWrap = (props: Omit) => {
+ const { lang } = useContext(LanguageContext)
+ const [search, result] = useLazyQuery(SEARCH_USERS)
+ const [debouncedSearch] = useDebouncedCallback(search, INPUT_DEBOUNCE)
+
+ return (
+
+ )
+}
+
+export default EditorWrap
diff --git a/src/components/Empty/EmptyFollowee.tsx b/src/components/Empty/EmptyFollowee.tsx
deleted file mode 100644
index 14c66132a8..0000000000
--- a/src/components/Empty/EmptyFollowee.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { Empty, Icon, Translate } from '~/components'
-
-import ICON_EMPTY_WARNING from '~/static/icons/empty-warning.svg?sprite'
-
-const EmptyFollowee = ({ description }: { description?: React.ReactNode }) => (
-
- }
- description={
- description || (
-
- )
- }
- />
-)
-
-export default EmptyFollowee
diff --git a/src/components/Empty/EmptyFollower.tsx b/src/components/Empty/EmptyFollower.tsx
deleted file mode 100644
index 52597e7e5c..0000000000
--- a/src/components/Empty/EmptyFollower.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { Empty, Icon, Translate } from '~/components'
-
-import ICON_EMPTY_WARNING from '~/static/icons/empty-warning.svg?sprite'
-
-const EmptyFollower = ({ description }: { description?: React.ReactNode }) => (
-
- }
- description={
- description ||
- }
- />
-)
-
-export default EmptyFollower
diff --git a/src/components/Empty/EmptyWarning.tsx b/src/components/Empty/EmptyWarning.tsx
new file mode 100644
index 0000000000..bddfcdbb6a
--- /dev/null
+++ b/src/components/Empty/EmptyWarning.tsx
@@ -0,0 +1,18 @@
+import { Empty, Icon } from '~/components'
+
+import ICON_EMPTY_WARNING from '~/static/icons/empty-warning.svg?sprite'
+
+const EmptyWarning = ({ description }: { description: React.ReactNode }) => (
+
+ }
+ description={description}
+ />
+)
+
+export default EmptyWarning
diff --git a/src/components/Empty/index.tsx b/src/components/Empty/index.tsx
index cc6b8d26ee..85285da997 100644
--- a/src/components/Empty/index.tsx
+++ b/src/components/Empty/index.tsx
@@ -34,6 +34,7 @@ export const Empty: React.FC = ({
{icon && }
{description && }
{children && }
+
)
diff --git a/src/components/Error/index.tsx b/src/components/Error/index.tsx
index ae3c245be7..d41130c09a 100644
--- a/src/components/Error/index.tsx
+++ b/src/components/Error/index.tsx
@@ -1,3 +1,4 @@
+import classNames from 'classnames'
import getConfig from 'next/config'
import { Translate } from '~/components'
@@ -44,12 +45,19 @@ export const Error: React.FC = ({
error,
type = 'network'
}) => {
+ const errorCodeClass = classNames({
+ 'error-code': true,
+ small: typeof statusCode === 'string' && statusCode.length > 3
+ })
+
return (
- {statusCode && {statusCode}
}
+
+ {statusCode && {statusCode}
}
+
{type === 'not_found' ? (
@@ -59,7 +67,9 @@ export const Error: React.FC = ({
)}
+
{children && }
+
{error && !isProd && (
= ({
}}
/>
)}
+
)
diff --git a/src/components/Error/styles.css b/src/components/Error/styles.css
index 61fc94a85b..1d517a08fb 100644
--- a/src/components/Error/styles.css
+++ b/src/components/Error/styles.css
@@ -14,6 +14,10 @@
font-size: 3rem;
font-weight: var(--font-weight-normal);
line-height: 1.16666667;
+
+ &.small {
+ font-size: var(--font-size-xl);
+ }
}
.error-message {
diff --git a/src/components/FileUploader/ProfileAvatar/index.tsx b/src/components/FileUploader/ProfileAvatar/index.tsx
index 2175f92b9e..8b10f80a88 100644
--- a/src/components/FileUploader/ProfileAvatar/index.tsx
+++ b/src/components/FileUploader/ProfileAvatar/index.tsx
@@ -1,8 +1,9 @@
import gql from 'graphql-tag'
-import { FC, useState } from 'react'
+import { useState } from 'react'
import { Avatar } from '~/components/Avatar'
-import { Mutation } from '~/components/GQL'
+import { useMutation } from '~/components/GQL'
+import { SingleFileUpload } from '~/components/GQL/mutations/__generated__/SingleFileUpload'
import UPLOAD_FILE from '~/components/GQL/mutations/uploadFile'
import { Icon } from '~/components/Icon'
import { Translate } from '~/components/Language'
@@ -13,6 +14,7 @@ import {
} from '~/common/enums'
import ICON_CAMERA from '~/static/icons/camera-white.svg?sprite'
+import { UpdateUserInfoAvatar } from './__generated__/UpdateUserInfoAvatar'
import styles from './styles.css'
/**
@@ -38,12 +40,13 @@ const UPDATE_USER_INFO = gql`
}
`
-export const ProfileAvatarUploader: FC = ({ user }) => {
- const acceptTypes = ACCEPTED_UPLOAD_IMAGE_TYPES.join(',')
-
+export const ProfileAvatarUploader: React.FC = ({ user }) => {
+ const [update] = useMutation(UPDATE_USER_INFO)
+ const [upload] = useMutation(UPLOAD_FILE)
const [error, setError] = useState<'size' | undefined>(undefined)
+ const acceptTypes = ACCEPTED_UPLOAD_IMAGE_TYPES.join(',')
- const handleChange = (event: any, upload: any, update: any) => {
+ const handleChange = (event: React.ChangeEvent) => {
event.stopPropagation()
if (!upload || !event.target || !event.target.files) {
@@ -63,70 +66,54 @@ export const ProfileAvatarUploader: FC = ({ user }) => {
input: { file, type: 'avatar', entityType: 'user' }
}
})
- .then(({ data }: any) => {
- const {
- singleFileUpload: { id }
- } = data
+ .then(({ data }) => {
+ const id = data && data.singleFileUpload.id
if (update) {
return update({ variables: { input: { avatar: id } } })
}
})
- .then((result: any) => {
+ .then(() => {
setError(undefined)
})
- .catch((result: any) => {
+ .catch(() => {
// TODO: Handler error
})
}
- const Uploader = ({
- upload,
- update
- }: {
- upload: () => {}
- update: () => {}
- }) => (
- <>
-
-
-
-
-
-
-
-
-
-
handleChange(event, upload, update)}
- />
-
- {error === 'size' && (
-
- )}
-
+ return (
+
+
+
+
+
+
+
+
+
-
-
- >
- )
- return (
-
- {(update: any) => (
-
- {(upload: any) => }
-
- )}
-
+
handleChange(event)}
+ />
+
+
+ {error === 'size' && (
+
+ )}
+
+
+
+
+
)
}
diff --git a/src/components/FileUploader/ProfileCover/index.tsx b/src/components/FileUploader/ProfileCover/index.tsx
index c2f0d48213..26c17a04c5 100644
--- a/src/components/FileUploader/ProfileCover/index.tsx
+++ b/src/components/FileUploader/ProfileCover/index.tsx
@@ -1,7 +1,8 @@
import gql from 'graphql-tag'
-import { FC, useState } from 'react'
+import { useState } from 'react'
-import { Mutation } from '~/components/GQL'
+import { useMutation } from '~/components/GQL'
+import { SingleFileUpload } from '~/components/GQL/mutations/__generated__/SingleFileUpload'
import UPLOAD_FILE from '~/components/GQL/mutations/uploadFile'
import { Icon } from '~/components/Icon'
import { Translate } from '~/components/Language'
@@ -13,6 +14,7 @@ import {
} from '~/common/enums'
import ICON_CAMERA from '~/static/icons/camera-white.svg?sprite'
+import { UpdateUserInfoCover } from './__generated__/UpdateUserInfoCover'
import styles from './styles.css'
/**
@@ -40,12 +42,13 @@ const UPDATE_USER_INFO = gql`
}
`
-export const ProfileCoverUploader: FC
= ({ user }) => {
- const acceptTypes = ACCEPTED_UPLOAD_IMAGE_TYPES.join(',')
-
+export const ProfileCoverUploader: React.FC = ({ user }) => {
+ const [update] = useMutation(UPDATE_USER_INFO)
+ const [upload] = useMutation(UPLOAD_FILE)
const [error, setError] = useState<'size' | undefined>(undefined)
+ const acceptTypes = ACCEPTED_UPLOAD_IMAGE_TYPES.join(',')
- const handleChange = (event: any, upload: any, update: any) => {
+ const handleChange = (event: React.ChangeEvent) => {
event.stopPropagation()
if (!upload || !event.target || !event.target.files) {
@@ -65,24 +68,24 @@ export const ProfileCoverUploader: FC = ({ user }) => {
input: { file, type: 'profileCover', entityType: 'user' }
}
})
- .then(({ data }: any) => {
- const {
- singleFileUpload: { id }
- } = data
+ .then(({ data }) => {
+ const id = data && data.singleFileUpload.id
if (update) {
return update({ variables: { input: { profileCover: id } } })
}
})
- .then((result: any) => {
+ .then(result => {
setError(undefined)
})
- .catch((result: any) => {
+ .catch(result => {
// TODO: error handler
})
}
- const removeCover = (event: any, update: any) => {
+ const removeCover = (
+ event: React.MouseEvent
+ ) => {
event.stopPropagation()
if (!update || !user.info.profileCover) {
@@ -98,66 +101,49 @@ export const ProfileCoverUploader: FC = ({ user }) => {
})
}
- const Uploader = ({
- upload,
- update
- }: {
- upload: () => {}
- update: () => {}
- }) => (
- <>
-
-
-
-
-
-
+ return (
+
+
+
+
+
+
+
+ {user.info.profileCover && (
+
removeCover(event)}
+ >
+
- {user.info.profileCover && (
-
removeCover(event, update)}
- >
-
-
+ )}
+
+ {error === 'size' && (
+
)}
-
- {error === 'size' && (
-
- )}
-
-
- >
- )
- return (
-
- {(update: any) => (
-
- {(upload: any) => }
-
- )}
-
+
+
)
}
diff --git a/src/components/FileUploader/SignUpAvatar/index.tsx b/src/components/FileUploader/SignUpAvatar/index.tsx
index d53fe6530e..2ec5def006 100644
--- a/src/components/FileUploader/SignUpAvatar/index.tsx
+++ b/src/components/FileUploader/SignUpAvatar/index.tsx
@@ -1,7 +1,8 @@
-import { FC, useState } from 'react'
+import { useState } from 'react'
import { Avatar } from '~/components/Avatar'
-import { Mutation } from '~/components/GQL'
+import { useMutation } from '~/components/GQL'
+import { SingleFileUpload } from '~/components/GQL/mutations/__generated__/SingleFileUpload'
import UPLOAD_FILE from '~/components/GQL/mutations/uploadFile'
import { Icon } from '~/components/Icon'
@@ -31,39 +32,36 @@ import styles from './styles.css'
interface Props {
field: string
lang: Language
- uploadCallback: (field: string, value: any, validate?: boolean) => {}
+ uploadCallback: (field: string, value: any) => void
}
-export const SignUpAvatarUploader: FC = ({
+export const SignUpAvatarUploader: React.FC = ({
field,
lang,
uploadCallback
}) => {
- const [avatar, setAvatar] = useState(undefined)
-
- const [error, setError] = useState<'size' | undefined>(undefined)
+ const [upload] = useMutation(UPLOAD_FILE)
+ const [avatar, setAvatar] = useState()
+ const [error, setError] = useState<'size'>()
const avatarText = translate({
zh_hant: '選擇圖片',
zh_hans: '选择图片',
lang
})
-
const avatarHint = translate({
zh_hant: '上傳圖片作為大頭照 (5 MB 內)',
zh_hans: '上传图片作为头像 (5 MB 內)',
lang
})
-
const sizeError = translate({
zh_hant: '上傳檔案超過 5 MB',
zh_hans: '上传文件超过 5 MB',
lang
})
-
const acceptTypes = ACCEPTED_UPLOAD_IMAGE_TYPES.join(',')
- const handleChange = (event: any, upload: any) => {
+ const handleChange = (event: React.ChangeEvent) => {
event.stopPropagation()
if (!upload || !event.target || !event.target.files) {
@@ -81,15 +79,15 @@ export const SignUpAvatarUploader: FC = ({
upload({
variables: { input: { file, type: 'avatar', entityType: 'user' } }
})
- .then(({ data }: any) => {
- const {
- singleFileUpload: { id, path }
- } = data
+ .then(({ data }) => {
+ const id = data && data.singleFileUpload.id
+ const path = data && data.singleFileUpload.path
+
setAvatar(path)
setError(undefined)
if (uploadCallback) {
- uploadCallback(field, id, false)
+ uploadCallback(field, id)
}
})
.catch((result: any) => {
@@ -97,40 +95,33 @@ export const SignUpAvatarUploader: FC = ({
})
}
- const Uploader = ({ upload }: any) => (
- <>
-
-
-
-
-
- {avatarText}
- handleChange(event, upload)}
- />
-
-
{avatarHint}
-
{error === 'size' && sizeError}
+ return (
+
+
+
+
+
+ {avatarText}
+ handleChange(event)}
+ />
-
-
- >
- )
+
{avatarHint}
+
{error === 'size' && sizeError}
+
- return (
-
- {(upload: any) => }
-
+
+
)
}
diff --git a/src/components/Fingerprint/index.tsx b/src/components/Fingerprint/index.tsx
index c42caf94b1..6ff4373153 100644
--- a/src/components/Fingerprint/index.tsx
+++ b/src/components/Fingerprint/index.tsx
@@ -1,9 +1,7 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
import { useState } from 'react'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
-import { Query } from '~/components/GQL'
import { Icon } from '~/components/Icon'
import { Translate } from '~/components/Language'
import { Popover } from '~/components/Popper'
@@ -20,6 +18,7 @@ import ICON_HELP from '~/static/icons/help.svg?sprite'
import ICON_SHARE_LINK from '~/static/icons/share-link.svg?sprite'
import { FingerprintArticle } from './__generated__/FingerprintArticle'
+import { Gateways } from './__generated__/Gateways'
import styles from './styles.css'
const GATEWAYS = gql`
@@ -30,16 +29,13 @@ const GATEWAYS = gql`
}
`
-const FingerprintContent = ({
- shown,
- dataHash
-}: {
- shown: boolean
- dataHash: string
-}) => {
+const FingerprintContent = ({ dataHash }: { dataHash: string }) => {
const [gatewaysExpand, setGatewaysExpand] = useState(false)
const [helpExpand, setHelpExpand] = useState(false)
+ const { loading, data } = useQuery
(GATEWAYS)
+ const gateways = (data && data.official.gatewayUrls) || []
+
return (
@@ -129,44 +125,32 @@ const FingerprintContent = ({
-
- {({ data, loading, error }: QueryResult & { data: any }) => {
- if (loading) {
- return
- }
-
- const gateways: string[] = _get(data, 'official.gatewayUrls', [])
+ {loading && }
+
+ {gateways.slice(0, gatewaysExpand ? undefined : 2).map((url, i) => {
+ const gatewayUrl = `${url}${dataHash}`
return (
-
- {gateways
- .slice(0, gatewaysExpand ? undefined : 2)
- .map((url, i) => {
- const gatewayUrl = `${url}${dataHash}`
- return (
- -
-
-
- {gatewayUrl}
-
-
-
-
-
- )
- })}
-
+ -
+
+
+ {gatewayUrl}
+
+
+
+
+
)
- }}
-
+ })}
+
{/* help */}
@@ -197,6 +181,7 @@ const FingerprintContent = ({
)}
+
)
@@ -211,16 +196,11 @@ const Fingerprint = ({
color?: 'grey' | 'green'
size?: 'xs' | 'sm'
}) => {
- const [shown, setShown] = useState(false)
-
return (
setShown(true)}
- content={
-
- }
+ content={}
>
+
diff --git a/src/components/Follow/AuthorPicker/index.tsx b/src/components/Follow/AuthorPicker/index.tsx
index 68ce7bd47f..f84e7939ef 100644
--- a/src/components/Follow/AuthorPicker/index.tsx
+++ b/src/components/Follow/AuthorPicker/index.tsx
@@ -1,10 +1,9 @@
import classNames from 'classnames'
import gql from 'graphql-tag'
-import _get from 'lodash/get'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import { PageHeader, ShuffleButton, Spinner, Translate } from '~/components'
-import { Query } from '~/components/GQL'
+import { QueryError } from '~/components/GQL'
import FullDesc from '~/components/UserDigest/FullDesc'
import { numFormat } from '~/common/utils'
@@ -32,7 +31,7 @@ const AUTHOR_PICKER = gql`
${FullDesc.fragments.user}
`
-export const AuthorPicker = ({
+const AuthorPicker = ({
viewer,
title,
titleIs,
@@ -47,58 +46,45 @@ export const AuthorPicker = ({
'small-size-header': titleIs === 'span'
})
+ const { loading, data, error, refetch } = useQuery(
+ AUTHOR_PICKER,
+ {
+ notifyOnNetworkStatusChange: true
+ }
+ )
+ const edges =
+ (data && data.viewer && data.viewer.recommendation.authors.edges) || []
+ const followeeCount = viewer.followees.totalCount || 0
+
return (
-
- {({
- data,
- loading,
- error,
- refetch
- }: QueryResult & { data: AuthorPickerType }) => {
- const edges = _get(data, 'viewer.recommendation.authors.edges', [])
- const followeeCount = _get(viewer, 'followees.totalCount', 0)
+
+
+
+ refetch()} />
+
+
+ {numFormat(followeeCount)}
+
+
+
+
- if (!edges || edges.length <= 0) {
- return null
- }
+ {loading &&
}
- return (
- <>
-
-
-
- refetch()} />
-
-
-
- {numFormat(followeeCount)}
-
-
-
-
-
+ {error &&
}
- {loading &&
}
+ {!loading && (
+
+ {edges.map(({ node, cursor }) => (
+ -
+
+
+ ))}
+
+ )}
- {!loading && (
-
- {edges.map(({ node, cursor }: { node: any; cursor: any }) => (
- -
-
-
- ))}
-
- )}
-
-
- >
- )
- }}
-
+
+
)
}
@@ -111,3 +97,5 @@ AuthorPicker.fragments = {
}
`
}
+
+export default AuthorPicker
diff --git a/src/components/Follow/index.tsx b/src/components/Follow/index.tsx
deleted file mode 100644
index 88ac24be70..0000000000
--- a/src/components/Follow/index.tsx
+++ /dev/null
@@ -1 +0,0 @@
-export * from './AuthorPicker'
diff --git a/src/components/Footer/index.tsx b/src/components/Footer/index.tsx
index 754a3b5c86..9b7202ead7 100644
--- a/src/components/Footer/index.tsx
+++ b/src/components/Footer/index.tsx
@@ -21,6 +21,7 @@ const BaseLink = ({
{text}
+
>
)
@@ -31,11 +32,6 @@ export const Footer = () => {
return (
)
diff --git a/src/components/Form/Button/SendCode/index.tsx b/src/components/Form/Button/SendCode/index.tsx
index 0f3ddcc84a..cf35a03abb 100644
--- a/src/components/Form/Button/SendCode/index.tsx
+++ b/src/components/Form/Button/SendCode/index.tsx
@@ -1,14 +1,15 @@
import gql from 'graphql-tag'
-import { FC, useState } from 'react'
+import { useState } from 'react'
import { Button } from '~/components/Button'
-import { getErrorCodes, Mutation } from '~/components/GQL'
+import { getErrorCodes, useMutation } from '~/components/GQL'
import { useCountdown } from '~/components/Hook'
import { Translate } from '~/components/Language'
import { ADD_TOAST, TEXT } from '~/common/enums'
import { translate } from '~/common/utils'
+import { SendVerificationCode } from './__generated__/SendVerificationCode'
import styles from './styles.css'
/**
@@ -41,28 +42,28 @@ export const SEND_CODE = gql`
}
`
-const SendCodeButton: FC = ({ email, lang, type }) => {
+const SendCodeButton: React.FC = ({ email, lang, type }) => {
+ const [send] = useMutation(SEND_CODE)
const [sent, setSent] = useState(false)
const { countdown, setCountdown, formattedTimeLeft } = useCountdown({
timeLeft: 0
})
- const sendCode = (params: any) => {
- const { event, send } = params
+ const sendCode = (event: any) => {
event.stopPropagation()
- if (!send || !params.email || countdown.timeLeft !== 0) {
+ if (!send || !email || countdown.timeLeft !== 0) {
return
}
send({
- variables: { input: { email: params.email, type } }
+ variables: { input: { email, type } }
})
- .then((result: any) => {
+ .then(result => {
setCountdown({ timeLeft: 1000 * 60 })
setSent(true)
})
- .catch((error: any) => {
+ .catch(error => {
const errorCode = getErrorCodes(error)[0]
const errorMessage = (
= ({ email, lang, type }) => {
}
return (
- <>
-
- {(send: any) => (
- sendCode({ event, email, send })}
- >
- {sent
- ? translate({
- zh_hant: TEXT.zh_hant.resend,
- zh_hans: TEXT.zh_hans.resend,
- lang
- })
- : translate({
- zh_hant: TEXT.zh_hant.sendVerificationCode,
- zh_hans: TEXT.zh_hans.sendVerificationCode,
- lang
- })}
- {sent && countdown.timeLeft !== 0 && (
- {formattedTimeLeft.ss}
- )}
-
- )}
-
+ sendCode({ event })}
+ >
+ {sent
+ ? translate({
+ zh_hant: TEXT.zh_hant.resend,
+ zh_hans: TEXT.zh_hans.resend,
+ lang
+ })
+ : translate({
+ zh_hant: TEXT.zh_hant.sendVerificationCode,
+ zh_hans: TEXT.zh_hans.sendVerificationCode,
+ lang
+ })}
+ {sent && countdown.timeLeft !== 0 && (
+ {formattedTimeLeft.ss}
+ )}
+
- >
+
)
}
diff --git a/src/components/Form/CheckBox/index.tsx b/src/components/Form/CheckBox/index.tsx
index c7631b1659..1604b4be36 100644
--- a/src/components/Form/CheckBox/index.tsx
+++ b/src/components/Form/CheckBox/index.tsx
@@ -1,5 +1,4 @@
import classNames from 'classnames'
-import { FC } from 'react'
import { Icon } from '~/components/Icon'
@@ -17,13 +16,13 @@ interface Props {
values: any
errors: any
handleBlur?: () => {}
- handleChange: () => {}
- setFieldValue: (field: string, value: any, validate?: boolean) => {}
+ handleChange(e: React.ChangeEvent): void
+ setFieldValue(field: string, value: any): void
[key: string]: any
}
-const CheckBox: FC = ({
+const CheckBox: React.FC = ({
children,
className = [],
field,
@@ -63,6 +62,7 @@ const CheckBox: FC = ({
{children}
+
>
)
diff --git a/src/components/Form/CommentForm/index.tsx b/src/components/Form/CommentForm/index.tsx
index d229d46111..0b316bc748 100644
--- a/src/components/Form/CommentForm/index.tsx
+++ b/src/components/Form/CommentForm/index.tsx
@@ -1,14 +1,15 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
import dynamic from 'next/dynamic'
import { useContext, useState } from 'react'
+import { useQuery } from 'react-apollo'
import { Button } from '~/components/Button'
-import { Mutation, Query } from '~/components/GQL'
+import { useMutation } from '~/components/GQL'
import COMMENT_COMMENTS from '~/components/GQL/queries/commentComments'
import { Icon } from '~/components/Icon'
import IconSpinner from '~/components/Icon/Spinner'
import { Translate } from '~/components/Language'
+import { ModalSwitch } from '~/components/ModalManager'
import { Spinner } from '~/components/Spinner'
import { ViewerContext } from '~/components/Viewer'
@@ -16,6 +17,8 @@ import { ADD_TOAST } from '~/common/enums'
import { dom, trimLineBreaks } from '~/common/utils'
import ICON_POST from '~/static/icons/post.svg?sprite'
+import { CommentDraft } from './__generated__/CommentDraft'
+import { PutComment } from './__generated__/PutComment'
import styles from './styles.css'
const CommentEditor = dynamic(
@@ -45,7 +48,6 @@ const COMMENT_DRAFT = gql`
`
interface CommentFormProps {
- defaultContent?: string | null
articleId: string
commentId?: string
replyToId?: string
@@ -53,20 +55,23 @@ interface CommentFormProps {
submitCallback?: () => void
refetch?: boolean
extraButton?: React.ReactNode
+ blocked?: boolean
defaultExpand?: boolean
+ defaultContent?: string | null
}
// TODO: remove refetchQueries, use refetch in submitCallback instead
const CommentForm = ({
- defaultContent,
commentId,
parentId,
replyToId,
articleId,
submitCallback,
refetch,
+ blocked,
extraButton,
- defaultExpand
+ defaultExpand,
+ defaultContent
}: CommentFormProps) => {
const commentDraftId = `${articleId}:${commentId || '0'}:${parentId ||
'0'}:${replyToId || '0'}`
@@ -81,136 +86,147 @@ const CommentForm = ({
]
: []
- return (
-
- {({ data: commentDraftData, client }: any) => {
- const draftContent = _get(commentDraftData, 'commentDraft.content', '')
-
- return (
-
- {(putComment: any) => {
- const [isSubmitting, setSubmitting] = useState(false)
- const [expand, setExpand] = useState(defaultExpand || false)
- const [content, setContent] = useState(
- draftContent || defaultContent || ''
- )
- const viewer = useContext(ViewerContext)
- const isValid = !!trimLineBreaks(content)
-
- const handleSubmit = (
- event: React.FormEvent
- ) => {
- const mentions = dom.getAttributes('data-id', content)
- const input = {
- id: commentId,
- comment: {
- content: trimLineBreaks(content),
- replyTo: replyToId,
- articleId,
- parentId,
- mentions
- }
- }
-
- event.preventDefault()
- setSubmitting(true)
-
- putComment({ variables: { input } })
- .then(({ data }: any) => {
- if (submitCallback) {
- submitCallback()
- }
- setContent('')
- window.dispatchEvent(
- new CustomEvent(ADD_TOAST, {
- detail: {
- color: 'green',
- content: (
-
- )
- }
- })
- )
- })
- .catch((result: any) => {
- window.dispatchEvent(
- new CustomEvent(ADD_TOAST, {
- detail: {
- color: 'red',
- content: (
-
- )
- }
- })
- )
- })
- .finally(() => {
- setSubmitting(false)
- })
- }
-
- return (
-
+ const { data, client } = useQuery(COMMENT_DRAFT, {
+ variables: {
+ id: commentDraftId
+ }
+ })
+ const [putComment] = useMutation(PUT_COMMENT, {
+ refetchQueries
+ })
+ const draftContent = (data && data.commentDraft.content) || ''
+
+ const [isSubmitting, setSubmitting] = useState(false)
+ const [expand, setExpand] = useState(defaultExpand || false)
+ const [content, setContent] = useState(draftContent || defaultContent || '')
+ const viewer = useContext(ViewerContext)
+ const isValid = !!trimLineBreaks(content)
+ const handleSubmit = (event: React.FormEvent) => {
+ const mentions = dom.getAttributes('data-id', content)
+ const input = {
+ id: commentId,
+ comment: {
+ content: trimLineBreaks(content),
+ replyTo: replyToId,
+ articleId,
+ parentId,
+ mentions
+ }
+ }
+
+ event.preventDefault()
+ setSubmitting(true)
+
+ putComment({ variables: { input } })
+ .then(() => {
+ if (submitCallback) {
+ submitCallback()
+ }
+ setContent('')
+ window.dispatchEvent(
+ new CustomEvent(ADD_TOAST, {
+ detail: {
+ color: 'green',
+ content:
+ }
+ })
+ )
+ })
+ .catch((result: any) => {
+ window.dispatchEvent(
+ new CustomEvent(ADD_TOAST, {
+ detail: {
+ color: 'red',
+ content: (
+
)
- }}
-
+ }
+ })
)
+ })
+ .finally(() => {
+ setSubmitting(false)
+ })
+ }
+
+ return (
+
+ >
+
setContent(value)}
+ />
+
+ {extraButton && extraButton}
+
+ ) : (
+
+ )
+ }
+ >
+
+
+
+
+
+
)
}
-export default CommentForm
+const CommentFormWrap = (props: CommentFormProps) => {
+ const viewer = useContext(ViewerContext)
+
+ if (viewer.shouldSetupLikerID) {
+ return (
+
+ {(open: any) => (
+
+
+
+
+
+ )}
+
+ )
+ }
+
+ if (props.blocked) {
+ return (
+
+ )
+ }
+
+ return
+}
+
+export default CommentFormWrap
diff --git a/src/components/Form/CommentForm/styles.css b/src/components/Form/CommentForm/styles.css
index 49901e871b..47ccb77285 100644
--- a/src/components/Form/CommentForm/styles.css
+++ b/src/components/Form/CommentForm/styles.css
@@ -13,3 +13,14 @@
display: block;
}
}
+
+.blocked {
+ display: block;
+ width: 100%;
+ padding: var(--spacing-x-tight);
+
+ color: var(--color-grey-dark);
+ font-size: var(--font-size-sm);
+ text-align: center;
+ background: var(--color-grey-lighter);
+}
diff --git a/src/components/Form/EmailChangeForm/Confirm/index.tsx b/src/components/Form/EmailChangeForm/Confirm/index.tsx
index 09e67434aa..b53ac3ad36 100644
--- a/src/components/Form/EmailChangeForm/Confirm/index.tsx
+++ b/src/components/Form/EmailChangeForm/Confirm/index.tsx
@@ -1,12 +1,12 @@
-import classNames from 'classnames'
-import { withFormik } from 'formik'
+import { FormikProps, withFormik } from 'formik'
import gql from 'graphql-tag'
import _isEmpty from 'lodash/isEmpty'
-import { FC, useContext } from 'react'
+import { useContext } from 'react'
import { Form } from '~/components/Form'
import SendCodeButton from '~/components/Form/Button/SendCode'
-import { getErrorCodes, Mutation } from '~/components/GQL'
+import { getErrorCodes, useMutation } from '~/components/GQL'
+import { ConfirmVerificationCode } from '~/components/GQL/mutations/__generated__/ConfirmVerificationCode'
import { CONFIRM_CODE } from '~/components/GQL/mutations/verificationCode'
import { LanguageContext, Translate } from '~/components/Language'
import { Modal } from '~/components/Modal'
@@ -14,14 +14,19 @@ import { Modal } from '~/components/Modal'
import { TEXT } from '~/common/enums'
import { isValidEmail, translate } from '~/common/utils'
+import { ChangeEmail } from './__generated__/ChangeEmail'
import styles from './styles.css'
-interface Props {
+interface FormProps {
oldData: { email: string; codeId: string }
- extraClass?: string[]
submitCallback: () => void
}
+interface FormValues {
+ email: string
+ code: string
+}
+
const CHANGE_EMAIL = gql`
mutation ChangeEmail($input: ChangeEmailInput!) {
changeEmail(input: $input) {
@@ -33,15 +38,85 @@ const CHANGE_EMAIL = gql`
}
`
-export const EmailChangeConfirmForm: FC = ({
- oldData,
- extraClass = [],
- submitCallback
-}) => {
+const InnerForm = ({
+ values,
+ errors,
+ touched,
+ isSubmitting,
+ handleBlur,
+ handleChange,
+ handleSubmit,
+ setFieldError
+}: FormikProps) => {
+ const { lang } = useContext(LanguageContext)
+ const emailPlaceholder = translate({
+ zh_hant: TEXT.zh_hant.enterNewEmail,
+ zh_hans: TEXT.zh_hans.enterNewEmail,
+ lang
+ })
+ const codePlaceholder = translate({
+ zh_hant: TEXT.zh_hant.enterVerificationCode,
+ zh_hans: TEXT.zh_hans.enterVerificationCode,
+ lang
+ })
+
+ return (
+
+ )
+}
+
+export const EmailChangeConfirmForm: React.FC = formProps => {
+ const [confirmCode] = useMutation(CONFIRM_CODE)
+ const [changeEmail] = useMutation(CHANGE_EMAIL)
const { lang } = useContext(LanguageContext)
+ const { oldData, submitCallback } = formProps
+
+ const validateEmail = (value: string) => {
+ let result
- const validateEmail = (value: string, language: string) => {
- let result: any
if (!value) {
result = {
zh_hant: TEXT.zh_hant.required,
@@ -53,111 +128,36 @@ export const EmailChangeConfirmForm: FC = ({
zh_hans: TEXT.zh_hans.invalidEmail
}
}
+
if (result) {
- return translate({ ...result, lang: language })
+ return translate({ ...result, lang })
}
}
- const validateCode = (value: string, language: string) => {
- let result: any
+ const validateCode = (value: string) => {
+ let result
+
if (!value) {
result = {
zh_hant: TEXT.zh_hant.required,
zh_hans: TEXT.zh_hans.required
}
}
+
if (result) {
- return translate({ ...result, lang: language })
+ return translate({ ...result, lang })
}
}
- const BaseForm = ({
- values,
- errors,
- touched,
- isSubmitting,
- handleBlur,
- handleChange,
- handleSubmit,
- setFieldError
- }: {
- [key: string]: any
- }) => {
- const formClass = classNames('form', ...extraClass)
-
- const emailPlaceholder = translate({
- zh_hant: TEXT.zh_hant.enterNewEmail,
- zh_hans: TEXT.zh_hans.enterNewEmail,
- lang
- })
-
- const codePlaceholder = translate({
- zh_hant: TEXT.zh_hant.enterVerificationCode,
- zh_hans: TEXT.zh_hans.enterVerificationCode,
- lang
- })
-
- return (
- <>
-
-
- >
- )
- }
-
- const MainForm: any = withFormik({
+ const MainForm = withFormik({
mapPropsToValues: () => ({
email: '',
code: ''
}),
validate: ({ email, code }) => {
- const isInvalidEmail = validateEmail(email, lang)
- const isInvalidCode = validateCode(code, lang)
+ const isInvalidEmail = validateEmail(email)
+ const isInvalidCode = validateCode(code)
const errors = {
...(isInvalidEmail ? { email: isInvalidEmail } : {}),
...(isInvalidCode ? { code: isInvalidCode } : {})
@@ -165,62 +165,43 @@ export const EmailChangeConfirmForm: FC = ({
return errors
},
- handleSubmit: (values, { props, setFieldError, setSubmitting }: any) => {
+ handleSubmit: async (values, { setFieldError, setSubmitting }) => {
const { email, code } = values
- const { preSubmitAction, submitAction } = props
- if (!preSubmitAction || !submitAction) {
- return
- }
- preSubmitAction({
- variables: { input: { email, type: 'email_reset_confirm', code } }
- })
- .then(({ data }: any) => {
- const { confirmVerificationCode } = data
- const params = {
- variables: {
- input: {
- oldEmail: oldData.email,
- oldEmailCodeId: oldData.codeId,
- newEmail: email,
- newEmailCodeId: confirmVerificationCode
- }
- }
- }
- return submitAction(params)
+ try {
+ const { data } = await confirmCode({
+ variables: { input: { email, type: 'email_reset_confirm', code } }
})
- .then((result: any) => {
- if (submitCallback) {
- submitCallback()
+ const confirmVerificationCode = data && data.confirmVerificationCode
+ const params = {
+ variables: {
+ input: {
+ oldEmail: oldData.email,
+ oldEmailCodeId: oldData.codeId,
+ newEmail: email,
+ newEmailCodeId: confirmVerificationCode
+ }
}
+ }
+
+ await changeEmail(params)
+
+ if (submitCallback) {
+ submitCallback()
+ }
+ } catch (error) {
+ const errorCode = getErrorCodes(error)[0]
+ const errorMessage = translate({
+ zh_hant: TEXT.zh_hant.error[errorCode] || errorCode,
+ zh_hans: TEXT.zh_hans.error[errorCode] || errorCode,
+ lang
})
- .catch((error: any) => {
- const errorCode = getErrorCodes(error)[0]
- const errorMessage = translate({
- zh_hant: TEXT.zh_hant.error[errorCode] || errorCode,
- zh_hans: TEXT.zh_hans.error[errorCode] || errorCode,
- lang
- })
- setFieldError('code', errorMessage)
- })
- .finally(() => {
- setSubmitting(false)
- })
+ setFieldError('code', errorMessage)
+ }
+
+ setSubmitting(false)
}
- })(BaseForm)
+ })(InnerForm)
- return (
- <>
-
- {(confirm: any) => (
-
- {(update: any) => (
-
- )}
-
- )}
-
-
- >
- )
+ return
}
diff --git a/src/components/Form/EmailChangeForm/Confirm/styles.css b/src/components/Form/EmailChangeForm/Confirm/styles.css
index 9b1e84b5e2..b8ea4f7c96 100644
--- a/src/components/Form/EmailChangeForm/Confirm/styles.css
+++ b/src/components/Form/EmailChangeForm/Confirm/styles.css
@@ -1,3 +1,3 @@
-.form {
+form {
width: 100%;
}
diff --git a/src/components/Form/EmailChangeForm/Request/index.tsx b/src/components/Form/EmailChangeForm/Request/index.tsx
index c8c13ac5e6..33ea49932d 100644
--- a/src/components/Form/EmailChangeForm/Request/index.tsx
+++ b/src/components/Form/EmailChangeForm/Request/index.tsx
@@ -1,11 +1,11 @@
-import classNames from 'classnames'
-import { withFormik } from 'formik'
+import { FormikProps, withFormik } from 'formik'
import _isEmpty from 'lodash/isEmpty'
-import { FC, useContext } from 'react'
+import { useContext } from 'react'
import { Form } from '~/components/Form'
import SendCodeButton from '~/components/Form/Button/SendCode'
-import { getErrorCodes, Mutation } from '~/components/GQL'
+import { getErrorCodes, useMutation } from '~/components/GQL'
+import { ConfirmVerificationCode } from '~/components/GQL/mutations/__generated__/ConfirmVerificationCode'
import { CONFIRM_CODE } from '~/components/GQL/mutations/verificationCode'
import { LanguageContext, Translate } from '~/components/Language'
import { Modal } from '~/components/Modal'
@@ -15,21 +15,92 @@ import { isValidEmail, translate } from '~/common/utils'
import styles from './styles.css'
-interface Props {
+interface FormProps {
defaultEmail: string
- extraClass?: string[]
submitCallback?: (params: any) => void
}
-export const EmailChangeRequestForm: FC = ({
- defaultEmail = '',
- extraClass = [],
- submitCallback
-}) => {
+interface FormValues {
+ email: string
+ code: string
+}
+
+const InnerForm = ({
+ values,
+ errors,
+ touched,
+ isSubmitting,
+ handleBlur,
+ handleChange,
+ handleSubmit
+}: FormikProps) => {
+ const { lang } = useContext(LanguageContext)
+ const codePlaceholder = translate({
+ zh_hant: TEXT.zh_hant.enterVerificationCode,
+ zh_hans: TEXT.zh_hans.enterVerificationCode,
+ lang
+ })
+
+ return (
+
+ )
+}
+
+export const EmailChangeRequestForm: React.FC = formProps => {
+ const [confirmCode] = useMutation(CONFIRM_CODE)
const { lang } = useContext(LanguageContext)
+ const { defaultEmail = '', submitCallback } = formProps
+
+ const validateEmail = (value: string) => {
+ let result
- const validateEmail = (value: string, language: string) => {
- let result: any
if (!value) {
result = {
zh_hant: TEXT.zh_hant.required,
@@ -41,105 +112,35 @@ export const EmailChangeRequestForm: FC = ({
zh_hans: TEXT.zh_hans.invalidEmail
}
}
+
if (result) {
- return translate({ ...result, lang: language })
+ return translate({ ...result, lang })
}
}
+ const validateCode = (value: string) => {
+ let result
- const validateCode = (value: string, language: string) => {
- let result: any
if (!value) {
result = {
zh_hant: TEXT.zh_hant.required,
zh_hans: TEXT.zh_hans.required
}
}
+
if (result) {
- return translate({ ...result, lang: language })
+ return translate({ ...result, lang })
}
}
- const BaseForm = ({
- values,
- errors,
- touched,
- isSubmitting,
- handleBlur,
- handleChange,
- handleSubmit
- }: {
- [key: string]: any
- }) => {
- const formClass = classNames('form', ...extraClass)
-
- const codePlaceholder = translate({
- zh_hant: TEXT.zh_hant.enterVerificationCode,
- zh_hans: TEXT.zh_hans.enterVerificationCode,
- lang
- })
-
- return (
- <>
-
-
- >
- )
- }
-
- const MainForm: any = withFormik({
+ const MainForm = withFormik({
mapPropsToValues: () => ({
email: defaultEmail,
code: ''
}),
validate: ({ email, code }) => {
- const isInvalidEmail = validateEmail(email, lang)
- const isInvalidCode = validateCode(code, lang)
+ const isInvalidEmail = validateEmail(email)
+ const isInvalidCode = validateCode(code)
const errors = {
...(isInvalidEmail ? { email: isInvalidEmail } : {}),
...(isInvalidCode ? { code: isInvalidCode } : {})
@@ -147,43 +148,31 @@ export const EmailChangeRequestForm: FC = ({
return errors
},
- handleSubmit: (values, { props, setFieldError, setSubmitting }: any) => {
+ handleSubmit: async (values, { setFieldError, setSubmitting }) => {
const { email, code } = values
- const { submitAction } = props
- if (!submitAction) {
- return
- }
- submitAction({
- variables: { input: { email, type: 'email_reset', code } }
- })
- .then(({ data }: any) => {
- const { confirmVerificationCode } = data
- if (submitCallback && confirmVerificationCode) {
- submitCallback({ codeId: confirmVerificationCode })
- }
+ try {
+ const { data } = await confirmCode({
+ variables: { input: { email, type: 'email_reset', code } }
})
- .catch((error: any) => {
- const errorCode = getErrorCodes(error)[0]
- const errorMessage = translate({
- zh_hant: TEXT.zh_hant.error[errorCode] || errorCode,
- zh_hans: TEXT.zh_hans.error[errorCode] || errorCode,
- lang
- })
- setFieldError('code', errorMessage)
- })
- .finally(() => {
- setSubmitting(false)
+ const confirmVerificationCode = data && data.confirmVerificationCode
+
+ if (submitCallback && confirmVerificationCode) {
+ submitCallback({ codeId: confirmVerificationCode })
+ }
+ } catch (error) {
+ const errorCode = getErrorCodes(error)[0]
+ const errorMessage = translate({
+ zh_hant: TEXT.zh_hant.error[errorCode] || errorCode,
+ zh_hans: TEXT.zh_hans.error[errorCode] || errorCode,
+ lang
})
+ setFieldError('code', errorMessage)
+ }
+
+ setSubmitting(false)
}
- })(BaseForm)
+ })(InnerForm)
- return (
- <>
-
- {(confirmCode: any) => }
-
-
- >
- )
+ return
}
diff --git a/src/components/Form/EmailChangeForm/Request/styles.css b/src/components/Form/EmailChangeForm/Request/styles.css
index 9b1e84b5e2..b8ea4f7c96 100644
--- a/src/components/Form/EmailChangeForm/Request/styles.css
+++ b/src/components/Form/EmailChangeForm/Request/styles.css
@@ -1,3 +1,3 @@
-.form {
+form {
width: 100%;
}
diff --git a/src/components/Form/Input/index.tsx b/src/components/Form/Input/index.tsx
index 8a8c4c5adf..4f78c4f63b 100644
--- a/src/components/Form/Input/index.tsx
+++ b/src/components/Form/Input/index.tsx
@@ -1,5 +1,4 @@
import classNames from 'classnames'
-import { FC } from 'react'
import styles from './styles.css'
@@ -45,7 +44,7 @@ interface Props {
[key: string]: any
}
-const Input: FC = ({
+const Input: React.FC = ({
className = [],
type,
field,
@@ -94,6 +93,7 @@ const Input: FC = ({
{(!error || !isTouched) && hint && {hint}
}
+
>
)
diff --git a/src/components/Form/LoginForm/index.tsx b/src/components/Form/LoginForm/index.tsx
index feec63ea14..dadd74b451 100644
--- a/src/components/Form/LoginForm/index.tsx
+++ b/src/components/Form/LoginForm/index.tsx
@@ -1,12 +1,12 @@
import classNames from 'classnames'
-import { withFormik } from 'formik'
+import { FormikProps, withFormik } from 'formik'
import gql from 'graphql-tag'
import _isEmpty from 'lodash/isEmpty'
-import { FC, useContext } from 'react'
+import { useContext } from 'react'
import { Button } from '~/components/Button'
import { Form } from '~/components/Form'
-import { getErrorCodes, Mutation } from '~/components/GQL'
+import { getErrorCodes, useMutation } from '~/components/GQL'
import { LanguageContext, Translate } from '~/components/Language'
import { Modal } from '~/components/Modal'
import { ModalSwitch } from '~/components/ModalManager'
@@ -27,6 +27,7 @@ import {
translate
} from '~/common/utils'
+import { UserLogin } from './__generated__/UserLogin'
import styles from './styles.css'
/**
@@ -43,13 +44,18 @@ import styles from './styles.css'
* ```
*
*/
-interface Props {
+interface FormProps {
extraClass?: string[]
purpose: 'modal' | 'page'
submitCallback?: () => void
scrollLock?: boolean
}
+interface FormValues {
+ email: string
+ password: ''
+}
+
export const USER_LOGIN = gql`
mutation UserLogin($input: UserLoginInput!) {
userLogin(input: $input) {
@@ -73,6 +79,7 @@ const PasswordResetRedirectButton = () => (
/>
?
+
>
)
@@ -117,18 +124,15 @@ const SignUpRedirection = () => (
)
-const LoginForm: FC = ({
- extraClass = [],
- purpose,
- submitCallback,
- scrollLock
-}) => {
+const LoginForm: React.FC = formProps => {
+ const [login] = useMutation(USER_LOGIN)
const { lang } = useContext(LanguageContext)
+ const { extraClass = [], purpose, submitCallback, scrollLock } = formProps
const isInModal = purpose === 'modal'
const isInPage = purpose === 'page'
- const validateEmail = (value: string, language: string) => {
- let result: any
+ const validateEmail = (value: string) => {
+ let result
if (!value) {
result = {
@@ -142,12 +146,11 @@ const LoginForm: FC = ({
}
}
if (result) {
- return translate({ ...result, lang: language })
+ return translate({ ...result, lang })
}
}
-
- const validatePassword = (value: string, language: string) => {
- let result: any
+ const validatePassword = (value: string) => {
+ let result
if (!value) {
result = {
@@ -155,12 +158,13 @@ const LoginForm: FC = ({
zh_hans: TEXT.zh_hans.required
}
}
+
if (result) {
- return translate({ ...result, lang: language })
+ return translate({ ...result, lang })
}
}
- const BaseForm = ({
+ const InnerForm = ({
values,
errors,
touched,
@@ -168,85 +172,76 @@ const LoginForm: FC = ({
handleBlur,
handleChange,
handleSubmit
- }: {
- [key: string]: any
- }) => {
+ }: FormikProps) => {
const formClass = classNames('form', ...extraClass)
-
const emailPlaceholder = translate({
zh_hant: TEXT.zh_hant.enterEmail,
zh_hans: TEXT.zh_hans.enterEmail,
lang
})
-
const passwordPlaceholder = translate({
zh_hant: TEXT.zh_hant.enterPassword,
zh_hans: TEXT.zh_hans.enterPassword,
lang
})
- const loginText = translate({
- zh_hant: TEXT.zh_hant.login,
- zh_hans: TEXT.zh_hans.login,
- lang
- })
-
return (
- <>
-
- >
+
)
}
- const MainForm: any = withFormik({
+ const MainForm = withFormik({
mapPropsToValues: () => ({
email: '',
password: ''
}),
validate: ({ email, password }) => {
- const isInvalidEmail = validateEmail(email, lang)
- const isInvalidPassword = validatePassword(password, lang)
+ const isInvalidEmail = validateEmail(email)
+ const isInvalidPassword = validatePassword(password)
const errors = {
...(isInvalidEmail ? { email: isInvalidEmail } : {}),
...(isInvalidPassword ? { password: isInvalidPassword } : {})
@@ -254,88 +249,77 @@ const LoginForm: FC = ({
return errors
},
- handleSubmit: (values, { props, setErrors, setSubmitting }: any) => {
+ handleSubmit: async (values, { setErrors, setSubmitting }) => {
const { email, password } = values
- const { submitAction } = props
- if (!submitAction) {
- return
- }
- submitAction({ variables: { input: { email, password } } })
- .then(async (result: any) => {
- if (submitCallback) {
- submitCallback()
- }
- window.dispatchEvent(
- new CustomEvent(ADD_TOAST, {
- detail: {
- color: 'green',
- content: (
-
- )
- }
- })
- )
- analytics.identifyUser()
- analytics.trackEvent(ANALYTICS_EVENTS.LOG_IN)
- // await clearPersistCache()
- redirectToTarget({
- fallback: !!isInPage ? 'homepage' : 'current'
+ try {
+ await login({ variables: { input: { email, password } } })
+
+ if (submitCallback) {
+ submitCallback()
+ }
+
+ window.dispatchEvent(
+ new CustomEvent(ADD_TOAST, {
+ detail: {
+ color: 'green',
+ content: (
+
+ )
+ }
})
+ )
+ analytics.identifyUser()
+ analytics.trackEvent(ANALYTICS_EVENTS.LOG_IN)
+
+ // await clearPersistCache()
+
+ redirectToTarget({
+ fallback: !!isInPage ? 'homepage' : 'current'
})
- .catch((error: any) => {
- const errorCodes = getErrorCodes(error)
-
- if (errorCodes.indexOf(ERROR_CODES.USER_EMAIL_NOT_FOUND) >= 0) {
- setErrors({
- email: translate({
- zh_hant: TEXT.zh_hant.error.USER_EMAIL_NOT_FOUND,
- zh_hans: TEXT.zh_hans.error.USER_EMAIL_NOT_FOUND,
- lang
- })
+ } catch (error) {
+ const errorCodes = getErrorCodes(error)
+
+ if (errorCodes.indexOf(ERROR_CODES.USER_EMAIL_NOT_FOUND) >= 0) {
+ setErrors({
+ email: translate({
+ zh_hant: TEXT.zh_hant.error.USER_EMAIL_NOT_FOUND,
+ zh_hans: TEXT.zh_hans.error.USER_EMAIL_NOT_FOUND,
+ lang
})
- } else if (
- errorCodes.indexOf(ERROR_CODES.USER_PASSWORD_INVALID) >= 0
- ) {
- setErrors({
- password: translate({
- zh_hant: TEXT.zh_hant.error.USER_PASSWORD_INVALID,
- zh_hans: TEXT.zh_hans.error.USER_PASSWORD_INVALID,
- lang
- })
+ })
+ } else if (errorCodes.indexOf(ERROR_CODES.USER_PASSWORD_INVALID) >= 0) {
+ setErrors({
+ password: translate({
+ zh_hant: TEXT.zh_hant.error.USER_PASSWORD_INVALID,
+ zh_hans: TEXT.zh_hans.error.USER_PASSWORD_INVALID,
+ lang
})
- } else {
- setErrors({
- email: translate({
- zh_hant: TEXT.zh_hant.error.UNKNOWN_ERROR,
- zh_hans: TEXT.zh_hans.error.UNKNOWN_ERROR,
- lang
- })
+ })
+ } else {
+ setErrors({
+ email: translate({
+ zh_hant: TEXT.zh_hant.error.UNKNOWN_ERROR,
+ zh_hans: TEXT.zh_hans.error.UNKNOWN_ERROR,
+ lang
})
- }
-
- analytics.trackEvent(ANALYTICS_EVENTS.LOG_IN_FAILED, {
- email,
- error
})
+ }
+
+ analytics.trackEvent(ANALYTICS_EVENTS.LOG_IN_FAILED, {
+ email,
+ error
})
- .finally(() => {
- setSubmitting(false)
- })
+ }
+
+ setSubmitting(false)
}
- })(BaseForm)
-
- return (
- <>
-
- {(login: any) => }
-
-
- >
- )
+ })(InnerForm)
+
+ return
}
export default LoginForm
diff --git a/src/components/Form/PasswordChangeForm/Confirm/index.tsx b/src/components/Form/PasswordChangeForm/Confirm/index.tsx
index 58d2690c67..063ba49fa9 100644
--- a/src/components/Form/PasswordChangeForm/Confirm/index.tsx
+++ b/src/components/Form/PasswordChangeForm/Confirm/index.tsx
@@ -1,20 +1,21 @@
import classNames from 'classnames'
-import { withFormik } from 'formik'
+import { FormikProps, withFormik } from 'formik'
import gql from 'graphql-tag'
import _isEmpty from 'lodash/isEmpty'
-import { FC, useContext } from 'react'
+import { useContext } from 'react'
import { Form } from '~/components/Form'
-import { getErrorCodes, Mutation } from '~/components/GQL'
+import { getErrorCodes, useMutation } from '~/components/GQL'
import { LanguageContext, Translate } from '~/components/Language'
import { Modal } from '~/components/Modal'
import { TEXT } from '~/common/enums'
import { isValidPassword, translate } from '~/common/utils'
+import { ResetPassword } from './__generated__/ResetPassword'
import styles from './styles.css'
-interface Props {
+interface FormProps {
codeId: string
extraClass?: string[]
container: 'modal' | 'page'
@@ -23,24 +24,30 @@ interface Props {
scrollLock?: boolean
}
+interface FormValues {
+ password: string
+ comparedPassword: string
+}
+
export const RESET_PASSWORD = gql`
mutation ResetPassword($input: ResetPasswordInput!) {
resetPassword(input: $input)
}
`
-export const PasswordChangeConfirmForm: FC = ({
- codeId,
- extraClass = [],
- container,
- backPreviousStep,
- submitCallback,
- scrollLock
-}) => {
+export const PasswordChangeConfirmForm: React.FC = formProps => {
+ const [reset] = useMutation(RESET_PASSWORD)
const { lang } = useContext(LanguageContext)
+ const {
+ codeId,
+ extraClass = [],
+ backPreviousStep,
+ submitCallback,
+ scrollLock
+ } = formProps
- const validatePassword = (value: string, language: string) => {
- let result: any
+ const validatePassword = (value: string) => {
+ let result
if (!value) {
result = {
@@ -53,17 +60,14 @@ export const PasswordChangeConfirmForm: FC = ({
zh_hans: TEXT.zh_hans.passwordHint
}
}
+
if (result) {
- return translate({ ...result, lang: language })
+ return translate({ ...result, lang })
}
}
- const validateComparedPassword = (
- value: string,
- comparedValue: string,
- language: string
- ) => {
- let result: any
+ const validateComparedPassword = (value: string, comparedValue: string) => {
+ let result
if (!comparedValue) {
result = {
@@ -76,12 +80,13 @@ export const PasswordChangeConfirmForm: FC = ({
zh_hans: TEXT.zh_hans.passwordNotMatch
}
}
+
if (result) {
- return translate({ ...result, lang: language })
+ return translate({ ...result, lang })
}
}
- const BaseForm = ({
+ const InnerForm = ({
values,
errors,
touched,
@@ -89,23 +94,18 @@ export const PasswordChangeConfirmForm: FC = ({
handleBlur,
handleChange,
handleSubmit
- }: {
- [key: string]: any
- }) => {
+ }: FormikProps) => {
const formClass = classNames('form', ...extraClass)
-
const passwordPlaceholder = translate({
zh_hant: TEXT.zh_hant.enterPassword,
zh_hans: TEXT.zh_hans.enterPassword,
lang
})
-
const passwordHint = translate({
zh_hant: TEXT.zh_hant.passwordHint,
zh_hans: TEXT.zh_hans.passwordHint,
lang
})
-
const comparedPlaceholder = translate({
zh_hant: TEXT.zh_hant.enterPasswordAgain,
zh_hans: TEXT.zh_hans.enterPasswordAgain,
@@ -113,68 +113,66 @@ export const PasswordChangeConfirmForm: FC = ({
})
return (
- <>
-
)
}
- const MainForm: any = withFormik({
+ const MainForm = withFormik({
mapPropsToValues: () => ({
password: '',
comparedPassword: ''
}),
validate: ({ password, comparedPassword }) => {
- const isInvalidPassword = validatePassword(password, lang)
+ const isInvalidPassword = validatePassword(password)
const isInvalidComparedPassword = validateComparedPassword(
password,
- comparedPassword,
- lang
+ comparedPassword
)
const errors = {
...(isInvalidPassword ? { password: isInvalidPassword } : {}),
@@ -185,40 +183,31 @@ export const PasswordChangeConfirmForm: FC = ({
return errors
},
- handleSubmit: (values, { props, setFieldError, setSubmitting }: any) => {
+ handleSubmit: async (values, { setFieldError, setSubmitting }) => {
const { password } = values
- const { submitAction } = props
- if (!submitAction) {
- return
- }
- submitAction({ variables: { input: { password, codeId } } })
- .then(({ data }: any) => {
- const { resetPassword } = data
- if (submitCallback && resetPassword) {
- submitCallback()
- }
- })
- .catch((error: any) => {
- const errorCode = getErrorCodes(error)[0]
- const errorMessage = translate({
- zh_hant: TEXT.zh_hant.error[errorCode] || errorCode,
- zh_hans: TEXT.zh_hans.error[errorCode] || errorCode,
- lang
- })
- setFieldError('password', errorMessage)
+
+ try {
+ const { data } = await reset({
+ variables: { input: { password, codeId } }
})
- .finally(() => {
- setSubmitting(false)
+ const resetPassword = data && data.resetPassword
+
+ if (submitCallback && resetPassword) {
+ submitCallback()
+ }
+ } catch (error) {
+ const errorCode = getErrorCodes(error)[0]
+ const errorMessage = translate({
+ zh_hant: TEXT.zh_hant.error[errorCode] || errorCode,
+ zh_hans: TEXT.zh_hans.error[errorCode] || errorCode,
+ lang
})
+ setFieldError('password', errorMessage)
+ }
+
+ setSubmitting(false)
}
- })(BaseForm)
-
- return (
- <>
-
- {(reset: any) => }
-
-
- >
- )
+ })(InnerForm)
+
+ return
}
diff --git a/src/components/Form/PasswordChangeForm/Request/index.tsx b/src/components/Form/PasswordChangeForm/Request/index.tsx
index 7a1e2e10d9..8854518e26 100644
--- a/src/components/Form/PasswordChangeForm/Request/index.tsx
+++ b/src/components/Form/PasswordChangeForm/Request/index.tsx
@@ -1,11 +1,12 @@
import classNames from 'classnames'
-import { withFormik } from 'formik'
+import { FormikProps, withFormik } from 'formik'
import _isEmpty from 'lodash/isEmpty'
-import { FC, useContext } from 'react'
+import { useContext } from 'react'
import { Form } from '~/components/Form'
import SendCodeButton from '~/components/Form/Button/SendCode'
-import { getErrorCodes, Mutation } from '~/components/GQL'
+import { getErrorCodes, useMutation } from '~/components/GQL'
+import { ConfirmVerificationCode } from '~/components/GQL/mutations/__generated__/ConfirmVerificationCode'
import { CONFIRM_CODE } from '~/components/GQL/mutations/verificationCode'
import { LanguageContext, Translate } from '~/components/Language'
import { Modal } from '~/components/Modal'
@@ -15,7 +16,7 @@ import { isValidEmail, translate } from '~/common/utils'
import styles from './styles.css'
-interface Props {
+interface FormProps {
defaultEmail: string
extraClass?: string[]
container: 'modal' | 'page'
@@ -24,18 +25,24 @@ interface Props {
scrollLock?: boolean
}
-export const PasswordChangeRequestForm: FC = ({
- defaultEmail = '',
- extraClass = [],
- container,
- purpose,
- submitCallback,
- scrollLock
-}) => {
- const { lang } = useContext(LanguageContext)
+interface FormValues {
+ email: string
+ code: string
+}
- const validateEmail = (value: string, language: string) => {
- let result: any
+export const PasswordChangeRequestForm: React.FC = formProps => {
+ const [confirmCode] = useMutation(CONFIRM_CODE)
+ const { lang } = useContext(LanguageContext)
+ const {
+ defaultEmail = '',
+ extraClass = [],
+ purpose,
+ submitCallback,
+ scrollLock
+ } = formProps
+
+ const validateEmail = (value: string) => {
+ let result
if (!value) {
result = {
zh_hant: TEXT.zh_hant.required,
@@ -48,12 +55,12 @@ export const PasswordChangeRequestForm: FC = ({
}
}
if (result) {
- return translate({ ...result, lang: language })
+ return translate({ ...result, lang })
}
}
- const validateCode = (value: string, language: string) => {
- let result: any
+ const validateCode = (value: string) => {
+ let result
if (!value) {
result = {
zh_hant: TEXT.zh_hant.required,
@@ -61,11 +68,11 @@ export const PasswordChangeRequestForm: FC = ({
}
}
if (result) {
- return translate({ ...result, lang: language })
+ return translate({ ...result, lang })
}
}
- const BaseForm = ({
+ const InnerForm = ({
values,
errors,
touched,
@@ -74,9 +81,7 @@ export const PasswordChangeRequestForm: FC = ({
handleChange,
handleSubmit,
setFieldError
- }: {
- [key: string]: any
- }) => {
+ }: FormikProps) => {
const formClass = classNames('form', ...extraClass)
const emailPlaceholder =
@@ -99,68 +104,67 @@ export const PasswordChangeRequestForm: FC = ({
})
return (
- <>
-
+ }
+ values={values}
+ errors={errors}
+ touched={touched}
+ handleBlur={handleBlur}
+ handleChange={handleChange}
+ />
+
+
+
+
+
+
+
+
- >
+
)
}
- const MainForm: any = withFormik({
+ const MainForm = withFormik({
mapPropsToValues: () => ({
email: defaultEmail,
code: ''
}),
validate: ({ email, code }) => {
- const isInvalidEmail = validateEmail(email, lang)
- const isInvalidCode = validateCode(code, lang)
+ const isInvalidEmail = validateEmail(email)
+ const isInvalidCode = validateCode(code)
const errors = {
...(isInvalidEmail ? { email: isInvalidEmail } : {}),
...(isInvalidCode ? { code: isInvalidCode } : {})
@@ -168,43 +172,31 @@ export const PasswordChangeRequestForm: FC = ({
return errors
},
- handleSubmit: (values, { props, setFieldError, setSubmitting }: any) => {
+ handleSubmit: async (values, { setFieldError, setSubmitting }) => {
const { email, code } = values
- const { submitAction } = props
- if (!submitAction) {
- return
- }
- submitAction({
- variables: { input: { email, type: 'password_reset', code } }
- })
- .then(({ data }: any) => {
- const { confirmVerificationCode } = data
- if (submitCallback && confirmVerificationCode) {
- submitCallback({ email, codeId: confirmVerificationCode })
- }
- })
- .catch((error: any) => {
- const errorCode = getErrorCodes(error)[0]
- const errorMessage = translate({
- zh_hant: TEXT.zh_hant.error[errorCode] || errorCode,
- zh_hans: TEXT.zh_hans.error[errorCode] || errorCode,
- lang
- })
- setFieldError('code', errorMessage)
+ try {
+ const { data } = await confirmCode({
+ variables: { input: { email, type: 'password_reset', code } }
})
- .finally(() => {
- setSubmitting(false)
+ const confirmVerificationCode = data && data.confirmVerificationCode
+
+ if (submitCallback && confirmVerificationCode) {
+ submitCallback({ email, codeId: confirmVerificationCode })
+ }
+ } catch (error) {
+ const errorCode = getErrorCodes(error)[0]
+ const errorMessage = translate({
+ zh_hant: TEXT.zh_hant.error[errorCode] || errorCode,
+ zh_hans: TEXT.zh_hans.error[errorCode] || errorCode,
+ lang
})
+ setFieldError('code', errorMessage)
+ }
+
+ setSubmitting(false)
}
- })(BaseForm)
-
- return (
- <>
-
- {(confirmCode: any) => }
-
-
- >
- )
+ })(InnerForm)
+
+ return
}
diff --git a/src/components/Form/SignUpComplete/index.tsx b/src/components/Form/SignUpComplete/index.tsx
index a23c322f10..e2220198b4 100644
--- a/src/components/Form/SignUpComplete/index.tsx
+++ b/src/components/Form/SignUpComplete/index.tsx
@@ -65,6 +65,7 @@ const SignUpComplete = ({
+
)
diff --git a/src/components/Form/SignUpForm/Follow/index.tsx b/src/components/Form/SignUpForm/Follow/index.tsx
index db85430d5e..dfe3d60b53 100644
--- a/src/components/Form/SignUpForm/Follow/index.tsx
+++ b/src/components/Form/SignUpForm/Follow/index.tsx
@@ -1,17 +1,17 @@
import classNames from 'classnames'
import gql from 'graphql-tag'
-import _get from 'lodash/get'
-import { FC, useContext } from 'react'
-import { QueryResult } from 'react-apollo'
+import { useContext } from 'react'
+import { useQuery } from 'react-apollo'
import { Button } from '~/components/Button'
-import { AuthorPicker } from '~/components/Follow'
-import { Query } from '~/components/GQL'
+import AuthorPicker from '~/components/Follow/AuthorPicker'
+import { QueryError } from '~/components/GQL'
import { LanguageContext } from '~/components/Language'
import { Spinner } from '~/components/Spinner'
import { translate } from '~/common/utils'
+import { SignUpMeFollow } from './__generated__/SignUpMeFollow'
import styles from './styles.css'
/**
@@ -45,60 +45,64 @@ interface Props {
submitCallback?: () => void
}
-export const SignUpFollowForm: FC = ({
+export const SignUpFollowForm: React.FC = ({
extraClass = [],
purpose,
submitCallback
}) => {
const { lang } = useContext(LanguageContext)
+ const { loading, data, error } = useQuery(ME_FOLLOW)
const containerStyle = classNames(
purpose === 'modal' ? 'modal-container' : 'page-container'
)
-
const titleText = translate({
zh_hant: '請至少選擇 5 位作者',
zh_hans: '请至少选择 5 位作者',
lang
})
-
const nextText = translate({
zh_hant: '下一步',
zh_hans: '下一步',
lang
})
+ if (loading) {
+ return
+ }
+
+ if (error) {
+ return
+ }
+
+ if (!data || !data.viewer) {
+ return null
+ }
+
+ const followeeCount = data.viewer.followees.totalCount || 0
+
return (
-
- {({ data, loading, error }: QueryResult & { data: any }) => {
- if (loading) {
- return
- }
+
+
+
+
+
+ {nextText}
+
+
- const followeeCount = _get(data, 'viewer.followees.totalCount', 0)
- return (
-
-
-
-
- {nextText}
-
-
-
-
- )
- }}
-
+
+
)
}
diff --git a/src/components/Form/SignUpForm/Init/index.tsx b/src/components/Form/SignUpForm/Init/index.tsx
index 7e07c46385..b07ada8ced 100644
--- a/src/components/Form/SignUpForm/Init/index.tsx
+++ b/src/components/Form/SignUpForm/Init/index.tsx
@@ -1,13 +1,14 @@
import classNames from 'classnames'
-import { withFormik } from 'formik'
+import { FormikProps, withFormik } from 'formik'
import gql from 'graphql-tag'
import _isEmpty from 'lodash/isEmpty'
import Link from 'next/link'
-import { FC, useContext } from 'react'
+import { useContext } from 'react'
import { Form } from '~/components/Form'
import SendCodeButton from '~/components/Form/Button/SendCode'
-import { getErrorCodes, Mutation } from '~/components/GQL'
+import { getErrorCodes, useMutation } from '~/components/GQL'
+import { ConfirmVerificationCode } from '~/components/GQL/mutations/__generated__/ConfirmVerificationCode'
import { CONFIRM_CODE } from '~/components/GQL/mutations/verificationCode'
import { LanguageContext, Translate } from '~/components/Language'
import { Modal } from '~/components/Modal'
@@ -23,6 +24,7 @@ import {
translate
} from '~/common/utils'
+import { UserRegister } from './__generated__/UserRegister'
import styles from './styles.css'
/**
@@ -40,7 +42,7 @@ import styles from './styles.css'
* ```
*
*/
-interface Props {
+interface FormProps {
defaultEmail?: string
extraClass?: string[]
purpose: 'modal' | 'page'
@@ -48,6 +50,14 @@ interface Props {
scrollLock?: boolean
}
+interface FormValues {
+ email: string
+ code: string
+ userName: string
+ password: string
+ tos: boolean
+}
+
const USER_REGISTER = gql`
mutation UserRegister($input: UserRegisterInput!) {
userRegister(input: $input) {
@@ -76,19 +86,22 @@ const LoginRedirection = () => (
)
-export const SignUpInitForm: FC = ({
- defaultEmail = '',
- extraClass = [],
- purpose,
- submitCallback,
- scrollLock
-}) => {
+export const SignUpInitForm: React.FC = formProps => {
+ const [confirm] = useMutation(CONFIRM_CODE)
+ const [register] = useMutation(USER_REGISTER)
const { lang } = useContext(LanguageContext)
+ const {
+ defaultEmail = '',
+ extraClass = [],
+ purpose,
+ submitCallback,
+ scrollLock
+ } = formProps
const isInModal = purpose === 'modal'
const isInPage = purpose === 'page'
- const validateEmail = (value: string, language: string) => {
- let result: any
+ const validateEmail = (value: string) => {
+ let result
if (!value) {
result = {
zh_hant: TEXT.zh_hant.required,
@@ -101,12 +114,11 @@ export const SignUpInitForm: FC = ({
}
}
if (result) {
- return translate({ ...result, lang: language })
+ return translate({ ...result, lang })
}
}
-
- const validateCode = (value: string, language: string) => {
- let result: any
+ const validateCode = (value: string) => {
+ let result
if (!value) {
result = {
zh_hant: TEXT.zh_hant.required,
@@ -114,12 +126,12 @@ export const SignUpInitForm: FC = ({
}
}
if (result) {
- return translate({ ...result, lang: language })
+ return translate({ ...result, lang })
}
}
+ const validateUserName = (value: string) => {
+ let result
- const validateUserName = (value: string, language: string) => {
- let result: any
if (!value) {
result = {
zh_hant: TEXT.zh_hant.required,
@@ -131,13 +143,14 @@ export const SignUpInitForm: FC = ({
zh_hans: TEXT.zh_hans.userNameHint
}
}
+
if (result) {
- return translate({ ...result, lang: language })
+ return translate({ ...result, lang })
}
}
+ const validatePassword = (value: string) => {
+ let result
- const validatePassword = (value: string, language: string) => {
- let result: any
if (!value) {
result = {
zh_hant: TEXT.zh_hant.required,
@@ -149,22 +162,24 @@ export const SignUpInitForm: FC = ({
zh_hans: TEXT.zh_hans.passwordHint
}
}
+
if (result) {
- return translate({ ...result, lang: language })
+ return translate({ ...result, lang })
}
}
+ const validateToS = (value: boolean) => {
+ let result
- const validateToS = (value: boolean, language: string) => {
- let result: any
if (value === false) {
result = { zh_hant: '請勾選', zh_hans: '请勾选' }
}
+
if (result) {
- return translate({ ...result, lang: language })
+ return translate({ ...result, lang })
}
}
- const BaseForm = ({
+ const InnerForm = ({
values,
errors,
touched,
@@ -174,9 +189,7 @@ export const SignUpInitForm: FC = ({
handleSubmit,
setFieldValue,
setFieldError
- }: {
- [key: string]: any
- }) => {
+ }: FormikProps) => {
const formClass = classNames('form', ...extraClass)
const emailPlaceholder = translate({
@@ -216,112 +229,111 @@ export const SignUpInitForm: FC = ({
})
return (
- <>
-
)
diff --git a/src/components/Menu/Item/index.tsx b/src/components/Menu/Item/index.tsx
index 66e637b1cb..90585dc230 100644
--- a/src/components/Menu/Item/index.tsx
+++ b/src/components/Menu/Item/index.tsx
@@ -30,6 +30,7 @@ const Item: React.FC = ({
return (
-
{children}
+
)
diff --git a/src/components/Menu/index.tsx b/src/components/Menu/index.tsx
index 0a6661a2cd..288d233e62 100644
--- a/src/components/Menu/index.tsx
+++ b/src/components/Menu/index.tsx
@@ -36,12 +36,11 @@ export class Menu extends React.PureComponent {
})
return (
- <>
-
+
)
}
}
diff --git a/src/components/Modal/Anchor/index.tsx b/src/components/Modal/Anchor/index.tsx
index 6791a7ce80..43b5a5e2f3 100644
--- a/src/components/Modal/Anchor/index.tsx
+++ b/src/components/Modal/Anchor/index.tsx
@@ -53,7 +53,7 @@ const Anchor = () => {
viewer.isAuthed &&
!isLikeCoinClosed &&
isLikeCoinAllowPaths &&
- (viewer.isOnboarding || !viewer.likerId)
+ viewer.shouldSetupLikerID
const closeLikeCoinModal = () => {
setIsLikeCoinClosed(true)
}
@@ -101,7 +101,9 @@ const Anchor = () => {
{viewer.isAuthed && disagreedToS && }
+
{shouldShowLikeCoinModal && }
+
>
)
diff --git a/src/components/Modal/BlockUserModal/index.tsx b/src/components/Modal/BlockUserModal/index.tsx
new file mode 100644
index 0000000000..8fb25adc89
--- /dev/null
+++ b/src/components/Modal/BlockUserModal/index.tsx
@@ -0,0 +1,99 @@
+import { useState } from 'react'
+import { useMutation } from 'react-apollo'
+
+import { BlockUser } from '~/components/GQL/fragments/__generated__/BlockUser'
+import { BlockUser as BlockUserMutate } from '~/components/GQL/mutations/__generated__/BlockUser'
+import BLOCK_USER from '~/components/GQL/mutations/blockUser'
+import { Translate } from '~/components/Language'
+import { Modal } from '~/components/Modal'
+import ModalComplete from '~/components/Modal/Complete'
+
+import { TEXT } from '~/common/enums'
+
+/**
+ * This component is a modal for changing user name.
+ *
+ * Usage:
+ *
+ * ```jsx
+ *
+ * ```
+ *
+ */
+
+interface Props {
+ user: BlockUser
+}
+
+type Step = 'confirm' | 'complete'
+
+const BlocKUserModal: React.FC = ({
+ close,
+ user
+}) => {
+ const [step, setStep] = useState('confirm')
+ const [blockUser] = useMutation(BLOCK_USER, {
+ variables: { id: user.id },
+ optimisticResponse: {
+ blockUser: {
+ id: user.id,
+ isBlocked: true,
+ __typename: 'User'
+ }
+ }
+ })
+
+ const confirmCallback = () => setStep('complete')
+
+ return (
+ <>
+ {step === 'confirm' && (
+ <>
+
+
+
+
+
+
+
+
+
+ {
+ event.stopPropagation()
+ await blockUser()
+ confirmCallback()
+ }}
+ >
+
+
+
+ >
+ )}
+ {step === 'complete' && (
+
+ }
+ />
+ )}
+ >
+ )
+}
+
+export default BlocKUserModal
diff --git a/src/components/Modal/Complete/index.tsx b/src/components/Modal/Complete/index.tsx
index 699d0a52db..9732021644 100644
--- a/src/components/Modal/Complete/index.tsx
+++ b/src/components/Modal/Complete/index.tsx
@@ -15,7 +15,9 @@ const ModalComplete = ({
{message}
+
{hint}
+
diff --git a/src/components/Modal/Container/index.tsx b/src/components/Modal/Container/index.tsx
index 32fcbdba26..7e79b696e7 100644
--- a/src/components/Modal/Container/index.tsx
+++ b/src/components/Modal/Container/index.tsx
@@ -1,6 +1,6 @@
// External modules
import classNames from 'classnames'
-import { FC, useContext, useRef, useState } from 'react'
+import { useContext, useRef, useState } from 'react'
import { LanguageContext } from '~/components'
import { useNativeEventListener, useOutsideClick } from '~/components/Hook'
@@ -20,7 +20,7 @@ export interface ContainerProps {
layout?: 'default' | 'small'
}
-const Container: FC = ({
+const Container: React.FC = ({
children,
close,
defaultCloseable = true,
@@ -100,6 +100,7 @@ const Container: FC = ({
+
)
diff --git a/src/components/Modal/EmailModal/index.tsx b/src/components/Modal/EmailModal/index.tsx
index 4e31dac617..6ef4c20df0 100644
--- a/src/components/Modal/EmailModal/index.tsx
+++ b/src/components/Modal/EmailModal/index.tsx
@@ -1,4 +1,4 @@
-import { FC, useContext, useState } from 'react'
+import { useContext, useState } from 'react'
import {
EmailChangeConfirmForm,
@@ -23,7 +23,7 @@ import { TEXT } from '~/common/enums'
type Step = 'request' | 'confirm' | 'complete'
-const EmailModal: FC = ({ close }) => {
+const EmailModal: React.FC = ({ close }) => {
const viewer = useContext(ViewerContext)
const [step, setStep] = useState('request')
const [data, setData] = useState<{ [key: string]: any }>({
diff --git a/src/components/Modal/FooterButton/index.tsx b/src/components/Modal/FooterButton/index.tsx
index 0ba1cc964d..32d9f0596e 100644
--- a/src/components/Modal/FooterButton/index.tsx
+++ b/src/components/Modal/FooterButton/index.tsx
@@ -56,6 +56,7 @@ const FooterButton: React.FC = ({
{children}
+
>
)
@@ -68,6 +69,7 @@ const FooterButton: React.FC = ({
{loading && }
{!loading && children}
+
>
)
diff --git a/src/components/Modal/Header/index.tsx b/src/components/Modal/Header/index.tsx
index 934b47a12d..c4f333f9d9 100644
--- a/src/components/Modal/Header/index.tsx
+++ b/src/components/Modal/Header/index.tsx
@@ -39,6 +39,7 @@ const ModalHeader: React.FC = ({
/>
)}
+
)}
diff --git a/src/components/Modal/LikeCoinTermModal/index.tsx b/src/components/Modal/LikeCoinTermModal/index.tsx
index 92ff3c2b65..06835fcbb5 100644
--- a/src/components/Modal/LikeCoinTermModal/index.tsx
+++ b/src/components/Modal/LikeCoinTermModal/index.tsx
@@ -1,4 +1,4 @@
-import { FC, useContext } from 'react'
+import { useContext } from 'react'
import { LanguageContext, Translate } from '~/components/Language'
import { Modal } from '~/components/Modal'
@@ -15,7 +15,7 @@ interface Props {
submitCallback: () => void
}
-const LikeCoinTermModal: FC = ({
+const LikeCoinTermModal: React.FC = ({
submitCallback
}) => {
const { lang } = useContext(LanguageContext)
diff --git a/src/components/Modal/PasswordModal/index.tsx b/src/components/Modal/PasswordModal/index.tsx
index 11600e50c0..184b9cbc79 100644
--- a/src/components/Modal/PasswordModal/index.tsx
+++ b/src/components/Modal/PasswordModal/index.tsx
@@ -1,4 +1,4 @@
-import { FC, useContext, useState } from 'react'
+import { useContext, useState } from 'react'
import {
PasswordChangeConfirmForm,
@@ -23,7 +23,7 @@ import { translate } from '~/common/utils'
*
*/
-const PasswordModal: FC<
+const PasswordModal: React.FC<
ModalInstanceProps & { purpose: 'forget' | 'change' }
> = ({ purpose }) => {
const { lang } = useContext(LanguageContext)
@@ -96,7 +96,7 @@ const PasswordModal: FC<
setStep('reset')
}
- const backPreviousStep = (event: any) => {
+ const backPreviousStep = () => {
setStep('request')
}
diff --git a/src/components/Modal/PublishModal/PublishSlide.tsx b/src/components/Modal/PublishModal/PublishSlide.tsx
index 801c13eb34..1c0c72aed2 100644
--- a/src/components/Modal/PublishModal/PublishSlide.tsx
+++ b/src/components/Modal/PublishModal/PublishSlide.tsx
@@ -1,4 +1,3 @@
-import _get from 'lodash/get'
import { useContext } from 'react'
import { LanguageContext } from '~/components/Language'
@@ -16,6 +15,7 @@ const Descriptions = ({ data }: any) => (
{desc}
))}
+
)
@@ -66,6 +66,7 @@ const PublishSlide = () => {
style={{ backgroundImage: `url(${PUBLISH_IMAGE})` }}
/>
+
{title}
@@ -74,6 +75,7 @@ const PublishSlide = () => {
+
>
)
diff --git a/src/components/Modal/PublishModal/index.tsx b/src/components/Modal/PublishModal/index.tsx
index 075e5a107a..cbe720f9fd 100644
--- a/src/components/Modal/PublishModal/index.tsx
+++ b/src/components/Modal/PublishModal/index.tsx
@@ -1,12 +1,11 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
-import { FC } from 'react'
import { DraftDetailQuery_node_Draft } from '~/views/Me/DraftDetail/__generated__/DraftDetailQuery'
-import { Mutation } from '~/components/GQL'
+import { useMutation } from '~/components/GQL'
import { Translate } from '~/components/Language'
import { Modal } from '~/components/Modal'
+import { PublishArticle } from './__generated__/PublishArticle'
import PublishSlide from './PublishSlide'
import styles from './styles.css'
@@ -37,13 +36,24 @@ const PUBLISH_ARTICLE = gql`
}
`
-export const PublishModal: FC = ({ close, draft }) => {
+export const PublishModal: React.FC = ({ close, draft }) => {
const draftId = draft.id
const hasContent = draft.content && draft.content.length > 0
const hasTitle = draft.title && draft.title.length > 0
const isUnpublished = draft.publishState === 'unpublished'
const publishable = draftId && isUnpublished && hasContent && hasTitle
+ const [publish] = useMutation(PUBLISH_ARTICLE, {
+ optimisticResponse: {
+ publishArticle: {
+ id: draftId,
+ scheduledAt: new Date(Date.now() + 1000).toISOString(),
+ publishState: 'pending' as any,
+ __typename: 'Draft'
+ }
+ }
+ })
+
return (
@@ -55,38 +65,22 @@ export const PublishModal: FC = ({ close, draft }) => {
- {
+ const { data } = await publish({ variables: { draftId } })
+ const state =
+ (data && data.publishArticle.publishState) || 'unpublished'
+
+ if (state === 'pending') {
+ close()
}
}}
>
- {(publish: any, loading: any) => (
- {
- const { data }: any = await publish({ variables: { draftId } })
- const state = _get(
- data,
- 'publishArticle.publishState',
- 'unpublished'
- )
-
- if (state === 'pending') {
- close()
- }
- }}
- >
-
-
- )}
-
+
+
+
)
diff --git a/src/components/Modal/SignUpModal/index.tsx b/src/components/Modal/SignUpModal/index.tsx
index 7230b468c5..457b5ea520 100644
--- a/src/components/Modal/SignUpModal/index.tsx
+++ b/src/components/Modal/SignUpModal/index.tsx
@@ -1,4 +1,4 @@
-import { FC, useContext, useState } from 'react'
+import { useContext, useState } from 'react'
import SignUpComplete from '~/components/Form/SignUpComplete'
import { SignUpInitForm, SignUpProfileForm } from '~/components/Form/SignUpForm'
@@ -22,7 +22,10 @@ import { translate } from '~/common/utils'
type Step = 'signUp' | 'profile' | 'setupLikeCoin' | 'complete'
-const SignUpModal: FC = ({ closeable, setCloseable }) => {
+const SignUpModal: React.FC = ({
+ closeable,
+ setCloseable
+}) => {
const { lang } = useContext(LanguageContext)
const [step, setStep] = useState('signUp')
diff --git a/src/components/Modal/TermModal/index.tsx b/src/components/Modal/TermModal/index.tsx
index d966c3626d..c9b82eff61 100644
--- a/src/components/Modal/TermModal/index.tsx
+++ b/src/components/Modal/TermModal/index.tsx
@@ -1,9 +1,10 @@
-import { withFormik } from 'formik'
+import { FormikProps, FormikValues, withFormik } from 'formik'
import gql from 'graphql-tag'
import Router from 'next/router'
-import { FC, useContext } from 'react'
+import { useContext } from 'react'
-import { Mutation } from '~/components/GQL'
+import { useMutation } from '~/components/GQL'
+import { UserLogout } from '~/components/GQL/mutations/__generated__/UserLogout'
import USER_LOGOUT from '~/components/GQL/mutations/userLogout'
import IconSpinner from '~/components/Icon/Spinner'
import { LanguageContext } from '~/components/Language'
@@ -13,6 +14,7 @@ import { TEXT } from '~/common/enums'
import { translate } from '~/common/utils'
import { Modal } from '..'
+import { UpdateUserInfoAgreeOn } from './__generated__/UpdateUserInfoAgreeOn'
import styles from './styles.css'
/**
@@ -25,6 +27,8 @@ import styles from './styles.css'
* ```
*/
+type FormProps = ModalInstanceProps
+
const UPDATE_AGREE_ON = gql`
mutation UpdateUserInfoAgreeOn($input: UpdateUserInfoInput!) {
updateUserInfo(input: $input) {
@@ -36,98 +40,85 @@ const UPDATE_AGREE_ON = gql`
}
`
-const TermModal: FC = ({ close }) => {
+const TermModal: React.FC = formProps => {
+ const [logout] = useMutation(USER_LOGOUT)
+ const [update] = useMutation(UPDATE_AGREE_ON)
const { lang } = useContext(LanguageContext)
- const disagree = (action: any) => {
- if (action) {
- action()
- .then(() => {
- close()
- Router.replace('/')
- })
- .catch(() => {
- // TODO: Handle error
- })
- }
- }
+ const InnerForm = ({
+ isSubmitting,
+ handleSubmit
+ }: FormikProps) => (
+
)
- const TermForm: any = withFormik({
- handleSubmit: (values, { props, setSubmitting }: any) => {
- const { submitAction } = props
- if (!submitAction) {
- return
+ const MainForm = withFormik({
+ handleSubmit: async (values, { props, setSubmitting }) => {
+ try {
+ await update({ variables: { input: { agreeOn: true } } })
+ props.close()
+ } catch (error) {
+ // TODO: Handle error
}
- submitAction({ variables: { input: { agreeOn: true } } })
- .then((result: any) => {
- close()
- })
- .catch((result: any) => {
- // TODO: Handle error
- })
- .finally(() => {
- setSubmitting(false)
- })
+
+ setSubmitting(false)
}
- })(BaseForm)
+ })(InnerForm)
- return (
-
- {(update: any) => }
-
- )
+ return
}
export default TermModal
diff --git a/src/components/Modal/TermModal/styles.css b/src/components/Modal/TermModal/styles.css
index 7ee3b1a07d..078aab0e66 100644
--- a/src/components/Modal/TermModal/styles.css
+++ b/src/components/Modal/TermModal/styles.css
@@ -1,4 +1,4 @@
-.form {
+form {
& > .term {
padding: var(--spacing-tight) var(--spacing-tight) var(--spacing-default);
diff --git a/src/components/Modal/UserNameModal/index.tsx b/src/components/Modal/UserNameModal/index.tsx
index 9b8027c13b..b92ba6df80 100644
--- a/src/components/Modal/UserNameModal/index.tsx
+++ b/src/components/Modal/UserNameModal/index.tsx
@@ -1,4 +1,4 @@
-import { FC, useState } from 'react'
+import { useState } from 'react'
import { UserNameChangeConfirmForm } from '~/components/Form/UserNameChangeForm'
import { Translate } from '~/components/Language'
@@ -20,14 +20,9 @@ import { TEXT } from '~/common/enums'
type Step = 'ask' | 'confirm' | 'complete'
-const UserNameModal: FC = ({ close }) => {
+const UserNameModal: React.FC = ({ close }) => {
const [step, setStep] = useState('ask')
- const askCallback = (event: any) => {
- event.stopPropagation()
- setStep('confirm')
- }
-
const confirmCallback = () => setStep('complete')
return (
@@ -47,7 +42,12 @@ const UserNameModal: FC = ({ close }) => {
zh_hans={TEXT.zh_hans.cancel}
/>
-
+ {
+ event.stopPropagation()
+ setStep('confirm')
+ }}
+ >
{
+
)
diff --git a/src/components/NoticeDigest/ArticleNewAppreciationNotice.tsx b/src/components/NoticeDigest/ArticleNewAppreciationNotice.tsx
index f13ed480e2..2788885ec8 100644
--- a/src/components/NoticeDigest/ArticleNewAppreciationNotice.tsx
+++ b/src/components/NoticeDigest/ArticleNewAppreciationNotice.tsx
@@ -61,6 +61,7 @@ const ArticleNewAppreciationNotice = ({ notice }: { notice: NoticeType }) => {
+
)
diff --git a/src/components/NoticeDigest/ArticleNewCollectedNotice.tsx b/src/components/NoticeDigest/ArticleNewCollectedNotice.tsx
index 9325af9255..3419839a1e 100644
--- a/src/components/NoticeDigest/ArticleNewCollectedNotice.tsx
+++ b/src/components/NoticeDigest/ArticleNewCollectedNotice.tsx
@@ -38,6 +38,7 @@ const ArticleNewCollectedNotice = ({ notice }: { notice: NoticeType }) => {
+
)
diff --git a/src/components/NoticeDigest/ArticleNewCommentNotice.tsx b/src/components/NoticeDigest/ArticleNewCommentNotice.tsx
index 3f4a90715d..110ad87739 100644
--- a/src/components/NoticeDigest/ArticleNewCommentNotice.tsx
+++ b/src/components/NoticeDigest/ArticleNewCommentNotice.tsx
@@ -60,6 +60,7 @@ const ArticleNewCommentNotice = ({ notice }: { notice: NoticeType }) => {
+
)
diff --git a/src/components/NoticeDigest/ArticleNewDownstreamNotice.tsx b/src/components/NoticeDigest/ArticleNewDownstreamNotice.tsx
index 41dc046797..d97e0a86e6 100644
--- a/src/components/NoticeDigest/ArticleNewDownstreamNotice.tsx
+++ b/src/components/NoticeDigest/ArticleNewDownstreamNotice.tsx
@@ -59,6 +59,7 @@ const ArticleNewDownstreamNotice = ({ notice }: { notice: NoticeType }) => {
+
)
diff --git a/src/components/NoticeDigest/ArticleNewSubscriberNotice.tsx b/src/components/NoticeDigest/ArticleNewSubscriberNotice.tsx
index 84aaf7e156..e02c6fe8bf 100644
--- a/src/components/NoticeDigest/ArticleNewSubscriberNotice.tsx
+++ b/src/components/NoticeDigest/ArticleNewSubscriberNotice.tsx
@@ -58,6 +58,7 @@ const ArticleNewSubscriberNotice = ({ notice }: { notice: NoticeType }) => {
+
)
diff --git a/src/components/NoticeDigest/ArticlePublishedNotice.tsx b/src/components/NoticeDigest/ArticlePublishedNotice.tsx
index e9d9ee37ae..6517892dc3 100644
--- a/src/components/NoticeDigest/ArticlePublishedNotice.tsx
+++ b/src/components/NoticeDigest/ArticlePublishedNotice.tsx
@@ -29,6 +29,7 @@ const ArticlePublishedNotice = ({ notice }: { notice: NoticeType }) => {
+
)
diff --git a/src/components/NoticeDigest/CommentMentionedYouNotice.tsx b/src/components/NoticeDigest/CommentMentionedYouNotice.tsx
index 35593d0a08..f2dc558bfb 100644
--- a/src/components/NoticeDigest/CommentMentionedYouNotice.tsx
+++ b/src/components/NoticeDigest/CommentMentionedYouNotice.tsx
@@ -26,6 +26,7 @@ const CommentMentionedYouNotice = ({ notice }: { notice: NoticeType }) => {
+
)
diff --git a/src/components/NoticeDigest/CommentNewReplyNotice.tsx b/src/components/NoticeDigest/CommentNewReplyNotice.tsx
index 4cc7b55344..8431ae6df8 100644
--- a/src/components/NoticeDigest/CommentNewReplyNotice.tsx
+++ b/src/components/NoticeDigest/CommentNewReplyNotice.tsx
@@ -58,6 +58,7 @@ const CommentNewReplyNotice = ({ notice }: { notice: NoticeType }) => {
+
)
diff --git a/src/components/NoticeDigest/CommentNewUpvoteNotice.tsx b/src/components/NoticeDigest/CommentNewUpvoteNotice.tsx
index 9f56735f63..3563508dc2 100644
--- a/src/components/NoticeDigest/CommentNewUpvoteNotice.tsx
+++ b/src/components/NoticeDigest/CommentNewUpvoteNotice.tsx
@@ -58,6 +58,7 @@ const CommentNewUpvoteNotice = ({ notice }: { notice: NoticeType }) => {
+
)
diff --git a/src/components/NoticeDigest/CommentPinnedNotice.tsx b/src/components/NoticeDigest/CommentPinnedNotice.tsx
index e67a898a77..99ca878c70 100644
--- a/src/components/NoticeDigest/CommentPinnedNotice.tsx
+++ b/src/components/NoticeDigest/CommentPinnedNotice.tsx
@@ -26,6 +26,7 @@ const CommentPinnedNotice = ({ notice }: { notice: NoticeType }) => {
+
)
diff --git a/src/components/NoticeDigest/DownstreamArticleArchivedNotice.tsx b/src/components/NoticeDigest/DownstreamArticleArchivedNotice.tsx
index 076df57359..5f35f77516 100644
--- a/src/components/NoticeDigest/DownstreamArticleArchivedNotice.tsx
+++ b/src/components/NoticeDigest/DownstreamArticleArchivedNotice.tsx
@@ -35,6 +35,7 @@ const DownstreamArticleArchivedNotice = ({
+
)
diff --git a/src/components/NoticeDigest/NoticeComment.tsx b/src/components/NoticeDigest/NoticeComment.tsx
index e14ee75eed..853dc953da 100644
--- a/src/components/NoticeDigest/NoticeComment.tsx
+++ b/src/components/NoticeDigest/NoticeComment.tsx
@@ -1,5 +1,4 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
import CommentContent from '~/components/CommentDigest/Content'
@@ -12,7 +11,7 @@ const NoticeComment = ({ comment }: { comment: NoticeCommentType | null }) => {
return null
}
- const parentId = _get(comment, 'parentComment.id')
+ const parentId = comment && comment.parentComment && comment.parentComment.id
const path = toPath({
page: 'articleDetail',
userName: comment.article.author.userName || '',
diff --git a/src/components/NoticeDigest/OfficialAnnouncementNotice.tsx b/src/components/NoticeDigest/OfficialAnnouncementNotice.tsx
index b6684996d1..9fd1851fb3 100644
--- a/src/components/NoticeDigest/OfficialAnnouncementNotice.tsx
+++ b/src/components/NoticeDigest/OfficialAnnouncementNotice.tsx
@@ -28,6 +28,7 @@ const OfficialAnnouncementNotice = ({ notice }: { notice: NoticeType }) => {
+
)
diff --git a/src/components/NoticeDigest/SubscribedArticleNewCommentNotice.tsx b/src/components/NoticeDigest/SubscribedArticleNewCommentNotice.tsx
index 1d9747dada..93912d0653 100644
--- a/src/components/NoticeDigest/SubscribedArticleNewCommentNotice.tsx
+++ b/src/components/NoticeDigest/SubscribedArticleNewCommentNotice.tsx
@@ -67,6 +67,7 @@ const SubscribedArticleNewCommentNotice = ({
+
)
diff --git a/src/components/NoticeDigest/UserNewFollowerNotice.tsx b/src/components/NoticeDigest/UserNewFollowerNotice.tsx
index c156a8e5f2..b8f2bc2d94 100644
--- a/src/components/NoticeDigest/UserNewFollowerNotice.tsx
+++ b/src/components/NoticeDigest/UserNewFollowerNotice.tsx
@@ -59,6 +59,7 @@ const UserNewFollowerNotice = ({ notice }: { notice: NoticeType }) => {
+
)
diff --git a/src/components/PageHeader/index.tsx b/src/components/PageHeader/index.tsx
index 0f9a850518..54b8073eb5 100644
--- a/src/components/PageHeader/index.tsx
+++ b/src/components/PageHeader/index.tsx
@@ -19,6 +19,7 @@ export const PageHeader: React.FC = ({
{children}
+
)
diff --git a/src/components/Placeholder/ArticleDetail.tsx b/src/components/Placeholder/ArticleDetail.tsx
index fa882c6946..1fc2001efa 100644
--- a/src/components/Placeholder/ArticleDetail.tsx
+++ b/src/components/Placeholder/ArticleDetail.tsx
@@ -1,7 +1,7 @@
import React from 'react'
import ContentLoader from 'react-content-loader'
-import { Responsive } from '~/components'
+import { useResponsive } from '~/components/Hook'
import { LoaderProps } from './utils'
@@ -23,28 +23,30 @@ const LG = () => (
)
-const ArticleDetail = () => (
- <>
-
-
-
+const ArticleDetail = () => {
+ const isXSmall = useResponsive({ type: 'xsmall' })()
+ const isSmall = useResponsive({ type: 'small' })()
+ const isMedium = useResponsive({ type: 'medium' })()
+ const isLarge = useResponsive({ type: 'large' })()
+ const isXLarge = useResponsive({ type: 'xlarge' })()
-
-
-
+ if (isXSmall) {
+ return
+ }
-
-
-
+ if (isSmall) {
+ return
+ }
-
-
-
+ if (isMedium) {
+ return
+ }
-
-
-
- >
-)
+ if (isLarge || isXLarge) {
+ return
+ }
+
+ return null
+}
export default ArticleDetail
diff --git a/src/components/Placeholder/ArticleDigestList.tsx b/src/components/Placeholder/ArticleDigestList.tsx
index 64946cf21d..68b1c64fae 100644
--- a/src/components/Placeholder/ArticleDigestList.tsx
+++ b/src/components/Placeholder/ArticleDigestList.tsx
@@ -1,7 +1,7 @@
import React from 'react'
import ContentLoader from 'react-content-loader'
-import { Responsive } from '~/components'
+import { useResponsive } from '~/components/Hook'
import { LoaderProps } from './utils'
@@ -23,28 +23,30 @@ const LG: React.FC = props => (
)
-const ArticleDigestList = () => (
- <>
-
-
-
+const ArticleDigestList = () => {
+ const isXSmall = useResponsive({ type: 'xsmall' })()
+ const isSmall = useResponsive({ type: 'small' })()
+ const isMedium = useResponsive({ type: 'medium' })()
+ const isLarge = useResponsive({ type: 'large' })()
+ const isXLarge = useResponsive({ type: 'xlarge' })()
-
-
-
+ if (isXSmall) {
+ return
+ }
-
-
-
+ if (isSmall) {
+ return
+ }
-
-
-
+ if (isMedium) {
+ return
+ }
-
-
-
- >
-)
+ if (isLarge || isXLarge) {
+ return
+ }
+
+ return null
+}
export default ArticleDigestList
diff --git a/src/components/Placeholder/MattersToday.tsx b/src/components/Placeholder/MattersToday.tsx
index 75a1a5f384..f70b24cf2a 100644
--- a/src/components/Placeholder/MattersToday.tsx
+++ b/src/components/Placeholder/MattersToday.tsx
@@ -1,7 +1,7 @@
import React from 'react'
import ContentLoader from 'react-content-loader'
-import { Responsive } from '~/components'
+import { useResponsive } from '~/components/Hook'
import { LoaderProps } from './utils'
@@ -23,28 +23,30 @@ const LG = () => (
)
-const MattersToday = () => (
- <>
-
-
-
+const MattersToday = () => {
+ const isXSmall = useResponsive({ type: 'xsmall' })()
+ const isSmall = useResponsive({ type: 'small' })()
+ const isMedium = useResponsive({ type: 'medium' })()
+ const isLarge = useResponsive({ type: 'large' })()
+ const isXLarge = useResponsive({ type: 'xlarge' })()
-
-
-
+ if (isXSmall) {
+ return
+ }
-
-
-
+ if (isSmall) {
+ return
+ }
-
-
-
+ if (isMedium) {
+ return
+ }
-
-
-
- >
-)
+ if (isLarge || isXLarge) {
+ return
+ }
+
+ return null
+}
export default MattersToday
diff --git a/src/components/Placeholder/UserProfile.tsx b/src/components/Placeholder/UserProfile.tsx
index 1afd5e16fb..08146caedc 100644
--- a/src/components/Placeholder/UserProfile.tsx
+++ b/src/components/Placeholder/UserProfile.tsx
@@ -1,7 +1,7 @@
import React from 'react'
import ContentLoader from 'react-content-loader'
-import { Responsive } from '~/components'
+import { useResponsive } from '~/components/Hook'
import { LoaderProps } from './utils'
@@ -35,24 +35,29 @@ const LG = () => (
)
-const UserProfile = () => (
- <>
-
-
-
+const UserProfile = () => {
+ const isXSmall = useResponsive({ type: 'xsmall' })()
+ const isSmall = useResponsive({ type: 'small' })()
+ const isMedium = useResponsive({ type: 'medium' })()
+ const isLargeUp = useResponsive({ type: 'large-up' })()
-
-
-
+ if (isXSmall) {
+ return
+ }
-
-
-
+ if (isSmall) {
+ return
+ }
-
-
-
- >
-)
+ if (isMedium) {
+ return
+ }
+
+ if (isLargeUp) {
+ return
+ }
+
+ return null
+}
export default UserProfile
diff --git a/src/components/Protected/index.tsx b/src/components/Protected/index.tsx
index 90ad18b23a..cdac28e6e3 100644
--- a/src/components/Protected/index.tsx
+++ b/src/components/Protected/index.tsx
@@ -8,6 +8,12 @@ import { redirectToLogin } from '~/common/utils'
export const Protected: React.FC = ({ children }) => {
const viewer = useContext(ViewerContext)
+ useEffect(() => {
+ if (!viewer.isAuthed && process.browser) {
+ redirectToLogin()
+ }
+ }, [])
+
if (viewer.isAuthed) {
return <>{children}>
}
@@ -16,9 +22,5 @@ export const Protected: React.FC = ({ children }) => {
return
}
- useEffect(() => {
- redirectToLogin()
- }, [])
-
return
}
diff --git a/src/components/Responsive/index.tsx b/src/components/Responsive/index.tsx
deleted file mode 100644
index 01ec8ac77d..0000000000
--- a/src/components/Responsive/index.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import React from 'react'
-import MediaQuery from 'react-responsive'
-
-import { BREAKPOINTS } from '~/common/enums'
-
-const SmallDown: React.FC = props => (
-
-)
-const MediumDown: React.FC = props => (
-
-)
-
-const SmallUp: React.FC = props => (
-
-)
-const MediumUp: React.FC = props => (
-
-)
-const LargeUp: React.FC = props => (
-
-)
-
-const XSmall = SmallDown
-const Small: React.FC = props => (
-
-)
-const Medium: React.FC = props => (
-
-)
-const Large: React.FC = props => (
-
-)
-const XLarge: React.FC = props => (
-
-)
-
-export const Responsive = {
- SmallDown,
- MediumDown,
-
- SmallUp,
- MediumUp,
- LargeUp,
-
- XSmall,
- Small,
- Medium,
- Large,
- XLarge
-}
diff --git a/src/components/SearchBar/AutoComplete/ClearHistoryButton.tsx b/src/components/SearchBar/AutoComplete/ClearHistoryButton.tsx
index 820fb30954..ad1beb8009 100644
--- a/src/components/SearchBar/AutoComplete/ClearHistoryButton.tsx
+++ b/src/components/SearchBar/AutoComplete/ClearHistoryButton.tsx
@@ -1,10 +1,12 @@
import gql from 'graphql-tag'
-import { Mutation } from '~/components/GQL'
+import { useMutation } from '~/components/GQL'
import { Translate } from '~/components/Language'
import { ADD_TOAST } from '~/common/enums'
+import { ClearHistory } from './__generated__/ClearHistory'
+import { ViewerRecentSearches } from './__generated__/ViewerRecentSearches'
import styles from './styles.css'
const fragments = {
@@ -43,12 +45,11 @@ const VIEWER_RECENT_SEARCHES = gql`
${fragments.user}
`
-const ClearHistoryButton = () => (
- {
+const ClearHistoryButton = () => {
+ const [clear] = useMutation(CLEAR_HISTORY, {
+ update: cache => {
try {
- const data = cache.readQuery({
+ const data = cache.readQuery({
query: VIEWER_RECENT_SEARCHES
})
@@ -79,35 +80,34 @@ const ClearHistoryButton = () => (
} catch (e) {
console.error(e)
}
- }}
- >
- {(clear: any) => (
- {
- await clear()
- window.dispatchEvent(
- new CustomEvent(ADD_TOAST, {
- detail: {
- color: 'green',
- content: (
-
- )
- }
- })
- )
- }}
- >
-
-
-
- )}
-
-)
+ }
+ })
+
+ return (
+ {
+ await clear()
+
+ window.dispatchEvent(
+ new CustomEvent(ADD_TOAST, {
+ detail: {
+ color: 'green',
+ content: (
+
+ )
+ }
+ })
+ )
+ }}
+ >
+
+
+
+
+ )
+}
ClearHistoryButton.fragments = fragments
diff --git a/src/components/SearchBar/AutoComplete/index.tsx b/src/components/SearchBar/AutoComplete/index.tsx
index 6b929ae02d..7eb6b89116 100644
--- a/src/components/SearchBar/AutoComplete/index.tsx
+++ b/src/components/SearchBar/AutoComplete/index.tsx
@@ -1,10 +1,10 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
import Link from 'next/link'
-import { QueryResult } from 'react-apollo'
+import { useEffect } from 'react'
+import { useLazyQuery } from 'react-apollo'
-import { Empty, Icon, Menu, Spinner, Translate } from '~/components'
-import { Query } from '~/components/GQL'
+import { Empty, Icon, Menu, Translate } from '~/components'
+import { Spinner } from '~/components/Spinner'
import { TEXT } from '~/common/enums'
import { toPath } from '~/common/utils'
@@ -14,6 +14,12 @@ import { SearchAutoComplete } from './__generated__/SearchAutoComplete'
import ClearHistoryButton from './ClearHistoryButton'
import styles from './styles.css'
+interface Props {
+ hideDropdown: () => void
+ isShown: boolean
+ searchKey?: string
+}
+
const SEARCH_AUTOCOMPLETE = gql`
query SearchAutoComplete($searchKey: String) {
frequentSearch(input: { first: 5, key: $searchKey })
@@ -30,108 +36,114 @@ const EmptyAutoComplete = () => (
icon={
}
- description="暫無搜尋歷史"
+ description={}
size="small"
/>
)
-const AutoComplete = ({
- hideDropdown,
- searchKey = ''
-}: {
- hideDropdown: () => void
- searchKey?: string
-}) => (
+const AutoComplete = ({ hideDropdown, searchKey = '', isShown }: Props) => {
+ const [getAutoComplete, { data, loading }] = useLazyQuery(
+ SEARCH_AUTOCOMPLETE,
+ {
+ variables: { searchKey }
+ }
+ )
+
+ useEffect(() => {
+ if (isShown) {
+ getAutoComplete()
+ }
+ }, [searchKey, isShown])
+
+ const frequentSearch = (data && data.frequentSearch) || []
+ const recentSearches =
+ (data && data.viewer && data.viewer.activity.recentSearches.edges) || []
+ const showFrequentSearch = frequentSearch.length > 0
+ const showSearchHistory = !searchKey
+
+ if (loading) {
+ return
+ }
+
+ if (!showFrequentSearch && !showSearchHistory) {
+ return null
+ }
+
+ return (
+
+ )
+}
+
+export default (props: Props) => (
-
- {({
- data,
- loading,
- error
- }: QueryResult & { data: SearchAutoComplete }) => {
- if (loading) {
- return
- }
-
- const recentSearches = data.viewer.activity.recentSearches.edges
-
- return (
- (!searchKey || data.frequentSearch.length > 0) && (
-
- )
- )
- }}
-
+
+
)
-
-export default AutoComplete
diff --git a/src/components/SearchBar/index.tsx b/src/components/SearchBar/index.tsx
index e58108bfeb..e346a1f123 100644
--- a/src/components/SearchBar/index.tsx
+++ b/src/components/SearchBar/index.tsx
@@ -1,10 +1,11 @@
import { Formik } from 'formik'
import Router, { useRouter } from 'next/router'
import { useContext, useState } from 'react'
+import { useDebounce } from 'use-debounce'
import { Dropdown, Icon, LanguageContext, PopperInstance } from '~/components'
-import { TEXT } from '~/common/enums'
+import { INPUT_DEBOUNCE, TEXT } from '~/common/enums'
import { getQuery, toPath, translate } from '~/common/utils'
import ICON_SEARCH from '~/static/icons/search.svg?sprite'
@@ -21,9 +22,10 @@ const BaseSearchBar: React.FC<{
autoComplete?: boolean
}> = ({ autoComplete = true }) => {
const router = useRouter()
-
- // translations
+ const q = getQuery({ router, key: 'q' }) || ''
const { lang } = useContext(LanguageContext)
+ const [search, setSearch] = useState('')
+ const [debouncedSearch] = useDebounce(search, INPUT_DEBOUNCE)
const textAriaLabel = translate({
zh_hant: TEXT.zh_hant.search,
zh_hans: TEXT.zh_hans.search,
@@ -37,6 +39,7 @@ const BaseSearchBar: React.FC<{
// dropdown
const [instance, setInstance] = useState(null)
+ const [shown, setShown] = useState(false)
const hideDropdown = () => {
if (instance) {
instance.hide()
@@ -44,18 +47,13 @@ const BaseSearchBar: React.FC<{
}
const showDropdown = () => {
if (instance) {
- setTimeout(() => {
- instance.show()
- }, 100) // unknown bug, needs set a timeout
+ instance.show()
}
}
- // parse query
- const routerQ = getQuery({ router, key: 'q' })
-
return (
{
const path = toPath({
@@ -63,6 +61,7 @@ const BaseSearchBar: React.FC<{
q: values.q
})
Router.push(path.href, path.as)
+ hideDropdown()
}}
render={({ values, handleSubmit, handleChange }) => {
if (!autoComplete) {
@@ -78,7 +77,9 @@ const BaseSearchBar: React.FC<{
onChange={handleChange}
value={values.q}
/>
+
+
)
@@ -87,10 +88,15 @@ const BaseSearchBar: React.FC<{
return (
+
}
trigger="manual"
onCreate={setInstance}
+ onShown={() => setShown(true)}
theme="dropdown shadow-light"
>
@@ -103,13 +109,16 @@ const BaseSearchBar: React.FC<{
value={values.q}
onChange={e => {
handleChange(e)
+ setSearch(e.target.value)
showDropdown()
}}
- onFocus={() => !values.q && showDropdown()}
- onClick={() => !values.q && showDropdown()}
+ onFocus={showDropdown}
+ onClick={showDropdown}
onBlur={hideDropdown}
/>
+
+
diff --git a/src/components/SetupLikeCoin/Binding/index.tsx b/src/components/SetupLikeCoin/Binding/index.tsx
index 82748d69bc..c4f80faa8a 100644
--- a/src/components/SetupLikeCoin/Binding/index.tsx
+++ b/src/components/SetupLikeCoin/Binding/index.tsx
@@ -1,7 +1,6 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
import { useState } from 'react'
-import { Query, QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import { Translate } from '~/components/Language'
import { Modal } from '~/components/Modal'
@@ -9,6 +8,7 @@ import { Spinner } from '~/components/Spinner'
import { TEXT } from '~/common/enums'
+import { ViewerLikerId } from './__generated__/ViewerLikerId'
import styles from './styles.css'
interface Props {
@@ -34,74 +34,67 @@ const Binding: React.FC = ({
scrollLock
}) => {
const [polling, setPolling] = useState(true)
+ const { data, error } = useQuery(VIEWER_LIKER_ID, {
+ pollInterval: polling ? 1000 : undefined,
+ errorPolicy: 'none',
+ fetchPolicy: 'network-only',
+ skip: !process.browser
+ })
+ const likerId = data && data.viewer && data.viewer.likerId
- return (
-
- {({ data, loading, error }: QueryResult) => {
- const likerId = _get(data, 'viewer.likerId')
-
- if (likerId) {
- nextStep()
+ if (likerId) {
+ nextStep()
- if (windowRef) {
- setTimeout(() => {
- windowRef.close()
- }, 5000)
- }
-
- return null
- }
+ if (windowRef) {
+ setTimeout(() => {
+ windowRef.close()
+ }, 5000)
+ }
- if (error) {
- setPolling(false)
- }
+ return null
+ }
- return (
- <>
-
-
- {!error && (
- <>
-
-
-
-
- >
- )}
- {error && (
-
-
-
- )}
-
-
+ if (error) {
+ setPolling(false)
+ }
-
+
+ >
+ )}
+ {error && (
+
+
+
+ )}
+
+
+
+
-
- >
- )
- }}
-
+
+ >
)
}
diff --git a/src/components/SetupLikeCoin/Generating/index.tsx b/src/components/SetupLikeCoin/Generating/index.tsx
index bbe80a1769..4625886c2a 100644
--- a/src/components/SetupLikeCoin/Generating/index.tsx
+++ b/src/components/SetupLikeCoin/Generating/index.tsx
@@ -1,14 +1,14 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
import { useEffect } from 'react'
-import { Mutation } from '~/components/GQL'
+import { useMutation } from '~/components/GQL'
import { Translate } from '~/components/Language'
import { Modal } from '~/components/Modal'
import { Spinner } from '~/components/Spinner'
import { TEXT } from '~/common/enums'
+import { GenerateLikerId } from './__generated__/GenerateLikerId'
import styles from './styles.css'
interface Props {
@@ -30,64 +30,57 @@ const GENERATE_LIKER_ID = gql`
`
const Generating: React.FC = ({ prevStep, nextStep, scrollLock }) => {
- return (
-
- {(generate: any, { error }: any) => {
- useEffect(() => {
- generate().then((result: any) => {
- const likerId = _get(result, 'data.generateLikerId.likerId')
+ const [generate, { error }] = useMutation(GENERATE_LIKER_ID)
- if (likerId) {
- nextStep()
- return null
- }
- })
- }, [])
+ useEffect(() => {
+ generate().then(result => {
+ const likerId =
+ result && result.data && result.data.generateLikerId.likerId
- return (
- <>
-
-
- {!error && (
- <>
-
-
-
-
- >
- )}
- {error && (
-
-
-
- )}
-
-
+ if (likerId) {
+ nextStep()
+ return null
+ }
+ })
+ }, [])
-
+
+ >
+ )}
+ {error && (
+
+
+
+ )}
+
+
+
+
-
- >
- )
- }}
-
+
+ >
)
}
diff --git a/src/components/SetupLikeCoin/Select/index.tsx b/src/components/SetupLikeCoin/Select/index.tsx
index 4b6a1a2411..530abd8270 100644
--- a/src/components/SetupLikeCoin/Select/index.tsx
+++ b/src/components/SetupLikeCoin/Select/index.tsx
@@ -31,6 +31,7 @@ const Header = () => (
zh_hans="接下来我们会帮你生成 Liker ID,如果你已经有 Liker ID 也可以进行绑定。"
/>
+
)
diff --git a/src/components/Spinner/index.tsx b/src/components/Spinner/index.tsx
index a0fc51f9f2..df15f3efff 100644
--- a/src/components/Spinner/index.tsx
+++ b/src/components/Spinner/index.tsx
@@ -7,6 +7,7 @@ import styles from './styles.css'
export const Spinner = () => (
+
)
diff --git a/src/components/Switch/index.tsx b/src/components/Switch/index.tsx
index ad34b2dc38..31b2306f97 100644
--- a/src/components/Switch/index.tsx
+++ b/src/components/Switch/index.tsx
@@ -18,7 +18,9 @@ export const Switch = ({
return (
)
diff --git a/src/components/Tabs/index.tsx b/src/components/Tabs/index.tsx
index 60a675d209..cd24aae256 100644
--- a/src/components/Tabs/index.tsx
+++ b/src/components/Tabs/index.tsx
@@ -19,6 +19,7 @@ const Tab: React.FC = ({
return (
-
{children}
+
)
@@ -34,6 +35,7 @@ export const Tabs: React.FC & {
return (
)
diff --git a/src/components/Tag/index.tsx b/src/components/Tag/index.tsx
index 45b5cf3993..2cf92ac5e1 100644
--- a/src/components/Tag/index.tsx
+++ b/src/components/Tag/index.tsx
@@ -1,6 +1,5 @@
import classNames from 'classnames'
import gql from 'graphql-tag'
-import _get from 'lodash/get'
import Link from 'next/link'
import { Icon, TextIcon } from '~/components'
@@ -52,31 +51,30 @@ export const Tag = ({ size = 'default', type = 'default', tag }: TagProps) => {
page: 'tagDetail',
id: tag.id
})
- const tagCount = numAbbr(_get(tag, 'articles.totalCount', 0))
+ const tagCount = numAbbr(tag.articles.totalCount || 0)
return (
- <>
-
-
-
- }
- text={tag.content}
- weight="medium"
- size={isSmall ? 'sm' : 'md'}
- spacing={isSmall ? 'xtight' : 'tight'}
- />
+
+
+
+ }
+ text={tag.content}
+ weight="medium"
+ size={isSmall ? 'sm' : 'md'}
+ spacing={isSmall ? 'xtight' : 'tight'}
+ />
- {!!tagCount && {tagCount}}
-
-
-
- >
+ {!!tagCount && {tagCount}}
+
+
+
+
)
}
diff --git a/src/components/TextIcon/index.tsx b/src/components/TextIcon/index.tsx
index 280a38ee1c..a527da2506 100644
--- a/src/components/TextIcon/index.tsx
+++ b/src/components/TextIcon/index.tsx
@@ -51,7 +51,9 @@ export const TextIcon: React.FC = ({
return (
{text || children}
+
{icon}
+
)
@@ -60,7 +62,9 @@ export const TextIcon: React.FC = ({
return (
{icon}
+
{text === undefined ? children : text}
+
)
diff --git a/src/components/Title/index.tsx b/src/components/Title/index.tsx
index 575a1a2d6b..1534523669 100644
--- a/src/components/Title/index.tsx
+++ b/src/components/Title/index.tsx
@@ -65,6 +65,7 @@ export const Title: React.FC = ({
{children}
)}
+
>
)
diff --git a/src/components/ToastHolder/index.tsx b/src/components/ToastHolder/index.tsx
index 84be25f296..20ba365aa3 100644
--- a/src/components/ToastHolder/index.tsx
+++ b/src/components/ToastHolder/index.tsx
@@ -1,5 +1,5 @@
import _filter from 'lodash/filter'
-import { FC, useState } from 'react'
+import { useState } from 'react'
import { useEventListener } from '~/components'
@@ -25,7 +25,7 @@ interface Props {
layoutClasses?: string
}
-export const ToastHolder: FC = ({
+export const ToastHolder: React.FC = ({
layoutClasses = 'l-col-4 l-col-md-6 l-offset-md-1 l-col-lg-8 l-offset-lg-2'
}) => {
const [toasts, setToasts] = useState([])
@@ -49,17 +49,16 @@ export const ToastHolder: FC = ({
useEventListener('removeToast', remove)
return (
- <>
-
-
-
- {toasts.map(toast => (
-
- ))}
-
+
+
+
+ {toasts.map(toast => (
+
+ ))}
+
- >
+
)
}
diff --git a/src/components/TransactionDigest/AppreciationReceived/index.tsx b/src/components/TransactionDigest/AppreciationReceived/index.tsx
index abb68713b7..b83debaa16 100644
--- a/src/components/TransactionDigest/AppreciationReceived/index.tsx
+++ b/src/components/TransactionDigest/AppreciationReceived/index.tsx
@@ -77,6 +77,7 @@ const AppreciationReceived = ({
+
)
diff --git a/src/components/TransactionDigest/AppreciationSent/index.tsx b/src/components/TransactionDigest/AppreciationSent/index.tsx
index 6aa11a5f4d..b4da72e3af 100644
--- a/src/components/TransactionDigest/AppreciationSent/index.tsx
+++ b/src/components/TransactionDigest/AppreciationSent/index.tsx
@@ -65,6 +65,7 @@ const AppreciationSent = ({ tx }: { tx: AppreciationSentTransaction }) => {
+
)
diff --git a/src/components/UserDigest/FullDesc/index.tsx b/src/components/UserDigest/FullDesc/index.tsx
index 794182d2e5..d6fa8d9802 100644
--- a/src/components/UserDigest/FullDesc/index.tsx
+++ b/src/components/UserDigest/FullDesc/index.tsx
@@ -4,6 +4,7 @@ import Link from 'next/link'
import { Icon, TextIcon } from '~/components'
import { Avatar } from '~/components/Avatar'
+import UnblockButton from '~/components/Button/BlockUser/Unblock'
import { FollowButton } from '~/components/Button/Follow'
import { numAbbr, toPath } from '~/common/utils'
@@ -49,12 +50,14 @@ const FullDesc = ({
user,
nameSize = 'default',
readonly,
- appreciations
+ appreciations,
+ showUnblock
}: {
user: UserDigestFullDescUser
nameSize?: 'default' | 'small'
readonly?: boolean
appreciations?: number
+ showUnblock?: boolean
}) => {
const showAppreciations = appreciations && appreciations > 0
const nameSizeClasses = classNames({
@@ -87,11 +90,12 @@ const FullDesc = ({
{showAppreciations &&
}
-
+ {!showUnblock &&
}
-
+ {showUnblock && }
+ {!showUnblock && }
@@ -102,6 +106,7 @@ const FullDesc = ({
+
>
)
@@ -119,10 +124,12 @@ FullDesc.fragments = {
...AvatarUser
...FollowStateUser
...FollowButtonUser
+ ...UnblockButtonUser
}
${Avatar.fragments.user}
${FollowButton.State.fragments.user}
${FollowButton.fragments.user}
+ ${UnblockButton.fragments.user}
`
}
diff --git a/src/components/UserDigest/Mini/index.tsx b/src/components/UserDigest/Mini/index.tsx
index a665401889..ef16f2b225 100644
--- a/src/components/UserDigest/Mini/index.tsx
+++ b/src/components/UserDigest/Mini/index.tsx
@@ -68,6 +68,7 @@ const Mini = ({
+
>
)
diff --git a/src/components/UserProfile/Cover.tsx b/src/components/UserProfile/Cover.tsx
index f84374a906..31215c851f 100644
--- a/src/components/UserProfile/Cover.tsx
+++ b/src/components/UserProfile/Cover.tsx
@@ -8,6 +8,7 @@ export const ProfileCover = ({ cover }: { cover: string | null }) => (
className="profile-cover"
style={{ backgroundImage: `url(${cover || IMAGE_COVER})` }}
/>
+
>
)
diff --git a/src/components/UserProfile/DropdownActions/index.tsx b/src/components/UserProfile/DropdownActions/index.tsx
new file mode 100644
index 0000000000..4fd4965fb1
--- /dev/null
+++ b/src/components/UserProfile/DropdownActions/index.tsx
@@ -0,0 +1,63 @@
+import gql from 'graphql-tag'
+import { useState } from 'react'
+
+import { Dropdown, Icon, Menu, PopperInstance } from '~/components'
+import BlockUserButton from '~/components/Button/BlockUser/Dropdown'
+
+import ICON_MORE_SMALL from '~/static/icons/more-small.svg?sprite'
+
+import { DropdownActionsUser } from './__generated__/DropdownActionsUser'
+
+const fragments = {
+ user: gql`
+ fragment DropdownActionsUser on User {
+ id
+ ...BlockUser
+ }
+ ${BlockUserButton.fragments.user}
+ `
+}
+
+const DropdownActions = ({ user }: { user: DropdownActionsUser }) => {
+ const [shown, setShown] = useState(false)
+ const [instance, setInstance] = useState
(null)
+ const hideDropdown = () => {
+ if (!instance) {
+ return
+ }
+ instance.hide()
+ }
+
+ return (
+
+
+
+
+
+ }
+ trigger="click"
+ onCreate={setInstance}
+ onShown={() => setShown(true)}
+ placement="bottom-end"
+ zIndex={301}
+ >
+
+
+
+
+ )
+}
+
+DropdownActions.fragments = fragments
+
+export default DropdownActions
diff --git a/src/components/UserProfile/EditProfileButton.tsx b/src/components/UserProfile/EditProfileButton.tsx
index 2a15becaa8..e2e1ca4f56 100644
--- a/src/components/UserProfile/EditProfileButton.tsx
+++ b/src/components/UserProfile/EditProfileButton.tsx
@@ -1,5 +1,3 @@
-import _get from 'lodash/get'
-
import { Icon, TextIcon, Translate } from '~/components'
import ICON_SETTINGS from '~/static/icons/settings.svg?sprite'
diff --git a/src/components/UserProfile/index.tsx b/src/components/UserProfile/index.tsx
index 751be3e7ff..20ed604932 100644
--- a/src/components/UserProfile/index.tsx
+++ b/src/components/UserProfile/index.tsx
@@ -1,17 +1,18 @@
import classNames from 'classnames'
import gql from 'graphql-tag'
-import { get, some } from 'lodash'
+import _get from 'lodash/get'
+import _some from 'lodash/some'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useContext, useState } from 'react'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import { Avatar, Placeholder, Tooltip, Translate } from '~/components'
import { FollowButton } from '~/components/Button/Follow'
-import { Query } from '~/components/GQL'
+import ShareButton from '~/components/Button/Share'
+import ShareModal from '~/components/Button/Share/ShareModal'
import { Icon } from '~/components/Icon'
-import ShareButton from '~/components/ShareButton'
-import ShareModal from '~/components/ShareButton/ShareModal'
+import Throw404 from '~/components/Throw404'
import { UserProfileEditor } from '~/components/UserProfileEditor'
import { ViewerContext } from '~/components/Viewer'
@@ -19,10 +20,11 @@ import { TEXT } from '~/common/enums'
import { getQuery, numAbbr, toPath } from '~/common/utils'
import ICON_SEED_BADGE from '~/static/icons/early-user-badge.svg?sprite'
-import Throw404 from '../Throw404'
+import { MeProfileUser } from './__generated__/MeProfileUser'
import { UserProfileUser } from './__generated__/UserProfileUser'
import Cover from './Cover'
import Description from './Description'
+import DropdownActions from './DropdownActions'
import EditProfileButton from './EditProfileButton'
import styles from './styles.css'
@@ -50,9 +52,11 @@ const fragments = {
}
...AvatarUser
...FollowButtonUser @skip(if: $isMe)
+ ...DropdownActionsUser
}
${Avatar.fragments.user}
${FollowButton.fragments.user}
+ ${DropdownActions.fragments.user}
`
}
@@ -86,17 +90,14 @@ const SeedBadge = () => (
)
-const CoverContainer = ({ children }: any) => (
- <>
-
-
-
+const CoverContainer: React.FC = ({ children }) => (
+
+
+
- >
+
)
-type UserProfileResultType = QueryResult & { data: UserProfileUser }
-
const BaseUserProfile = () => {
const router = useRouter()
const viewer = useContext(ViewerContext)
@@ -110,154 +111,183 @@ const BaseUserProfile = () => {
inactive: isMe && viewer.isInactive
})
+ const { data, loading } = useQuery(
+ isMe ? ME_PROFILE : USER_PROFILE,
+ {
+ variables: isMe ? {} : { userName }
+ }
+ )
+ const user = isMe ? _get(data, 'viewer') : _get(data, 'user')
+
+ if (loading) {
+ return (
+
+ )
+ }
+
+ if (isMe && editing) {
+ return (
+
+ )
+ }
+
+ if (!user) {
+ return
+ }
+
+ const userFollowersPath = toPath({
+ page: 'userFollowers',
+ userName: user.userName
+ })
+ const userFolloweesPath = toPath({
+ page: 'userFollowees',
+ userName: user.userName
+ })
+ const badges = user.info.badges || []
+ const hasSeedBadge = _some(badges, { type: 'seed' })
+ const profileCover = user.info.profileCover || ''
+
return (
-
- {({ data, loading, error }: UserProfileResultType) => {
- if (loading) {
- return (
-
-
-
- )
- }
-
- if (isMe && editing) {
- return (
-
- )
- }
-
- const user = isMe ? data.viewer : data.user
-
- if (!user) {
- return
- }
-
- const userFollowersPath = toPath({
- page: 'userFollowers',
- userName: user.userName
- })
- const userFolloweesPath = toPath({
- page: 'userFollowees',
- userName: user.userName
- })
- const badges = get(user, 'info.badges', [])
- const hasSeedBadge = some(badges, { type: 'seed' })
- const profileCover = get(user, 'info.profileCover', '')
-
- return (
- <>
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+ {!isMe && (
+
+
+
+
+ {!isMe && }
+
+
+
+
+
+
+
+
+ )}
+
+
+
+
+
+ {!viewer.isInactive && (
+ <>
+ {user.displayName}
+ @{user.userName}
+ {hasSeedBadge && }
+
+ {!isMe && }
+
+ >
+ )}
+
+ {viewer.isArchived && (
+
+
+
+ )}
+
+ {viewer.isFrozen && (
+
+
+
+ )}
+
+ {viewer.isBanned && (
+
+
- {!isMe && (
-
- )}
-
-
-
-
-
- {!viewer.isInactive && (
-
- {user.displayName}
- {hasSeedBadge && }
- {!isMe && }
-
- )}
- {viewer.isArchived && (
-
-
-
- )}
- {viewer.isFrozen && (
-
-
-
- )}
- {viewer.isBanned && (
-
-
-
- )}
-
-
- {isMe && !viewer.isInactive && (
-
- )}
-
-
-
-
-
-
- {!viewer.isInactive && (
-
- )}
-
-
-
+
+ )}
-
- >
- )
- }}
-
-
+
+ {isMe && !viewer.isInactive && (
+
+ )}
+
+
+ {!isMe && }
+
+
+
+
+
+ {!viewer.isInactive && (
+
+ )}
+
+
+
+
+
+
)
}
-export const UserProfile = BaseUserProfile
+export const UserProfile = () => (
+ <>
+
+
+ >
+)
diff --git a/src/components/UserProfile/styles.css b/src/components/UserProfile/styles.css
index eb348034ef..799ee61aaf 100644
--- a/src/components/UserProfile/styles.css
+++ b/src/components/UserProfile/styles.css
@@ -75,31 +75,35 @@
@mixin flex-start-space-between;
}
-.name {
- font-size: var(--font-size-lg);
- font-weight: var(--font-weight-bold);
+.basic {
+ @mixin flex-center-all;
+ margin-left: var(--spacing-x-tight);
- @media (--sm-up) {
- font-size: var(--font-size-xl);
- line-height: 1.25;
+ & .name {
+ font-size: var(--font-size-lg);
+ font-weight: var(--font-weight-bold);
+
+ @media (--sm-up) {
+ font-size: var(--font-size-xl);
+ line-height: 1.25;
+ }
}
- & > span {
- display: inline-flex;
- align-items: center;
+ & .username {
margin-left: var(--spacing-x-tight);
- & :first-child {
- margin-left: 0;
- }
+ font-size: var(--font-size-sm);
+ font-weight: var(--font-weight-normal);
+ color: var(--color-grey);
+ }
- & :global(> *) {
- margin-left: var(--spacing-x-tight);
- }
+ & :first-child {
+ margin-left: 0;
+ }
- & :gloabl(> .btn) {
- margin-top: 1px;
- }
+ & :global(> * + *) {
+ font-size: 0;
+ margin-left: var(--spacing-x-tight);
}
}
@@ -108,15 +112,27 @@
top: -12px; /* position hack in small device */
right: 0;
+ & :global(button) {
+ margin-left: var(--spacing-tight);
+ }
+
+ & .follows {
+ & :global(button) {
+ margin: 0;
+ }
+
+ & .follow-state {
+ display: inline-block;
+ margin-top: var(--spacing-tight);
+ line-height: 1;
+ }
+ }
+
@media (--sm-up) {
position: initial;
top: initial;
right: initial;
}
-
- & > :global(*) + :global(*) {
- margin-left: var(--spacing-tight);
- }
}
.description {
diff --git a/src/components/UserProfileEditor/index.tsx b/src/components/UserProfileEditor/index.tsx
index 1a88ed9881..e0a8ede1fd 100644
--- a/src/components/UserProfileEditor/index.tsx
+++ b/src/components/UserProfileEditor/index.tsx
@@ -1,7 +1,7 @@
-import { withFormik } from 'formik'
+import { FormikProps, withFormik } from 'formik'
import gql from 'graphql-tag'
import _isEmpty from 'lodash/isEmpty'
-import { FC, useContext } from 'react'
+import { useContext } from 'react'
import { Button } from '~/components/Button'
import {
@@ -9,7 +9,7 @@ import {
ProfileCoverUploader
} from '~/components/FileUploader'
import { Form } from '~/components/Form'
-import { Mutation } from '~/components/GQL'
+import { useMutation } from '~/components/GQL'
import { Icon } from '~/components/Icon'
import IconSpinner from '~/components/Icon/Spinner'
import { LanguageContext, Translate } from '~/components/Language'
@@ -19,13 +19,19 @@ import { TEXT } from '~/common/enums'
import { isValidDisplayName, translate } from '~/common/utils'
import ICON_SAVE from '~/static/icons/write.svg?sprite'
+import { UpdateUserInfoProfile } from './__generated__/UpdateUserInfoProfile'
import styles from './styles.css'
-interface Props {
+interface FormProps {
user: { [key: string]: any }
setEditing: (value: boolean) => void
}
+interface FormValues {
+ displayName: string
+ description: string
+}
+
const UPDATE_USER_INFO = gql`
mutation UpdateUserInfoProfile($input: UpdateUserInfoInput!) {
updateUserInfo(input: $input) {
@@ -38,12 +44,14 @@ const UPDATE_USER_INFO = gql`
}
`
-export const UserProfileEditor: FC = ({ user, setEditing }) => {
+export const UserProfileEditor: React.FC = formProps => {
+ const [update] = useMutation(UPDATE_USER_INFO)
const { lang } = useContext(LanguageContext)
const viewer = useContext(ViewerContext)
+ const { user, setEditing } = formProps
- const validateDisplayName = (value: string, language: string) => {
- let result: any
+ const validateDisplayName = (value: string) => {
+ let result
if (!value) {
result = {
zh_hant: TEXT.zh_hant.required,
@@ -56,12 +64,12 @@ export const UserProfileEditor: FC = ({ user, setEditing }) => {
}
}
if (result) {
- return translate({ ...result, lang: language })
+ return translate({ ...result, lang })
}
}
- const validateDescription = (value: string, language: Language) => {
- let result: any
+ const validateDescription = (value: string) => {
+ let result
if (value && value.length > 200) {
result = {
zh_hant: `已超過 200 字,目前 ${value.length} 字`,
@@ -69,11 +77,11 @@ export const UserProfileEditor: FC = ({ user, setEditing }) => {
}
}
if (result) {
- return translate({ ...result, lang: language })
+ return translate({ ...result, lang })
}
}
- const BaseForm = ({
+ const InnerForm = ({
values,
errors,
touched,
@@ -81,9 +89,7 @@ export const UserProfileEditor: FC = ({ user, setEditing }) => {
handleBlur,
handleChange,
handleSubmit
- }: {
- [key: string]: any
- }) => {
+ }: FormikProps) => {
const displayNamePlaceholder = translate({
zh_hant: '輸入姓名',
zh_hans: '输入姓名',
@@ -111,77 +117,76 @@ export const UserProfileEditor: FC = ({ user, setEditing }) => {
})
return (
- <>
-
-
-
-
-
- ) : (
-
- )
- }
- >
- {save}
-
- setEditing(false)}
- >
-
-
-
-
+
+
+
+
+
+ ) : (
+
+ )
+ }
+ >
+ {save}
+
+ setEditing(false)}
+ >
+
+
+
+
- >
+
)
}
- const MainForm: any = withFormik({
+ const MainForm = withFormik({
mapPropsToValues: () => ({
displayName: user.displayName,
description: user.info.description
}),
validate: ({ displayName, description }) => {
- const inInvalidDisplayName = validateDisplayName(displayName, lang)
- const isInvalidDescription = validateDescription(description, lang)
+ const inInvalidDisplayName = validateDisplayName(displayName)
+ const isInvalidDescription = validateDescription(description)
const errors = {
...(inInvalidDisplayName ? { displayName: inInvalidDisplayName } : {}),
...(isInvalidDescription ? { description: isInvalidDescription } : {})
@@ -189,27 +194,22 @@ export const UserProfileEditor: FC = ({ user, setEditing }) => {
return errors
},
- handleSubmit: (values, { props, setSubmitting }: any) => {
+ handleSubmit: async (values, { setSubmitting }) => {
const { displayName, description } = values
- const { submitAction } = props
- if (!submitAction) {
- return
+
+ try {
+ await update({ variables: { input: { displayName, description } } })
+
+ if (setEditing) {
+ setEditing(false)
+ }
+ } catch (error) {
+ // TODO: Handle error
}
- submitAction({ variables: { input: { displayName, description } } })
- .then(({ data }: any) => {
- if (setEditing) {
- setEditing(false)
- }
- })
- .catch((result: any) => {
- // TODO: Handle error
- })
- .finally(() => {
- setSubmitting(false)
- })
+ setSubmitting(false)
}
- })(BaseForm)
+ })(InnerForm)
return (
<>
@@ -218,18 +218,18 @@ export const UserProfileEditor: FC = ({ user, setEditing }) => {
+
-
- {(update: any) => }
-
+
+
>
)
diff --git a/src/components/Viewer/index.tsx b/src/components/Viewer/index.tsx
index afa5b789b8..9bb4c6aa58 100644
--- a/src/components/Viewer/index.tsx
+++ b/src/components/Viewer/index.tsx
@@ -1,6 +1,5 @@
import * as Sentry from '@sentry/browser'
import gql from 'graphql-tag'
-import _get from 'lodash/get'
import React from 'react'
import { ViewerUser } from './__generated__/ViewerUser'
@@ -41,12 +40,13 @@ type Viewer = ViewerUser & {
isOnboarding: boolean
isInactive: boolean
isAdmin: boolean
+ shouldSetupLikerID: boolean
}
export const processViewer = (viewer: ViewerUser): Viewer => {
const isAuthed = !!viewer.id
- const state = _get(viewer, 'status.state')
- const role = _get(viewer, 'status.role')
+ const state = viewer && viewer.status && viewer.status.state
+ const role = viewer && viewer.status && viewer.status.role
const isActive = state === 'active'
const isFrozen = state === 'frozen'
const isBanned = state === 'banned'
@@ -54,13 +54,14 @@ export const processViewer = (viewer: ViewerUser): Viewer => {
const isOnboarding = state === 'onboarding'
const isInactive = isAuthed && (isFrozen || isBanned || isArchived)
const isAdmin = role === 'admin'
+ const shouldSetupLikerID = isOnboarding || !viewer.likerId
// Add user info for Sentry
Sentry.configureScope((scope: any) => {
scope.setUser({
id: viewer.id,
role,
- language: _get(viewer, 'settings.language')
+ language: viewer.settings.language
})
scope.setTag('source', 'web')
})
@@ -74,7 +75,8 @@ export const processViewer = (viewer: ViewerUser): Viewer => {
isFrozen,
isOnboarding,
isInactive,
- isAdmin
+ isAdmin,
+ shouldSetupLikerID
}
}
diff --git a/src/components/index.tsx b/src/components/index.tsx
index ddf98ea108..018e52e24b 100644
--- a/src/components/index.tsx
+++ b/src/components/index.tsx
@@ -2,7 +2,6 @@ export * from './Head'
export * from './Layout'
export * from './GlobalHeader'
export * from './GlobalStyles'
-export * from './Responsive'
export * from './Placeholder'
export * from './Spinner'
export * from './DateTime'
@@ -38,4 +37,3 @@ export * from './Tabs'
export * from './UserProfile'
export * from './DraftDigest'
export * from './CommentDigest'
-export * from './Drawer'
diff --git a/src/pages/MeSettingsBlocked.tsx b/src/pages/MeSettingsBlocked.tsx
new file mode 100644
index 0000000000..10e383a3a4
--- /dev/null
+++ b/src/pages/MeSettingsBlocked.tsx
@@ -0,0 +1,9 @@
+import MeSettingsBlocked from '~/views/Me/Settings/Blocked'
+
+import { Protected } from '~/components/Protected'
+
+export default () => (
+
+
+
+)
diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx
index 010d86848b..3803f3564f 100644
--- a/src/pages/_app.tsx
+++ b/src/pages/_app.tsx
@@ -1,11 +1,10 @@
import * as Sentry from '@sentry/browser'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { ApolloClient } from 'apollo-client'
-import gql from 'graphql-tag'
import App from 'next/app'
import getConfig from 'next/config'
import React from 'react'
-import { ApolloProvider, QueryResult } from 'react-apollo'
+import { ApolloProvider } from 'react-apollo'
import {
AnalyticsProvider,
@@ -14,22 +13,9 @@ import {
ModalProvider
} from '~/components'
import ErrorBoundary from '~/components/ErrorBoundary'
-import { Query } from '~/components/GQL'
import withApollo from '~/common/utils/withApollo'
-import { RootQuery } from './__generated__/RootQuery'
-
-const ROOT_QUERY = gql`
- query RootQuery {
- viewer {
- id
- ...LayoutUser
- }
- }
- ${Layout.fragments.user}
-`
-
// start Sentry
const {
publicRuntimeConfig: { SENTRY_DSN }
@@ -47,21 +33,10 @@ class MattersApp extends App<{ apollo: ApolloClient }> {
-
- {({
- data,
- loading,
- error
- }: QueryResult & { data: RootQuery }) => (
-
-
-
- )}
-
+
+
+
+
diff --git a/src/views/ArticleDetail/Collection/CollectionList.tsx b/src/views/ArticleDetail/Collection/CollectionList.tsx
index e45d7d25e8..0fd43a6e66 100644
--- a/src/views/ArticleDetail/Collection/CollectionList.tsx
+++ b/src/views/ArticleDetail/Collection/CollectionList.tsx
@@ -1,10 +1,10 @@
import gql from 'graphql-tag'
import _get from 'lodash/get'
import _uniq from 'lodash/uniq'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import { ArticleDigest, Icon, Spinner, TextIcon, Translate } from '~/components'
-import { Query } from '~/components/GQL'
+import { QueryError } from '~/components/GQL'
import articleFragments from '~/components/GQL/fragments/article'
import { ANALYTICS_EVENTS, FEED_TYPE, TEXT } from '~/common/enums'
@@ -41,115 +41,114 @@ const CollectionList = ({
article: ArticleDetail_article
setEditing: (editing: boolean) => void
canEdit?: boolean
-}) => (
-
- {({
- data,
- loading,
- error,
- fetchMore
- }: QueryResult & { data: CollectionListTypes }) => {
- if (loading) {
- return
- }
+}) => {
+ const { data, loading, error, fetchMore } = useQuery(
+ COLLECTION_LIST,
+ {
+ variables: { mediaHash: article.mediaHash, first: 10 }
+ }
+ )
+
+ const connectionPath = 'article.collection'
+ const { edges, pageInfo, totalCount } =
+ (data && data.article && data.article.collection) || {}
+
+ if (loading) {
+ return
+ }
- const path = 'article.collection'
- const { edges, pageInfo, totalCount } = _get(data, path, {})
- const loadRest = () =>
- fetchMore({
- variables: {
- mediaHash: article.mediaHash,
- after: pageInfo.endCursor,
- first: null
- },
- updateQuery: (previousResult, { fetchMoreResult }) =>
- mergeConnections({
- oldData: previousResult,
- newData: fetchMoreResult,
- path
- })
+ if (error) {
+ return
+ }
+
+ if (!edges || !pageInfo) {
+ return null
+ }
+
+ const loadRest = () =>
+ fetchMore({
+ variables: {
+ mediaHash: article.mediaHash,
+ after: pageInfo.endCursor,
+ first: null
+ },
+ updateQuery: (previousResult, { fetchMoreResult }) =>
+ mergeConnections({
+ oldData: previousResult,
+ newData: fetchMoreResult,
+ path: connectionPath
})
+ })
+
+ if ((totalCount || 0) <= 0 && canEdit) {
+ return (
+ setEditing(true)}>
+
+ }
+ spacing="xtight"
+ color="green"
+ size="sm"
+ >
+
+
+
+ )
+ }
- if (totalCount <= 0 && canEdit) {
- return (
- setEditing(true)}>
+ return (
+ <>
+
+ {edges.map(({ node, cursor }, i) => (
+ -
+ analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
+ type: FEED_TYPE.COLLECTION,
+ location: i
+ })
+ }
+ >
+
+
+ ))}
+
+
+ {pageInfo.hasNextPage && (
+
+
}
- spacing="xtight"
color="green"
size="sm"
+ textPlacement="left"
+ spacing="xxtight"
>
-
+
- )
- }
-
- return (
- <>
-
- {edges.map(
- ({ node, cursor }: { node: any; cursor: any }, i: number) => (
- -
- analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
- type: FEED_TYPE.COLLECTION,
- location: i
- })
- }
- >
-
-
- )
- )}
-
-
- {pageInfo.hasNextPage && (
-
-
-
- }
- color="green"
- size="sm"
- textPlacement="left"
- spacing="xxtight"
- >
-
-
-
-
- )}
+
+ )}
-
- >
- )
- }}
-
-)
+
+ >
+ )
+}
export default CollectionList
diff --git a/src/views/ArticleDetail/Collection/EditButton.tsx b/src/views/ArticleDetail/Collection/EditButton.tsx
index 55935994c5..e33a5922b4 100644
--- a/src/views/ArticleDetail/Collection/EditButton.tsx
+++ b/src/views/ArticleDetail/Collection/EditButton.tsx
@@ -1,6 +1,5 @@
import classNames from 'classnames'
import gql from 'graphql-tag'
-import _get from 'lodash/get'
import _uniq from 'lodash/uniq'
import { useContext } from 'react'
@@ -11,7 +10,7 @@ import {
TextIcon,
Translate
} from '~/components'
-import { Mutation } from '~/components/GQL'
+import { useMutation } from '~/components/GQL'
import articleFragments from '~/components/GQL/fragments/article'
import IconSpinner from '~/components/Icon/Spinner'
@@ -21,6 +20,7 @@ import ICON_EDIT from '~/static/icons/collection-edit.svg?sprite'
import ICON_SAVE from '~/static/icons/pen.svg?sprite'
import { ArticleDetail_article } from '../__generated__/ArticleDetail'
+import { EditorSetCollection } from './__generated__/EditorSetCollection'
import styles from './styles.css'
/**
@@ -62,6 +62,9 @@ const EditButton = ({
setEditing: any
editingArticles: string[]
}) => {
+ const [setCollection, { loading }] = useMutation(
+ EDITOR_SET_COLLECTION
+ )
const { lang } = useContext(LanguageContext)
const editButtonClass = classNames({
'edit-button': true
@@ -75,88 +78,80 @@ const EditButton = ({
+
)
}
return (
-
- {(setCollection: any, { loading }: any) => (
-
- setEditing(false)}
- >
-
-
+
+ setEditing(false)}
+ >
+
+
- : }
- size="small"
- disabled={!!loading}
- onClick={async () => {
- try {
- await setCollection({
- variables: {
- id: article.id,
- collection: _uniq(
- editingArticles.map((item: any) => item.id)
- ),
- first: null
- }
- })
- window.dispatchEvent(
- new CustomEvent(ADD_TOAST, {
- detail: {
- color: 'green',
- content: translate({
- zh_hant: '關聯已更新',
- zh_hans: '关联已更新',
- lang
- }),
- closeButton: true,
- duration: 2000
- }
- })
- )
- } catch (error) {
- window.dispatchEvent(
- new CustomEvent(ADD_TOAST, {
- detail: {
- color: 'red',
- content: translate({
- zh_hant: '關聯失敗',
- zh_hans: '关联失敗',
- lang
- }),
- clostButton: true,
- duration: 2000
- }
- })
- )
+ : }
+ size="small"
+ disabled={!!loading}
+ onClick={async () => {
+ try {
+ await setCollection({
+ variables: {
+ id: article.id,
+ collection: _uniq(editingArticles.map((item: any) => item.id)),
+ first: null
}
- setEditing(false)
- }}
- outlineColor="green"
- >
-
-
+ })
+ window.dispatchEvent(
+ new CustomEvent(ADD_TOAST, {
+ detail: {
+ color: 'green',
+ content: translate({
+ zh_hant: '關聯已更新',
+ zh_hans: '关联已更新',
+ lang
+ }),
+ closeButton: true,
+ duration: 2000
+ }
+ })
+ )
+ } catch (error) {
+ window.dispatchEvent(
+ new CustomEvent(ADD_TOAST, {
+ detail: {
+ color: 'red',
+ content: translate({
+ zh_hant: '關聯失敗',
+ zh_hans: '关联失敗',
+ lang
+ }),
+ clostButton: true,
+ duration: 2000
+ }
+ })
+ )
+ }
+ setEditing(false)
+ }}
+ outlineColor="green"
+ >
+
+
-
-
- )}
-
+
+
)
}
diff --git a/src/views/ArticleDetail/Collection/EditingList.tsx b/src/views/ArticleDetail/Collection/EditingList.tsx
index d461929b62..61fec49746 100644
--- a/src/views/ArticleDetail/Collection/EditingList.tsx
+++ b/src/views/ArticleDetail/Collection/EditingList.tsx
@@ -1,16 +1,18 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
import _uniqBy from 'lodash/uniqBy'
import dynamic from 'next/dynamic'
import { useEffect } from 'react'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import { Spinner } from '~/components'
-import { Query } from '~/components/GQL'
+import { QueryError } from '~/components/GQL'
import articleFragments from '~/components/GQL/fragments/article'
import { ArticleDetail_article } from '../__generated__/ArticleDetail'
-import { EditorCollection } from './__generated__/EditorCollection'
+import {
+ EditorCollection,
+ EditorCollection_article_collection_edges_node
+} from './__generated__/EditorCollection'
import styles from './styles.css'
const EDITOR_COLLECTION = gql`
@@ -36,41 +38,43 @@ const EditingList = ({
setEditingArticles
}: {
article: ArticleDetail_article
- editingArticles: any[]
- setEditingArticles: (articles: any[]) => void
-}) => (
-
- {({
- data,
- loading,
- error,
- fetchMore
- }: QueryResult & { data: EditorCollection }) => {
- if (loading) {
- return
- }
+ editingArticles: EditorCollection_article_collection_edges_node[]
+ setEditingArticles: (
+ articles: EditorCollection_article_collection_edges_node[]
+ ) => void
+}) => {
+ const { data, loading, error } = useQuery(
+ EDITOR_COLLECTION,
+ {
+ variables: { mediaHash: article.mediaHash, first: null }
+ }
+ )
+ const edges = (data && data.article && data.article.collection.edges) || []
- const { edges } = _get(data, 'article.collection', {})
+ // init `editingArticles` when network collection is received
+ const edgesKeys = edges.map(({ node }) => node.id).join(',') || ''
+ useEffect(() => {
+ setEditingArticles(edges.map(({ node }) => node))
+ }, [edgesKeys])
- useEffect(() => {
- setEditingArticles(edges.map(({ node }: { node: any }) => node))
- }, [])
+ if (loading) {
+ return
+ }
- return (
-
- setEditingArticles(_uniqBy(articles, 'id'))}
- />
+ if (error) {
+ return
+ }
-
-
- )
- }}
-
-)
+ return (
+
+ setEditingArticles(_uniqBy(articles, 'id'))}
+ />
+
+
+
+ )
+}
export default EditingList
diff --git a/src/views/ArticleDetail/Collection/index.tsx b/src/views/ArticleDetail/Collection/index.tsx
index 58454a6bdd..5494b16c34 100644
--- a/src/views/ArticleDetail/Collection/index.tsx
+++ b/src/views/ArticleDetail/Collection/index.tsx
@@ -1,4 +1,3 @@
-import _get from 'lodash/get'
import _uniq from 'lodash/uniq'
import { useState } from 'react'
@@ -27,6 +26,7 @@ const Collection: React.FC<{
}> = ({ article, collectionCount, canEdit }) => {
const [editing, setEditing] = useState(false)
const [editingArticles, setEditingArticles] = useState([])
+
return (
diff --git a/src/views/ArticleDetail/Content/index.tsx b/src/views/ArticleDetail/Content/index.tsx
index cd6db3ff4d..3e81d25d62 100644
--- a/src/views/ArticleDetail/Content/index.tsx
+++ b/src/views/ArticleDetail/Content/index.tsx
@@ -2,13 +2,22 @@ import gql from 'graphql-tag'
import { useEffect, useState } from 'react'
import { Waypoint } from 'react-waypoint'
-import { Mutation } from '~/components/GQL'
+import { useMutation } from '~/components/GQL'
import { ANALYTICS_EVENTS } from '~/common/enums'
import styles from '~/common/styles/utils/content.article.css'
import { analytics, initAudioPlayers } from '~/common/utils'
import { ContentArticle } from './__generated__/ContentArticle'
+import { ReadArticle } from './__generated__/ReadArticle'
+
+const READ_ARTICLE = gql`
+ mutation ReadArticle($id: ID!) {
+ readArticle(input: { id: $id }) {
+ id
+ }
+ }
+`
const fragments = {
article: gql`
@@ -20,6 +29,9 @@ const fragments = {
}
const Content = ({ article }: { article: ContentArticle }) => {
+ const [read] = useMutation(READ_ARTICLE)
+ const [trackedFinish, setTrackedFinish] = useState(false)
+ const [trackedRead, setTrackedRead] = useState(false)
const { id } = article
useEffect(() => {
@@ -56,9 +68,6 @@ const Content = ({ article }: { article: ContentArticle }) => {
initAudioPlayers()
})
- const [trackedFinish, setTrackedFinish] = useState(false)
- const [trackedRead, setTrackedRead] = useState(false)
-
const FireOnMount = ({ fn }: { fn: () => void }) => {
useEffect(() => {
fn()
@@ -67,43 +76,34 @@ const Content = ({ article }: { article: ContentArticle }) => {
}
return (
-
+ {
+ if (!trackedRead) {
+ read({ variables: { id } })
+ setTrackedRead(true)
}
- }
- `}
- >
- {(read: any) => (
- <>
- {
- if (!trackedRead) {
- read({ variables: { id } })
- setTrackedRead(true)
- }
- }}
- />
-
- {
- if (!trackedFinish) {
- analytics.trackEvent(ANALYTICS_EVENTS.FINISH_ARTICLE, {
- entrance: id
- })
- setTrackedFinish(true)
- }
- }}
- />
-
- >
- )}
-
+ }}
+ />
+
+
+
+ {
+ if (!trackedFinish) {
+ analytics.trackEvent(ANALYTICS_EVENTS.FINISH_ARTICLE, {
+ entrance: id
+ })
+ setTrackedFinish(true)
+ }
+ }}
+ />
+
+
+ >
)
}
diff --git a/src/views/ArticleDetail/RelatedArticles/index.tsx b/src/views/ArticleDetail/RelatedArticles/index.tsx
index 39250ad69f..dda66d493c 100644
--- a/src/views/ArticleDetail/RelatedArticles/index.tsx
+++ b/src/views/ArticleDetail/RelatedArticles/index.tsx
@@ -1,5 +1,4 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
import { ArticleDigest } from '~/components'
@@ -27,7 +26,8 @@ const fragments = {
}
const RelatedArticles = ({ article }: { article: RelatedArticlesType }) => {
- const edges = _get(article, 'relatedArticles.edges')
+ const edges = article.relatedArticles.edges
+
if (!edges || edges.length <= 0) {
return null
}
@@ -36,23 +36,22 @@ const RelatedArticles = ({ article }: { article: RelatedArticlesType }) => {
- {edges.map(
- ({ node, cursor }: { node: any; cursor: any }, i: number) => (
-
- analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
- type: FEED_TYPE.RELATED_ARTICLE,
- location: i,
- entrance: article.id
- })
- }
- >
-
-
- )
- )}
+ {edges.map(({ node, cursor }, i) => (
+
+ analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
+ type: FEED_TYPE.RELATED_ARTICLE,
+ location: i,
+ entrance: article.id
+ })
+ }
+ >
+
+
+ ))}
+
)
diff --git a/src/views/ArticleDetail/Responses/FeaturedComments.tsx b/src/views/ArticleDetail/Responses/FeaturedComments.tsx
index 0a3c931326..1dec8b0c1a 100644
--- a/src/views/ArticleDetail/Responses/FeaturedComments.tsx
+++ b/src/views/ArticleDetail/Responses/FeaturedComments.tsx
@@ -1,18 +1,15 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
-import _has from 'lodash/has'
-import _merge from 'lodash/merge'
import { useRouter } from 'next/router'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import { LoadMore, Spinner, Translate } from '~/components'
import { CommentDigest } from '~/components/CommentDigest'
-import { Query } from '~/components/GQL'
import commentFragments from '~/components/GQL/fragments/comment'
import { TEXT } from '~/common/enums'
import { filterComments, getQuery, mergeConnections } from '~/common/utils'
+import { ArticleFeaturedComments } from './__generated__/ArticleFeaturedComments'
import styles from './styles.css'
const FEATURED_COMMENTS = gql`
@@ -46,80 +43,66 @@ const FEATURED_COMMENTS = gql`
const FeaturedComments = () => {
const router = useRouter()
const mediaHash = getQuery({ router, key: 'mediaHash' })
+ const { data, loading, fetchMore } = useQuery(
+ FEATURED_COMMENTS,
+ {
+ variables: { mediaHash },
+ notifyOnNetworkStatusChange: true
+ }
+ )
- if (!mediaHash) {
- return null
+ const connectionPath = 'article.featuredComments'
+ const { edges, pageInfo } =
+ (data && data.article && data.article.featuredComments) || {}
+ const comments = filterComments((edges || []).map(({ node }) => node))
+
+ if (loading) {
+ return
}
- return (
-
- {({ data, loading, fetchMore }: QueryResult) => {
- if (!data || !data.article) {
- return
- }
+ if (!edges || edges.length <= 0 || !pageInfo || comments.length <= 0) {
+ return null
+ }
- const connectionPath = 'article.featuredComments'
- const { edges, pageInfo } = _get(data, connectionPath, {
- edges: {},
- pageInfo: {}
+ const loadMore = () => {
+ return fetchMore({
+ variables: {
+ after: pageInfo.endCursor
+ },
+ updateQuery: (previousResult, { fetchMoreResult }) =>
+ mergeConnections({
+ oldData: previousResult,
+ newData: fetchMoreResult,
+ path: connectionPath
})
+ })
+ }
- const loadMore = () => {
- return fetchMore({
- variables: {
- after: pageInfo.endCursor
- },
- updateQuery: (previousResult, { fetchMoreResult }) =>
- mergeConnections({
- oldData: previousResult,
- newData: fetchMoreResult,
- path: connectionPath
- })
- })
- }
-
- const comments = filterComments(
- (edges || []).map(({ node }: { node: any }) => node)
- )
-
- if (!comments || comments.length <= 0) {
- return null
- }
-
- return (
-
- )
- }}
-
+ return (
+
)
}
diff --git a/src/views/ArticleDetail/Responses/LatestResponses.tsx b/src/views/ArticleDetail/Responses/LatestResponses.tsx
index ef64555d91..8f0dcf8a78 100644
--- a/src/views/ArticleDetail/Responses/LatestResponses.tsx
+++ b/src/views/ArticleDetail/Responses/LatestResponses.tsx
@@ -5,13 +5,13 @@ import _has from 'lodash/has'
import _merge from 'lodash/merge'
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import { LoadMore, Spinner, Translate } from '~/components'
import { ArticleDigest } from '~/components/ArticleDigest'
import { CommentDigest } from '~/components/CommentDigest'
import EmptyResponse from '~/components/Empty/EmptyResponse'
-import { Query } from '~/components/GQL'
+import { QueryError } from '~/components/GQL'
import { ArticleDetailResponses } from '~/components/GQL/fragments/response'
import { ArticleResponses as ArticleResponsesType } from '~/components/GQL/queries/__generated__/ArticleResponses'
import ARTICLE_RESPONSES from '~/components/GQL/queries/articleResponses'
@@ -27,6 +27,7 @@ import {
unshiftConnections
} from '~/common/utils'
+import { ArticleCommentAdded } from './__generated__/ArticleCommentAdded'
import styles from './styles.css'
const RESPONSES_COUNT = 15
@@ -60,11 +61,7 @@ const LatestResponses = () => {
const router = useRouter()
const mediaHash = getQuery({ router, key: 'mediaHash' })
const [articleOnlyMode, setArticleOnlyMode] = useState(false)
- const [storedCursor, setStoredCursor] = useState(null)
-
- if (!mediaHash) {
- return
- }
+ const [storedCursor, setStoredCursor] = useState(null)
/**
* Fragment Patterns
@@ -82,223 +79,207 @@ const LatestResponses = () => {
descendantId = fragment.split('-')[1]
}
- const queryVariables = {
- mediaHash,
- first: RESPONSES_COUNT,
- articleOnly: articleOnlyMode
- }
+ const {
+ data,
+ loading,
+ error,
+ fetchMore,
+ subscribeToMore,
+ refetch
+ } = useQuery(ARTICLE_RESPONSES, {
+ variables: {
+ mediaHash,
+ first: RESPONSES_COUNT,
+ articleOnly: articleOnlyMode
+ },
+ notifyOnNetworkStatusChange: true
+ })
+ const connectionPath = 'article.responses'
+ const article = data && data.article
+ const { edges, pageInfo } = (article && article.responses) || {}
+ const articleId = article && article.id
- return (
-
- {({
- data,
- loading,
- fetchMore,
- subscribeToMore,
- refetch
- }: QueryResult & { data: ArticleResponsesType }) => {
- if (!data || !data.article) {
- return
- }
+ const loadMore = (params?: { before: string }) => {
+ const loadBefore = (params && params.before) || null
+ const noLimit = loadBefore && pageInfo && pageInfo.endCursor
- const connectionPath = 'article.responses'
- const { edges, pageInfo } = _get(data, connectionPath, {
- edges: {},
- pageInfo: {}
+ return fetchMore({
+ variables: {
+ after: pageInfo && pageInfo.endCursor,
+ before: loadBefore,
+ first: noLimit ? null : RESPONSES_COUNT,
+ includeBefore: !!loadBefore,
+ articleOnly: articleOnlyMode
+ },
+ updateQuery: (previousResult, { fetchMoreResult }) =>
+ mergeConnections({
+ oldData: previousResult,
+ newData: fetchMoreResult,
+ path: connectionPath
})
+ })
+ }
- const loadMore = (params?: { before: string }) => {
- const loadBefore = (params && params.before) || null
- const noLimit = loadBefore && pageInfo.endCursor
- return fetchMore({
- variables: {
- after: pageInfo.endCursor,
- before: loadBefore,
- first: noLimit ? null : RESPONSES_COUNT,
- includeBefore: !!loadBefore,
- articleOnly: articleOnlyMode
- },
- updateQuery: (previousResult, { fetchMoreResult }) =>
- mergeConnections({
- oldData: previousResult,
- newData: fetchMoreResult,
- path: connectionPath
- })
- })
- }
-
- const commentCallback = () => {
- return fetchMore({
- variables: {
- before: storedCursor,
- includeBefore: false,
- articleOnly: articleOnlyMode
- },
- updateQuery: (previousResult, { fetchMoreResult }) => {
- const newEdges = _get(
- fetchMoreResult,
- `${connectionPath}.edges`,
- []
- )
- const newResponseCount = _get(
- fetchMoreResult,
- 'article.responseCount'
- )
- const oldResponseCount = _get(
- previousResult,
- 'article.responseCount'
- )
- // update if response count has changed
- if (newEdges.length === 0) {
- if (oldResponseCount !== newResponseCount) {
- return {
- ...previousResult,
- article: {
- ...previousResult.article,
- responseCount: newResponseCount
- }
- }
- }
- return previousResult
- }
+ const commentCallback = () => {
+ return fetchMore({
+ variables: {
+ before: storedCursor,
+ includeBefore: false,
+ articleOnly: articleOnlyMode
+ },
+ updateQuery: (previousResult, { fetchMoreResult }) => {
+ const newEdges = _get(fetchMoreResult, `${connectionPath}.edges`, [])
+ const newResponseCount = _get(fetchMoreResult, 'article.responseCount')
+ const oldResponseCount = _get(previousResult, 'article.responseCount')
- // update if there are new items in responses.edges
- const newResult = unshiftConnections({
- oldData: previousResult,
- newData: fetchMoreResult,
- path: connectionPath
- })
- const newStartCursor = _get(
- newResult,
- `${connectionPath}.pageInfo.startCursor`,
- null
- )
- if (newStartCursor) {
- setStoredCursor(newStartCursor)
+ // update if response count has changed
+ if (newEdges.length === 0) {
+ if (oldResponseCount !== newResponseCount) {
+ return {
+ ...previousResult,
+ article: {
+ ...previousResult.article,
+ responseCount: newResponseCount
}
- return newResult
}
- })
+ }
+ return previousResult
}
- const responses = filterResponses(
- (edges || []).map(({ node }: { node: any }) => node)
+ // update if there are new items in responses.edges
+ const newResult = unshiftConnections({
+ oldData: previousResult,
+ newData: fetchMoreResult,
+ path: connectionPath
+ })
+ const newStartCursor = _get(
+ newResult,
+ `${connectionPath}.pageInfo.startCursor`,
+ null
)
+ if (newStartCursor) {
+ setStoredCursor(newStartCursor)
+ }
+ return newResult
+ }
+ })
+ }
- // real time update with websocket
- useEffect(() => {
- if (data.article.live) {
- subscribeToMore({
- document: SUBSCRIBE_RESPONSES,
- variables: {
- id: data.article.id,
- first: edges.length,
- articleOnly: articleOnlyMode
- },
- updateQuery: (prev, { subscriptionData }) =>
- _merge(prev, {
- article: subscriptionData.data.nodeEdited
- })
- })
- }
- })
+ const responses = filterResponses((edges || []).map(({ node }) => node))
- // scroll to comment
- useEffect(() => {
- if (!fragment) {
- return
- }
+ // real time update with websocket
+ useEffect(() => {
+ if (article && edges) {
+ subscribeToMore({
+ document: SUBSCRIBE_RESPONSES,
+ variables: {
+ id: article.id,
+ first: edges.length,
+ articleOnly: articleOnlyMode
+ },
+ updateQuery: (prev, { subscriptionData }) =>
+ _merge(prev, {
+ article: subscriptionData.data.nodeEdited
+ })
+ })
+ }
+ }, [articleId])
- const jumpToFragment = () => {
- jump(`#${fragment}`, {
- offset: fragment === UrlFragments.COMMENTS ? -10 : -64
- })
- }
- const element = dom.$(`#${fragment}`)
+ // scroll to comment
+ useEffect(() => {
+ if (!fragment || !articleId) {
+ return
+ }
- if (!element) {
- loadMore({ before: parentId }).then(jumpToFragment)
- } else {
- jumpToFragment()
- }
- }, [])
+ const jumpToFragment = () => {
+ jump(`#${fragment}`, {
+ offset: fragment === UrlFragments.COMMENTS ? -10 : -64
+ })
+ }
+ const element = dom.$(`#${fragment}`)
- useEventListener(REFETCH_RESPONSES, refetch)
+ if (!element) {
+ loadMore({ before: parentId }).then(jumpToFragment)
+ } else {
+ jumpToFragment()
+ }
+ }, [articleId])
- useEffect(() => {
- if (pageInfo.startCursor) {
- setStoredCursor(pageInfo.startCursor)
- }
- }, [pageInfo.startCursor])
+ useEventListener(REFETCH_RESPONSES, refetch)
- return (
-
-
+ return (
+
+
-
- {responses.map(response => (
- -
- {_has(response, 'title') ? (
-
- ) : (
-
- )}
-
- ))}
-
+ {!responses ||
+ (responses.length <= 0 && (
+
+ ))}
- {pageInfo.hasNextPage && (
-
+
+ {responses.map(response => (
+ -
+ {_has(response, 'title') ? (
+
+ ) : (
+
)}
+
+ ))}
+
-
-
- )
- }}
-
+ {pageInfo && pageInfo.hasNextPage && (
+
+ )}
+
+
+
)
}
diff --git a/src/views/ArticleDetail/Responses/index.tsx b/src/views/ArticleDetail/Responses/index.tsx
index f245b0b127..f38df24541 100644
--- a/src/views/ArticleDetail/Responses/index.tsx
+++ b/src/views/ArticleDetail/Responses/index.tsx
@@ -1,39 +1,34 @@
-import _get from 'lodash/get'
-import { QueryResult } from 'react-apollo'
+import gql from 'graphql-tag'
+import { useQuery } from 'react-apollo'
import { Translate } from '~/components'
import CommentForm from '~/components/Form/CommentForm'
-import { Query } from '~/components/GQL'
import { ArticleResponseCount } from '~/components/GQL/queries/__generated__/ArticleResponseCount'
import ARTICLE_RESPONSE_COUNT from '~/components/GQL/queries/articleResponseCount'
import { REFETCH_RESPONSES, TEXT } from '~/common/enums'
+import { ResponsesArticle } from './__generated__/ResponsesArticle'
import FeatureComments from './FeaturedComments'
import LatestResponses from './LatestResponses'
import styles from './styles.css'
-const ResponseCount = ({ mediaHash }: { mediaHash: string }) => (
-
- {({ data }: QueryResult & { data: ArticleResponseCount }) => {
- const count = _get(data, 'article.responseCount', 0)
- return (
- <>
- {count}
-
- >
- )
- }}
-
-)
+const ResponseCount = ({ mediaHash }: { mediaHash: string }) => {
+ const { data } = useQuery(ARTICLE_RESPONSE_COUNT, {
+ variables: { mediaHash }
+ })
+ const count = (data && data.article && data.article.responseCount) || 0
-const Responses = ({
- articleId,
- mediaHash
-}: {
- articleId: string
- mediaHash: string
-}) => {
+ return (
+
+ {count}
+
+
+
+ )
+}
+
+const Responses = ({ article }: { article: ResponsesArticle }) => {
const refetchResponses = () => {
window.dispatchEvent(new CustomEvent(REFETCH_RESPONSES, {}))
}
@@ -46,13 +41,14 @@ const Responses = ({
zh_hant={TEXT.zh_hant.response}
zh_hans={TEXT.zh_hans.response}
/>
-
+
@@ -65,4 +61,17 @@ const Responses = ({
)
}
+Responses.fragments = {
+ article: gql`
+ fragment ResponsesArticle on Article {
+ id
+ mediaHash
+ author {
+ id
+ isBlocking
+ }
+ }
+ `
+}
+
export default Responses
diff --git a/src/views/ArticleDetail/State/index.tsx b/src/views/ArticleDetail/State/index.tsx
index 3f2131bddb..19d5c4a662 100644
--- a/src/views/ArticleDetail/State/index.tsx
+++ b/src/views/ArticleDetail/State/index.tsx
@@ -40,7 +40,9 @@ const State = ({ article }: { article: StateArticle }) => {
return (
{isBanned && } />}
+
{isArchived && } />}
+
)
diff --git a/src/views/ArticleDetail/TagList/index.tsx b/src/views/ArticleDetail/TagList/index.tsx
index 3e569a5064..34f3e5a13a 100644
--- a/src/views/ArticleDetail/TagList/index.tsx
+++ b/src/views/ArticleDetail/TagList/index.tsx
@@ -1,5 +1,4 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
import { Tag } from '~/components'
diff --git a/src/views/ArticleDetail/Toolbar/AppreciationButton/index.tsx b/src/views/ArticleDetail/Toolbar/AppreciationButton/index.tsx
index b89874cad6..1a3205aea6 100644
--- a/src/views/ArticleDetail/Toolbar/AppreciationButton/index.tsx
+++ b/src/views/ArticleDetail/Toolbar/AppreciationButton/index.tsx
@@ -1,17 +1,19 @@
import classNames from 'classnames'
import gql from 'graphql-tag'
-import _get from 'lodash/get'
-import { forwardRef, useContext, useRef, useState } from 'react'
+import { forwardRef, useContext, useState } from 'react'
+import { useDebouncedCallback } from 'use-debounce'
import { Icon, Translate } from '~/components'
-import { Mutation } from '~/components/GQL'
+import { useMutation } from '~/components/GQL'
import { ModalSwitch } from '~/components/ModalManager'
import { Tooltip } from '~/components/Popper'
import { ViewerContext } from '~/components/Viewer'
+import { APPRECIATE_DEBOUNCE } from '~/common/enums'
import { numAbbr } from '~/common/utils'
import ICON_LIKE from '~/static/icons/like.svg?sprite'
+import { AppreciateArticle } from './__generated__/AppreciateArticle'
import { AppreciationArticleDetail } from './__generated__/AppreciationArticleDetail'
import styles from './styles.css'
@@ -41,42 +43,40 @@ const APPRECIATE_ARTICLE = gql`
}
`
+const IconAppreciate = () => (
+
+)
+
const AppreciatedCount = ({ num, limit }: { num: number; limit: number }) => {
const classes = classNames({
'appreciated-count': true,
'appreciated-reach-limit': num === limit
})
return (
- <>
- {num}
+
+ {num}
+
- >
+
)
}
-const OnboardingAppreciateButton = ({
- article
-}: {
- article: AppreciationArticleDetail
-}) => {
- const buttonClasses = classNames({
- 'appreciate-button': true
- })
-
+const OnboardingAppreciateButton = () => {
return (
{(open: any) => (
-
+
+
)}
@@ -91,39 +91,32 @@ const AppreciateButton = forwardRef<
canAppreciate: boolean
isAuthed: boolean
appreciatedCount: number
- appreciateLimit: number
+ limit: number
}
->(
- (
- { appreciate, canAppreciate, isAuthed, appreciatedCount, appreciateLimit },
- ref
- ) => {
- const buttonClasses = classNames({
- 'appreciate-button': true
- })
+>(({ appreciate, canAppreciate, isAuthed, appreciatedCount, limit }, ref) => {
+ const buttonClasses = classNames({
+ 'appreciate-button': true
+ })
- return (
- canAppreciate && appreciate()}
- aria-label="讚賞作品"
- >
-
- {isAuthed && appreciatedCount > 0 && (
-
- )}
-
-
- )
- }
-)
+ return (
+ canAppreciate && appreciate()}
+ aria-label="讚賞作品"
+ >
+
+
+ {isAuthed && appreciatedCount > 0 && (
+
+ )}
+
+
+
+ )
+})
const AppreciationButtonContainer = ({
article
@@ -133,16 +126,38 @@ const AppreciationButtonContainer = ({
const viewer = useContext(ViewerContext)
// bundle appreciations
- const [bundling, setBundling] = useState(false)
- const [appreciationAmount, setAppreciationAmount] = useState(0)
- const amountRef = useRef(appreciationAmount)
- amountRef.current = appreciationAmount
-
- const { appreciateLimit } = article
- const appreciateLeft = article.appreciateLeft - appreciationAmount
- const appreciatedCount = appreciateLimit - appreciateLeft
- const isReachLimit = appreciateLeft <= 0
+ const [amount, setAmount] = useState(0)
+ const [sendAppreciation] = useMutation(APPRECIATE_ARTICLE)
+ const {
+ appreciateLimit,
+ appreciateLeft,
+ appreciationsReceivedTotal
+ } = article
+ const limit = appreciateLimit
+ const left = appreciateLeft - amount
+ const appreciatedCount = limit - left
+ const [debouncedSendAppreciation] = useDebouncedCallback(() => {
+ sendAppreciation({
+ variables: { id: article.id, amount },
+ optimisticResponse: {
+ appreciateArticle: {
+ id: article.id,
+ appreciationsReceivedTotal: appreciationsReceivedTotal + amount,
+ hasAppreciate: true,
+ appreciateLeft: left,
+ __typename: 'Article'
+ }
+ }
+ })
+ setAmount(0)
+ }, APPRECIATE_DEBOUNCE)
+ const appreciate = () => {
+ setAmount(amount + 1)
+ debouncedSendAppreciation()
+ }
+ // UI
+ const isReachLimit = left <= 0
const isMe = article.author.id === viewer.id
const canAppreciate =
(!isReachLimit && !isMe && !viewer.isInactive) || !viewer.isAuthed
@@ -152,103 +167,63 @@ const AppreciationButtonContainer = ({
inactive: !canAppreciate,
unlogged: !viewer.isAuthed
})
+ const appreciateButtonProps = {
+ limit,
+ appreciatedCount,
+ canAppreciate,
+ appreciate,
+ isAuthed: viewer.isAuthed
+ }
- if (viewer.isOnboarding) {
+ if (viewer.shouldSetupLikerID) {
return (
-
+
+
{numAbbr(article.appreciationsReceivedTotal)}
+
)
}
return (
-
- {(sendAppreciation: any, { data }: any) => {
- // bundle appreciations
- const appreciate = () => {
- const debounce = 1000
- setAppreciationAmount(appreciationAmount + 1)
-
- if (!bundling) {
- setBundling(true)
- setTimeout(() => {
- if (amountRef.current) {
- sendAppreciation({
- variables: { id: article.id, amount: amountRef.current }
- })
- setAppreciationAmount(0)
- setBundling(false)
- }
- }, debounce)
+
+ {canAppreciate && }
+
+ {!canAppreciate && (
+
}
- }
+ >
+
+
+ )}
+
+
+ {numAbbr(article.appreciationsReceivedTotal)}
+
- return (
-
- {canAppreciate && (
-
- )}
- {!canAppreciate && (
-
- }
- >
-
-
- )}
-
- {numAbbr(article.appreciationsReceivedTotal)}
-
-
-
- )
- }}
-
+
+
)
}
diff --git a/src/views/ArticleDetail/Toolbar/Appreciators/AppreciatorsModal.tsx b/src/views/ArticleDetail/Toolbar/Appreciators/AppreciatorsModal.tsx
index c5ce15a9ba..fdb3215a56 100644
--- a/src/views/ArticleDetail/Toolbar/Appreciators/AppreciatorsModal.tsx
+++ b/src/views/ArticleDetail/Toolbar/Appreciators/AppreciatorsModal.tsx
@@ -1,10 +1,9 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
import { useRouter } from 'next/router'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import { InfiniteScroll, Spinner, Translate, UserDigest } from '~/components'
-import { Query } from '~/components/GQL'
+import { QueryError } from '~/components/GQL'
import { Modal } from '~/components/Modal'
import { ModalInstance } from '~/components/ModalManager'
@@ -50,101 +49,94 @@ const ARTICLE_APPRECIATORS = gql`
const AppreciatorsModal = () => {
const router = useRouter()
const mediaHash = getQuery({ router, key: 'mediaHash' })
+ const { data, loading, error, fetchMore } = useQuery(
+ ARTICLE_APPRECIATORS,
+ { variables: { mediaHash } }
+ )
- if (!mediaHash) {
- return null
+ const article = data && data.article
+ const connectionPath = 'article.appreciationsReceived'
+ const { edges, pageInfo } =
+ (data && data.article && data.article.appreciationsReceived) || {}
+
+ if (loading) {
+ return
}
- return (
-
- {(props: ModalInstanceProps) => (
-
- {({
- data,
- loading,
- error,
- fetchMore
- }: QueryResult & { data: AllArticleAppreciators }) => {
- if (loading) {
- return
- }
+ if (error) {
+ return
+ }
- const connectionPath = 'article.appreciationsReceived'
- const { edges, pageInfo } = _get(data, connectionPath, {})
- const loadMore = () => {
- analytics.trackEvent(ANALYTICS_EVENTS.LOAD_MORE, {
- type: FEED_TYPE.APPRECIATOR,
- location: edges.length,
- entrance: data.article.id
- })
- return fetchMore({
- variables: {
- after: pageInfo.endCursor
- },
- updateQuery: (previousResult, { fetchMoreResult }) =>
- mergeConnections({
- oldData: previousResult,
- newData: fetchMoreResult,
- path: connectionPath
- })
- })
- }
- const totalCount = numFormat(
- _get(data, 'article.appreciationsReceived.totalCount', 0)
- )
+ if (!edges || edges.length <= 0 || !pageInfo || !article) {
+ return null
+ }
- return (
- <>
-
- }
- />
-
- {
+ analytics.trackEvent(ANALYTICS_EVENTS.LOAD_MORE, {
+ type: FEED_TYPE.APPRECIATOR,
+ location: edges.length,
+ entrance: article.id
+ })
+ return fetchMore({
+ variables: {
+ after: pageInfo.endCursor
+ },
+ updateQuery: (previousResult, { fetchMoreResult }) =>
+ mergeConnections({
+ oldData: previousResult,
+ newData: fetchMoreResult,
+ path: connectionPath
+ })
+ })
+ }
+ const totalCount = numFormat(
+ (data && data.article && data.article.appreciationsReceived.totalCount) || 0
+ )
+
+ return (
+ <>
+
+ }
+ />
+
+
+
+ {edges.map(
+ ({ node, cursor }, i) =>
+ node.sender && (
+ -
+ analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
+ type: FEED_TYPE.APPRECIATOR,
+ location: i,
+ entrance: article.id
+ })
+ }
>
-
- {edges.map(
- (
- { node, cursor }: { node: any; cursor: any },
- i: number
- ) => (
- -
- analytics.trackEvent(
- ANALYTICS_EVENTS.CLICK_FEED,
- {
- type: FEED_TYPE.APPRECIATOR,
- location: i,
- entrance: data.article.id
- }
- )
- }
- >
-
-
- )
- )}
-
-
-
-
- >
- )
- }}
-
- )}
-
+
+
+ )
+ )}
+
+
+
+
+
+ >
)
}
-export default AppreciatorsModal
+export default () => (
+
+ {(props: ModalInstanceProps) => }
+
+)
diff --git a/src/views/ArticleDetail/Toolbar/Appreciators/index.tsx b/src/views/ArticleDetail/Toolbar/Appreciators/index.tsx
index 32bceec611..c32a6edd8f 100644
--- a/src/views/ArticleDetail/Toolbar/Appreciators/index.tsx
+++ b/src/views/ArticleDetail/Toolbar/Appreciators/index.tsx
@@ -36,7 +36,7 @@ const fragments = {
}
const Appreciators = ({ article }: { article: AppreciatorsArticle }) => {
- const edges = _get(article, 'appreciationsReceived.edges')
+ const edges = article.appreciationsReceived.edges
if (!edges || edges.length <= 0) {
return null
@@ -54,17 +54,21 @@ const Appreciators = ({ article }: { article: AppreciatorsArticle }) => {
{edges
.slice(0, 5)
- .map(({ node, cursor }: { node: any; cursor: any }) => (
-
- ))}
+ .map(
+ ({ node, cursor }) =>
+ node.sender && (
+
+ )
+ )}
{edges
.slice(0, 3)
- .map(({ node }: { node: any }) => node.sender.displayName)
+ .map(({ node }) => node.sender && node.sender.displayName)
.join('、')}
+
`等 ${count} 人贊賞了作品`}
@@ -75,6 +79,7 @@ const Appreciators = ({ article }: { article: AppreciatorsArticle }) => {
/>
+
)}
diff --git a/src/views/ArticleDetail/Toolbar/Appreciators/styles.css b/src/views/ArticleDetail/Toolbar/Appreciators/styles.css
index a1f7793bde..f14e0b3987 100644
--- a/src/views/ArticleDetail/Toolbar/Appreciators/styles.css
+++ b/src/views/ArticleDetail/Toolbar/Appreciators/styles.css
@@ -48,4 +48,11 @@
.modal-appreciators-list {
margin-top: var(--spacing-default);
padding: 0 var(--spacing-tight);
+
+ & li:last-child {
+ & :global(> *) {
+ margin-bottom: 0;
+ border-bottom: 0;
+ }
+ }
}
diff --git a/src/views/ArticleDetail/Toolbar/CommentButton/index.tsx b/src/views/ArticleDetail/Toolbar/CommentButton/index.tsx
index fc4b194afd..20da093ffb 100644
--- a/src/views/ArticleDetail/Toolbar/CommentButton/index.tsx
+++ b/src/views/ArticleDetail/Toolbar/CommentButton/index.tsx
@@ -1,6 +1,5 @@
import gql from 'graphql-tag'
import jump from 'jump.js'
-import _get from 'lodash/get'
import { MouseEventHandler } from 'react'
import { Icon, TextIcon } from '~/components'
@@ -71,7 +70,7 @@ const CommentButton = ({
type: 'article-detail'
})
}}
- text={numAbbr(_get(article, 'commentCount', 0))}
+ text={numAbbr(article.commentCount || 0)}
textPlacement={textPlacement}
/>
)
diff --git a/src/views/ArticleDetail/Toolbar/ExtendButton/index.tsx b/src/views/ArticleDetail/Toolbar/ExtendButton/index.tsx
index 345e64c30d..a1930a06a4 100644
--- a/src/views/ArticleDetail/Toolbar/ExtendButton/index.tsx
+++ b/src/views/ArticleDetail/Toolbar/ExtendButton/index.tsx
@@ -3,7 +3,7 @@ import Router from 'next/router'
import { useContext } from 'react'
import { Icon, LanguageContext, Tooltip, Translate } from '~/components'
-import { Mutation } from '~/components/GQL'
+import { useMutation } from '~/components/GQL'
import { ViewerContext } from '~/components/Viewer'
import { TEXT } from '~/common/enums'
@@ -34,12 +34,16 @@ const fragments = {
const ExtendButton = ({ article }: { article: ExtendButtonArticle }) => {
const viewer = useContext(ViewerContext)
const { lang } = useContext(LanguageContext)
- const placeholder = translate({
- zh_hans: TEXT.zh_hans.untitle,
- zh_hant: TEXT.zh_hant.untitle,
- lang
+ const [extend] = useMutation(EXTEND_ARTICLE, {
+ variables: {
+ title: translate({
+ zh_hans: TEXT.zh_hans.untitle,
+ zh_hant: TEXT.zh_hant.untitle,
+ lang
+ }),
+ collection: [article.id]
+ }
})
-
const canExtend = viewer.isActive
if (!canExtend) {
@@ -47,35 +51,30 @@ const ExtendButton = ({ article }: { article: ExtendButtonArticle }) => {
}
return (
- }
+ placement="top"
>
- {(extend: any) => (
- }
- placement="top"
- >
- {
- const result = await extend()
- const { data } = result as { data: ExtendArticle }
- const { slug, id } = data.putDraft
- const path = toPath({ page: 'draftDetail', slug, id })
- Router.push(path.as)
- }}
- >
-
-
-
- )}
-
+ {
+ const { data } = await extend()
+ const { slug, id } = (data && data.putDraft) || {}
+
+ if (slug && id) {
+ const path = toPath({ page: 'draftDetail', slug, id })
+ Router.push(path.as)
+ }
+ }}
+ >
+
+
+
)
}
diff --git a/src/views/ArticleDetail/Toolbar/ResponseButton/index.tsx b/src/views/ArticleDetail/Toolbar/ResponseButton/index.tsx
index 2267f3515a..52fec98642 100644
--- a/src/views/ArticleDetail/Toolbar/ResponseButton/index.tsx
+++ b/src/views/ArticleDetail/Toolbar/ResponseButton/index.tsx
@@ -1,6 +1,5 @@
import gql from 'graphql-tag'
import jump from 'jump.js'
-import _get from 'lodash/get'
import { MouseEventHandler } from 'react'
import { Icon, TextIcon } from '~/components'
@@ -71,7 +70,7 @@ const ResponseButton = ({
type: 'article-detail'
})
}}
- text={numAbbr(_get(article, 'responseCount', 0))}
+ text={numAbbr(article.responseCount || 0)}
textPlacement={textPlacement}
/>
)
diff --git a/src/views/ArticleDetail/Toolbar/index.tsx b/src/views/ArticleDetail/Toolbar/index.tsx
index 5d36c17d40..577fd7d421 100644
--- a/src/views/ArticleDetail/Toolbar/index.tsx
+++ b/src/views/ArticleDetail/Toolbar/index.tsx
@@ -1,9 +1,8 @@
import classNames from 'classnames'
import gql from 'graphql-tag'
-import _get from 'lodash/get'
import { BookmarkButton } from '~/components/Button/Bookmark'
-import ShareButton from '~/components/ShareButton'
+import ShareButton from '~/components/Button/Share'
import { ToolbarArticle } from './__generated__/ToolbarArticle'
import AppreciationButton from './AppreciationButton'
@@ -67,6 +66,7 @@ const Toolbar = ({
+
@@ -74,6 +74,7 @@ const Toolbar = ({
+
)
diff --git a/src/views/ArticleDetail/Wall/index.tsx b/src/views/ArticleDetail/Wall/index.tsx
index b6b68b2f97..b4e6a4d310 100644
--- a/src/views/ArticleDetail/Wall/index.tsx
+++ b/src/views/ArticleDetail/Wall/index.tsx
@@ -62,6 +62,7 @@ const Wall = ({ show, client }: any) => {
+
)
diff --git a/src/views/ArticleDetail/index.tsx b/src/views/ArticleDetail/index.tsx
index cda40c1971..8c2bbb2994 100644
--- a/src/views/ArticleDetail/index.tsx
+++ b/src/views/ArticleDetail/index.tsx
@@ -1,27 +1,20 @@
import gql from 'graphql-tag'
import jump from 'jump.js'
-import _get from 'lodash/get'
import _merge from 'lodash/merge'
import { useRouter } from 'next/router'
import { useContext, useEffect, useState } from 'react'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import { Waypoint } from 'react-waypoint'
-import {
- DateTime,
- Footer,
- Head,
- Placeholder,
- Responsive,
- Title
-} from '~/components'
+import { DateTime, Footer, Head, Placeholder, Title } from '~/components'
import { BookmarkButton } from '~/components/Button/Bookmark'
+import ShareModal from '~/components/Button/Share/ShareModal'
import { Fingerprint } from '~/components/Fingerprint'
-import { Query } from '~/components/GQL'
+import { QueryError } from '~/components/GQL'
+import { ClientPreference } from '~/components/GQL/queries/__generated__/ClientPreference'
import CLIENT_PREFERENCE from '~/components/GQL/queries/clientPreference'
-import { useImmersiveMode } from '~/components/Hook'
+import { useImmersiveMode, useResponsive } from '~/components/Hook'
import IconLive from '~/components/Icon/Live'
-import ShareModal from '~/components/ShareButton/ShareModal'
import Throw404 from '~/components/Throw404'
import { UserDigest } from '~/components/UserDigest'
import { ViewerContext } from '~/components/Viewer'
@@ -30,6 +23,7 @@ import { ANALYTICS_EVENTS } from '~/common/enums'
import { analytics, getQuery } from '~/common/utils'
import { ArticleDetail as ArticleDetailType } from './__generated__/ArticleDetail'
+import { ArticleEdited } from './__generated__/ArticleEdited'
import Collection from './Collection'
import Content from './Content'
import RelatedArticles from './RelatedArticles'
@@ -60,6 +54,7 @@ const ARTICLE_DETAIL = gql`
summary
createdAt
author {
+ isBlocking
...UserDigestFullDescUser
}
collection(input: { first: 0 }) @connection(key: "articleCollection") {
@@ -72,6 +67,7 @@ const ARTICLE_DETAIL = gql`
...RelatedArticles
...StateArticle
...FingerprintArticle
+ ...ResponsesArticle
}
}
${UserDigest.FullDesc.fragments.user}
@@ -82,6 +78,20 @@ const ARTICLE_DETAIL = gql`
${RelatedArticles.fragments.article}
${State.fragments.article}
${Fingerprint.fragments.article}
+ ${Responses.fragments.article}
+`
+
+const ARTICLE_EDITED = gql`
+ subscription ArticleEdited($id: ID!) {
+ nodeEdited(input: { id: $id }) {
+ id
+ ... on Article {
+ id
+ ...ToolbarArticle
+ }
+ }
+ }
+ ${Toolbar.fragments.article}
`
const Block = ({
@@ -110,219 +120,188 @@ const ArticleDetail = ({
const [fixedToolbar, setFixedToolbar] = useState(true)
const [trackedFinish, setTrackedFinish] = useState(false)
const [fixedWall, setFixedWall] = useState(false)
+ const isMediumUp = useResponsive({ type: 'medium-up' })()
+ const { data, loading, error, subscribeToMore, client } = useQuery<
+ ArticleDetailType
+ >(ARTICLE_DETAIL, {
+ variables: { mediaHash }
+ })
+
const shouldShowWall = !viewer.isAuthed && wall
+ const article = data && data.article
+ const authorId = article && article.author.id
+ const collectionCount = (article && article.collection.totalCount) || 0
+ const canEditCollection = viewer.id === authorId
+ const handleWall = ({ currentPosition }: Waypoint.CallbackArgs) => {
+ if (shouldShowWall) {
+ setFixedWall(currentPosition === 'inside')
+ }
+ }
+
+ useEffect(() => {
+ if (article && article.live) {
+ subscribeToMore({
+ document: ARTICLE_EDITED,
+ variables: { id: article.id },
+ updateQuery: (prev, { subscriptionData }) =>
+ _merge(prev, {
+ article: subscriptionData.data.nodeEdited
+ })
+ })
+ }
+ })
+
+ useEffect(() => {
+ if (shouldShowWall && window.location.hash && article) {
+ jump('#comments', { offset: -10 })
+ }
+ }, [article])
+
+ useImmersiveMode('article > .content')
+
+ if (loading) {
+ return (
+
+
+
+ )
+ }
- if (!mediaHash) {
+ if (error) {
+ return (
+
+
+
+ )
+ }
+
+ if (!article) {
return null
}
+ if (article.state !== 'active' && viewer.id !== authorId) {
+ return
+ }
+
return (
-
- {({
- data,
- client,
- loading,
- subscribeToMore
- }: QueryResult & { data: ArticleDetailType }) => {
- const authorId = _get(data, 'article.author.id')
- const collectionCount = _get(data, 'article.collection.totalCount')
- const canEditCollection = viewer.id === authorId
-
- const handleWall = ({ currentPosition }: Waypoint.CallbackArgs) => {
- if (shouldShowWall) {
- setFixedWall(currentPosition === 'inside')
+ <>
+
+ content)
+ : []
}
- }
-
- return (
-
- {(() => {
- if (loading) {
- return (
-
-
-
- )
- }
-
- if (data.article.state !== 'active' && viewer.id !== authorId) {
- return
- }
-
- useEffect(() => {
- if (data.article.live) {
- subscribeToMore({
- document: gql`
- subscription ArticleEdited($id: ID!) {
- nodeEdited(input: { id: $id }) {
- id
- ... on Article {
- id
- ...ToolbarArticle
- }
- }
- }
- ${Toolbar.fragments.article}
- `,
- variables: { id: data.article.id },
- updateQuery: (prev, { subscriptionData }) =>
- _merge(prev, {
- article: subscriptionData.data.nodeEdited
- })
- })
+ image={article.cover}
+ />
+
+
+
+
+
+
+ {article.title}
+
+
+
+
+
+ {article.live && }
+
+
+
+
+
+
+
+
+ {(collectionCount > 0 || canEditCollection) && (
+
+ )}
+
+ {/* content:end */}
+ {!isMediumUp && (
+ {
+ if (currentPosition === 'below') {
+ setFixedToolbar(true)
+ } else {
+ setFixedToolbar(false)
}
- })
+ }}
+ />
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {shouldShowWall && }
- useEffect(() => {
- if (process.browser && shouldShowWall) {
- if (window.location.hash) {
- jump('#comments', { offset: -10 })
- }
+
+ {shouldShowWall && }
+
+ {!shouldShowWall && (
+ <>
+
+
+ {
+ if (!trackedFinish) {
+ analytics.trackEvent(ANALYTICS_EVENTS.FINISH_COMMENTS, {
+ entrance: article.id
+ })
+ setTrackedFinish(true)
}
- }, [])
-
- useImmersiveMode('article > .content')
-
- return (
-
- {(isMediumUp: boolean) => (
- <>
-
- content
- )}
- image={data.article.cover}
- />
-
-
-
-
-
-
- {data.article.title}
-
-
-
-
-
- {data.article.live && }
-
-
-
-
-
-
-
- {(collectionCount > 0 || canEditCollection) && (
-
- )}
-
- {/* content:end */}
- {!isMediumUp && (
- {
- if (currentPosition === 'below') {
- setFixedToolbar(true)
- } else {
- setFixedToolbar(false)
- }
- }}
- />
- )}
-
-
-
-
-
-
-
-
-
-
-
-
- {shouldShowWall && (
-
- )}
-
-
- {shouldShowWall && }
- {!shouldShowWall && (
- <>
-
- {
- if (!trackedFinish) {
- analytics.trackEvent(
- ANALYTICS_EVENTS.FINISH_COMMENTS,
- {
- entrance: data.article.id
- }
- )
- setTrackedFinish(true)
- }
- }}
- />
- >
- )}
-
-
-
- >
- )}
-
- )
- })()}
-
-
- {!shouldShowWall && }
-
-
-
-
- )
- }}
-
+ }}
+ />
+ >
+ )}
+
+
+
+
+
+
+
+ >
)
}
const ArticleDetailContainer = () => {
const router = useRouter()
const mediaHash = getQuery({ router, key: 'mediaHash' })
-
- if (!mediaHash) {
- return null
- }
+ const { data } = useQuery(CLIENT_PREFERENCE, {
+ variables: { id: 'local' }
+ })
+ const { wall } = (data && data.clientPreference) || { wall: true }
return (
-
- {({ data }: any) => {
- const { wall } = _get(data, 'clientPreference', { wall: true })
- return
- }}
-
+
+
+
+
+
)
}
diff --git a/src/views/Auth/Forget/index.tsx b/src/views/Auth/Forget/index.tsx
index 12d7434e04..e027f4cd74 100644
--- a/src/views/Auth/Forget/index.tsx
+++ b/src/views/Auth/Forget/index.tsx
@@ -63,69 +63,68 @@ const Forget = () => {
}
return (
- <>
-
-
+
+
-
- {step === 'request' && (
-
- )}
- {step === 'reset' && (
- setStep('complete')}
- scrollLock={false}
- />
- )}
- {step === 'complete' && (
-
-
-
-
-
-
-
-
- 。
-
-
+
+ {step === 'request' && (
+
+ )}
+ {step === 'reset' && (
+ setStep('complete')}
+ scrollLock={false}
+ />
+ )}
+ {step === 'complete' && (
+
- )}
-
-
+ 。
+
+
+
+
+
+
+
+ )}
+
+
- >
+
)
}
diff --git a/src/views/Auth/Login/index.tsx b/src/views/Auth/Login/index.tsx
index 3eb521e032..f9e06447be 100644
--- a/src/views/Auth/Login/index.tsx
+++ b/src/views/Auth/Login/index.tsx
@@ -28,18 +28,17 @@ const Login = () => {
)
return (
- <>
-
-
-
-
-
-
-
+
+
+
+
+
+
+
- >
+
)
}
diff --git a/src/views/Auth/SignUp/index.tsx b/src/views/Auth/SignUp/index.tsx
index 6d3e2f1cb0..169698b0e7 100644
--- a/src/views/Auth/SignUp/index.tsx
+++ b/src/views/Auth/SignUp/index.tsx
@@ -33,49 +33,48 @@ const SignUp = () => {
)
return (
- <>
-
-
+
+
+
+
+ {step === 'signUp' && (
+ {
+ setStep('profile')
+ }}
+ scrollLock={false}
+ />
+ )}
+ {step === 'profile' && (
+ {
+ setStep('setupLikeCoin')
+ }}
+ scrollLock={false}
+ />
+ )}
+ {step === 'setupLikeCoin' && (
+ {
+ setStep('complete')
+ }}
+ scrollLock={false}
+ />
+ )}
+ {step === 'complete' && (
+
+ )}
+
-
- {step === 'signUp' && (
- {
- setStep('profile')
- }}
- scrollLock={false}
- />
- )}
- {step === 'profile' && (
- {
- setStep('setupLikeCoin')
- }}
- scrollLock={false}
- />
- )}
- {step === 'setupLikeCoin' && (
- {
- setStep('complete')
- }}
- scrollLock={false}
- />
- )}
- {step === 'complete' && (
-
- )}
-
-
- >
+
)
}
diff --git a/src/views/Authors/index.tsx b/src/views/Authors/index.tsx
index 9d224e492d..21e3320716 100644
--- a/src/views/Authors/index.tsx
+++ b/src/views/Authors/index.tsx
@@ -1,6 +1,5 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import {
Footer,
@@ -11,7 +10,8 @@ import {
Translate,
UserDigest
} from '~/components'
-import { Query } from '~/components/GQL'
+import EmptyWarning from '~/components/Empty/EmptyWarning'
+import { QueryError } from '~/components/GQL'
import { ANALYTICS_EVENTS, FEED_TYPE, TEXT } from '~/common/enums'
import { analytics, mergeConnections } from '~/common/utils'
@@ -43,95 +43,98 @@ const ALL_AUTHORSS = gql`
${UserDigest.FullDesc.fragments.user}
`
-const Authors = () => (
-
-
-
+const Authors = () => {
+ const { data, loading, error, fetchMore } = useQuery(ALL_AUTHORSS)
-
- }
+ if (loading) {
+ return
+ }
+
+ if (error) {
+ return
+ }
+
+ const connectionPath = 'viewer.recommendation.authors'
+ const { edges, pageInfo } =
+ (data && data.viewer && data.viewer.recommendation.authors) || {}
+
+ if (!edges || edges.length <= 0 || !pageInfo) {
+ return (
+ }
/>
+ )
+ }
-
-
- {({
- data,
- loading,
- error,
- fetchMore
- }: QueryResult & { data: AllAuthors }) => {
- if (loading) {
- return
- }
+ const loadMore = () => {
+ analytics.trackEvent(ANALYTICS_EVENTS.LOAD_MORE, {
+ type: FEED_TYPE.ALL_AUTHORS,
+ location: edges.length
+ })
+ return fetchMore({
+ variables: {
+ after: pageInfo.endCursor
+ },
+ updateQuery: (previousResult, { fetchMoreResult }) =>
+ mergeConnections({
+ oldData: previousResult,
+ newData: fetchMoreResult,
+ path: connectionPath
+ })
+ })
+ }
- const connectionPath = 'viewer.recommendation.authors'
- const { edges, pageInfo } = _get(data, connectionPath, {})
- const loadMore = () => {
- analytics.trackEvent(ANALYTICS_EVENTS.LOAD_MORE, {
+ return (
+
+
+ {edges.map(({ node, cursor }, i) => (
+ -
+ analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
type: FEED_TYPE.ALL_AUTHORS,
- location: edges.length
- })
- return fetchMore({
- variables: {
- after: pageInfo.endCursor
- },
- updateQuery: (previousResult, { fetchMoreResult }) =>
- mergeConnections({
- oldData: previousResult,
- newData: fetchMoreResult,
- path: connectionPath
- })
+ location: i
})
}
+ >
+
+
+ ))}
+
+
+ )
+}
- return (
-
-
- {edges.map(
- (
- { node, cursor }: { node: any; cursor: any },
- i: number
- ) => (
- -
- analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
- type: FEED_TYPE.ALL_AUTHORS,
- location: i
- })
- }
- >
-
-
- )
- )}
-
-
- )
+export default () => {
+ return (
+
+
+
-
-
+ />
+
+
+ }
+ />
-
+
+
-
-
-)
+
-export default Authors
+
+
+ )
+}
diff --git a/src/views/Follow/FollowFeed/index.tsx b/src/views/Follow/FollowFeed/index.tsx
index ed9c9d826b..f0ae50287e 100644
--- a/src/views/Follow/FollowFeed/index.tsx
+++ b/src/views/Follow/FollowFeed/index.tsx
@@ -1,6 +1,5 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import {
ArticleDigest,
@@ -10,12 +9,13 @@ import {
Placeholder,
Translate
} from '~/components'
-import { Query } from '~/components/GQL'
+import EmptyArticle from '~/components/Empty/EmptyArticle'
+import { QueryError } from '~/components/GQL'
import { ANALYTICS_EVENTS, FEED_TYPE, TEXT } from '~/common/enums'
import { analytics, mergeConnections } from '~/common/utils'
-import { FollowFeed } from './__generated__/FollowFeed'
+import { FollowFeed as FollowFeedType } from './__generated__/FollowFeed'
const FOLLOW_FEED = gql`
query FollowFeed(
@@ -46,86 +46,84 @@ const FOLLOW_FEED = gql`
${ArticleDigest.Feed.fragments.article}
`
-export default () => {
- return (
-
- {({
- data,
- loading,
- error,
- fetchMore
- }: QueryResult & { data: FollowFeed }) => {
- if (loading) {
- return
- }
+const FollowFeed = () => {
+ const { data, loading, error, fetchMore } = useQuery(
+ FOLLOW_FEED
+ )
- const connectionPath = 'viewer.recommendation.followeeArticles'
- const { edges, pageInfo } = _get(data, connectionPath, {})
- const loadMore = () => {
- analytics.trackEvent(ANALYTICS_EVENTS.LOAD_MORE, {
- type: FEED_TYPE.FOLLOW,
- location: edges.length
- })
- return fetchMore({
- variables: {
- after: pageInfo.endCursor
- },
- updateQuery: (previousResult, { fetchMoreResult }) =>
- mergeConnections({
- oldData: previousResult,
- newData: fetchMoreResult,
- path: connectionPath
- })
- })
- }
+ if (loading) {
+ return
+ }
- return (
- <>
-
+ if (error) {
+ return
+ }
-
- }
- />
+ const connectionPath = 'viewer.recommendation.followeeArticles'
+ const { edges, pageInfo } =
+ (data && data.viewer && data.viewer.recommendation.followeeArticles) || {}
-
-
- {edges.map(
- ({ node, cursor }: { node: any; cursor: any }, i: number) => (
- -
- analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
- type: FEED_TYPE.FOLLOW,
- location: i
- })
- }
- >
-
-
- )
- )}
-
-
- >
- )
- }}
-
+ if (!edges || edges.length <= 0 || !pageInfo) {
+ return
+ }
+
+ const loadMore = () => {
+ analytics.trackEvent(ANALYTICS_EVENTS.LOAD_MORE, {
+ type: FEED_TYPE.FOLLOW,
+ location: edges.length
+ })
+ return fetchMore({
+ variables: {
+ after: pageInfo.endCursor
+ },
+ updateQuery: (previousResult, { fetchMoreResult }) =>
+ mergeConnections({
+ oldData: previousResult,
+ newData: fetchMoreResult,
+ path: connectionPath
+ })
+ })
+ }
+
+ return (
+
+
+ {edges.map(({ node, cursor }, i) => (
+ -
+ analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
+ type: FEED_TYPE.FOLLOW,
+ location: i
+ })
+ }
+ >
+
+
+ ))}
+
+
)
}
+
+export default () => (
+ <>
+
+
+
+ }
+ />
+
+
+ >
+)
diff --git a/src/views/Follow/PickAuthors/index.tsx b/src/views/Follow/PickAuthors/index.tsx
index a04eed917d..de59b15a4f 100644
--- a/src/views/Follow/PickAuthors/index.tsx
+++ b/src/views/Follow/PickAuthors/index.tsx
@@ -1,7 +1,5 @@
-import _get from 'lodash/get'
-
import { Head, Translate } from '~/components'
-import { AuthorPicker } from '~/components/Follow'
+import AuthorPicker from '~/components/Follow/AuthorPicker'
import { TEXT } from '~/common/enums'
import IMAGE_ILLUSTRATION_AVATAR from '~/static/images/illustration-avatar.svg'
@@ -23,6 +21,7 @@ const PickIntroHeader = () => {
zh_hans="欢迎加入 Matters,一个自由、自主、永续的创作与公共讨论空间。"
/>
+
5
@@ -32,6 +31,7 @@ const PickIntroHeader = () => {
/>
+
)
@@ -55,6 +55,7 @@ const PickAuthors = ({ viewer }: { [key: string]: any }) => (
/>
}
/>
+
>
)
diff --git a/src/views/Follow/index.tsx b/src/views/Follow/index.tsx
index f671060271..62c4da4730 100644
--- a/src/views/Follow/index.tsx
+++ b/src/views/Follow/index.tsx
@@ -1,14 +1,14 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
import { useContext, useEffect } from 'react'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import { Footer, Spinner } from '~/components'
-import { Mutation, Query } from '~/components/GQL'
+import { useMutation } from '~/components/GQL'
import viewerUnreadFolloweeArticles from '~/components/GQL/updates/viewerUnreadFolloweeArticles'
import { ViewerContext } from '~/components/Viewer'
import { MeFollow } from './__generated__/MeFollow'
+import { ReadFolloweeArticles } from './__generated__/ReadFolloweeArticles'
import FollowFeed from './FollowFeed'
import PickAuthors from './PickAuthors'
@@ -28,49 +28,48 @@ const ME_FOLLOW = gql`
${PickAuthors.fragments.user}
`
-export default () => {
+const Follow = () => {
const viewer = useContext(ViewerContext)
+ const [readFolloweeArticles] = useMutation(
+ READ_FOLLOWEE_ARTICLES,
+ {
+ update: viewerUnreadFolloweeArticles
+ }
+ )
+ const { data, loading } = useQuery(ME_FOLLOW)
- return (
-
-
-
- {(readFolloweeArticles: any) => (
-
- {({ data, loading, error }: QueryResult & { data: MeFollow }) => {
- if (loading) {
- return
- }
+ useEffect(() => {
+ if (viewer.isAuthed) {
+ readFolloweeArticles()
+ }
+ }, [])
- useEffect(() => {
- if (viewer.isAuthed) {
- readFolloweeArticles()
- }
- }, [])
+ if (loading) {
+ return
+ }
- const followeeCount = _get(
- data,
- 'viewer.followees.totalCount',
- 0
- )
+ if (!data) {
+ return null
+ }
- if (followeeCount < 5) {
- return
- } else {
- return
- }
- }}
-
- )}
-
-
+ const followeeCount =
+ (data && data.viewer && data.viewer.followees.totalCount) || 0
-
-
- )
+ if (followeeCount < 5) {
+ return
+ } else {
+ return
+ }
}
+
+export default () => (
+
+
+
+
+
+
+
+)
diff --git a/src/views/Home/Feed/SortBy/index.tsx b/src/views/Home/Feed/SortBy/index.tsx
index 9dbcf210a5..4f9a68edd1 100644
--- a/src/views/Home/Feed/SortBy/index.tsx
+++ b/src/views/Home/Feed/SortBy/index.tsx
@@ -42,6 +42,7 @@ const SortBy: React.FC = ({ sortBy, setSortBy }) => {
{!isHottest && }
+
>
)
diff --git a/src/views/Home/Feed/index.tsx b/src/views/Home/Feed/index.tsx
index 175d887d90..e231227a9a 100644
--- a/src/views/Home/Feed/index.tsx
+++ b/src/views/Home/Feed/index.tsx
@@ -1,23 +1,25 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import {
InfiniteScroll,
LoadMore,
PageHeader,
Placeholder,
- Responsive,
Translate
} from '~/components'
import { ArticleDigest } from '~/components/ArticleDigest'
-import { Query } from '~/components/GQL'
+import EmptyArticle from '~/components/Empty/EmptyArticle'
+import { QueryError } from '~/components/GQL'
+import { ClientPreference } from '~/components/GQL/queries/__generated__/ClientPreference'
import CLIENT_PREFERENCE from '~/components/GQL/queries/clientPreference'
+import { useResponsive } from '~/components/Hook'
import { ANALYTICS_EVENTS } from '~/common/enums'
import { analytics, mergeConnections } from '~/common/utils'
-import { FeedArticleConnection } from './__generated__/FeedArticleConnection'
+import { HottestFeed } from './__generated__/HottestFeed'
+import { NewestFeed } from './__generated__/NewestFeed'
import SortBy from './SortBy'
const feedFragment = gql`
@@ -37,7 +39,7 @@ const feedFragment = gql`
${ArticleDigest.Feed.fragments.article}
`
-export const queries: { [key: string]: any } = {
+export const queries = {
hottest: gql`
query HottestFeed(
$after: String
@@ -78,7 +80,85 @@ export const queries: { [key: string]: any } = {
type SortBy = 'hottest' | 'newest'
-const Feed = ({ feedSortType: sortBy, client }: any) => {
+const Feed = ({ feedSortType: sortBy }: { feedSortType: SortBy }) => {
+ const isMediumUp = useResponsive({ type: 'medium-up' })()
+ const { data, error, loading, fetchMore } = useQuery<
+ HottestFeed | NewestFeed
+ >(queries[sortBy], {
+ notifyOnNetworkStatusChange: true
+ })
+
+ const connectionPath = 'viewer.recommendation.feed'
+ const result = data && data.viewer && data.viewer.recommendation.feed
+ const { edges, pageInfo } = result || {}
+
+ if (loading && !result) {
+ return
+ }
+
+ if (error) {
+ return
+ }
+
+ if (!edges || edges.length <= 0 || !pageInfo) {
+ return
+ }
+
+ const loadMore = () => {
+ analytics.trackEvent(ANALYTICS_EVENTS.LOAD_MORE, {
+ type: sortBy,
+ location: edges.length
+ })
+ return fetchMore({
+ variables: {
+ after: pageInfo.endCursor
+ },
+ updateQuery: (previousResult, { fetchMoreResult }) =>
+ mergeConnections({
+ oldData: previousResult,
+ newData: fetchMoreResult,
+ path: connectionPath
+ })
+ })
+ }
+
+ return (
+ <>
+
+
+ {edges.map(({ node, cursor }, i) => (
+ -
+ analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
+ type: sortBy,
+ location: i
+ })
+ }
+ >
+
+
+ ))}
+
+
+
+ {!isMediumUp && pageInfo.hasNextPage && (
+
+ )}
+ >
+ )
+}
+
+const HomeFeed = () => {
+ const { data, client } = useQuery(CLIENT_PREFERENCE, {
+ variables: { id: 'local' }
+ })
+ const { feedSortType } = (data && data.clientPreference) || {
+ feedSortType: 'hottest'
+ }
const setSortBy = (type: SortBy) => {
if (client) {
client.writeData({
@@ -90,110 +170,21 @@ const Feed = ({ feedSortType: sortBy, client }: any) => {
return (
<>
-
- {({
- data,
- loading,
- fetchMore
- }: QueryResult & { data: FeedArticleConnection }) => {
- if (loading && !_get(data, 'viewer.recommendation.feed')) {
- return
- }
-
- const connectionPath = 'viewer.recommendation.feed'
- const { edges, pageInfo } = _get(data, connectionPath, {
- edges: [],
- pageInfo: {}
- })
- const loadMore = () => {
- analytics.trackEvent(ANALYTICS_EVENTS.LOAD_MORE, {
- type: sortBy,
- location: edges.length
- })
- return fetchMore({
- variables: {
- after: pageInfo.endCursor
- },
- updateQuery: (previousResult, { fetchMoreResult }) =>
- mergeConnections({
- oldData: previousResult,
- newData: fetchMoreResult,
- path: connectionPath
- })
- })
- }
-
- return (
- <>
-
- ) : (
-
- )
- }
- >
-
-
-
-
- {(match: boolean) => (
- <>
-
-
- {edges.map(
- (
- { node, cursor }: { node: any; cursor: any },
- i: number
- ) => (
- -
- analytics.trackEvent(
- ANALYTICS_EVENTS.CLICK_FEED,
- {
- type: sortBy,
- location: i
- }
- )
- }
- >
-
-
- )
- )}
-
-
-
- {!match && pageInfo.hasNextPage && (
-
- )}
- >
- )}
-
- >
+
+ ) : (
+
)
- }}
-
+ }
+ >
+
+
+
+
>
)
}
-export default () => (
-
- {({ data, client }: any) => {
- const { feedSortType } = _get(data, 'clientPreference', {
- feedSortType: 'hottest'
- })
- return
- }}
-
-)
+export default HomeFeed
diff --git a/src/views/Home/MattersToday/index.tsx b/src/views/Home/MattersToday/index.tsx
index 000ecc45d2..9f2901459c 100644
--- a/src/views/Home/MattersToday/index.tsx
+++ b/src/views/Home/MattersToday/index.tsx
@@ -1,16 +1,14 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
-import { Placeholder } from '~/components'
+import { Error, Placeholder } from '~/components'
import { ArticleDigest } from '~/components/ArticleDigest'
-import { Query } from '~/components/GQL'
+import { QueryError } from '~/components/GQL'
import { ANALYTICS_EVENTS, FEED_TYPE } from '~/common/enums'
import { analytics } from '~/common/utils'
import { HomeToday } from './__generated__/HomeToday'
-import styles from './styles.css'
export const HOME_TODAY = gql`
query HomeToday(
@@ -30,33 +28,38 @@ export const HOME_TODAY = gql`
${ArticleDigest.Feature.fragments.article}
`
-export default () => (
- <>
-
- {({ data, loading, error }: QueryResult & { data: HomeToday }) => {
- const article = _get(data, 'viewer.recommendation.today')
+const MattersToday = () => {
+ const { data, loading, error } = useQuery(HOME_TODAY)
- if (loading || !article) {
- return
+ if (loading) {
+ return
+ }
+
+ if (error) {
+ return
+ }
+
+ const article = data && data.viewer && data.viewer.recommendation.today
+
+ if (!article) {
+ return
+ }
+
+ return (
+ <>
+
+ analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
+ type: FEED_TYPE.TODAY
+ })
}
+ hasAuthor
+ hasDateTime
+ hasBookmark
+ />
+ >
+ )
+}
- return (
- <>
-
- analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
- type: FEED_TYPE.TODAY
- })
- }
- hasAuthor
- hasDateTime
- hasBookmark
- />
- >
- )
- }}
-
-
- >
-)
+export default MattersToday
diff --git a/src/views/Home/MattersToday/styles.css b/src/views/Home/MattersToday/styles.css
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/src/views/Home/Sidebar/Authors/index.tsx b/src/views/Home/Sidebar/Authors/index.tsx
index 20e531bb6e..ade3659c56 100644
--- a/src/views/Home/Sidebar/Authors/index.tsx
+++ b/src/views/Home/Sidebar/Authors/index.tsx
@@ -1,6 +1,5 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import {
Label,
@@ -9,7 +8,7 @@ import {
Translate,
UserDigest
} from '~/components'
-import { Query } from '~/components/GQL'
+import { QueryError } from '~/components/GQL'
import { ANALYTICS_EVENTS, FEED_TYPE } from '~/common/enums'
import { analytics } from '~/common/utils'
@@ -39,66 +38,66 @@ const SIDEBAR_AUTHORS = gql`
${UserDigest.FullDesc.fragments.user}
`
-export default () => (
- <>
-
- {({
- data,
- loading,
- error,
- refetch
- }: QueryResult & { data: SidebarAuthors }) => {
- const edges = _get(data, 'viewer.recommendation.authors.edges', [])
+const Authors = () => {
+ const { data, loading, error, refetch } = useQuery(
+ SIDEBAR_AUTHORS,
+ {
+ notifyOnNetworkStatusChange: true
+ }
+ )
+ const edges = data && data.viewer && data.viewer.recommendation.authors.edges
- if (!edges || edges.length <= 0) {
- return null
- }
+ if (error) {
+ return
+ }
+
+ if (!edges || edges.length <= 0) {
+ return null
+ }
+
+ return (
+ <>
+
- return (
- <>
-
+ {!loading && (
+
+ {edges.map(({ node, cursor }, i) => (
+ -
+ analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
+ type: FEED_TYPE.AUTHORS,
+ location: i
+ })
+ }
+ >
+
+
+ ))}
+
+ )}
- {loading && }
+
+ >
+ )
+}
- {!loading && (
-
- {edges.map(
- ({ node, cursor }: { node: any; cursor: any }, i: number) => (
- -
- analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
- type: FEED_TYPE.AUTHORS,
- location: i
- })
- }
- >
-
-
- )
- )}
-
- )}
- >
- )
- }}
-
-
- >
-)
+export default Authors
diff --git a/src/views/Home/Sidebar/Icymi/index.tsx b/src/views/Home/Sidebar/Icymi/index.tsx
index d56f6db74c..dfc126bdf8 100644
--- a/src/views/Home/Sidebar/Icymi/index.tsx
+++ b/src/views/Home/Sidebar/Icymi/index.tsx
@@ -1,10 +1,8 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import { Label, Placeholder, Translate } from '~/components'
import { ArticleDigest } from '~/components/ArticleDigest'
-import { Query } from '~/components/GQL'
import { ANALYTICS_EVENTS, FEED_TYPE } from '~/common/enums'
import { analytics } from '~/common/utils'
@@ -35,46 +33,43 @@ export const SIDEBAR_ICYMI = gql`
${ArticleDigest.Sidebar.fragments.article}
`
-export default () => (
-
- {({ data, loading, error }: QueryResult & { data: SidebarIcymi }) => {
- if (loading) {
- return
- }
+const ICYMI = () => {
+ const { data, loading } = useQuery(SIDEBAR_ICYMI)
+ const edges = data && data.viewer && data.viewer.recommendation.icymi.edges
- const edges = _get(data, 'viewer.recommendation.icymi.edges', [])
+ if (loading) {
+ return
+ }
- if (!edges || edges.length <= 0) {
- return null
- }
+ if (!edges || edges.length <= 0) {
+ return null
+ }
+
+ return (
+ <>
+
- return (
- <>
-
+
+ {edges.map(({ node, cursor }, i) => (
+ -
+ analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
+ type: FEED_TYPE.ICYMI,
+ location: i
+ })
+ }
+ >
+
+
+ ))}
+
+ >
+ )
+}
-
- {edges.map(
- ({ node, cursor }: { node: any; cursor: any }, i: number) => (
- -
- analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
- type: FEED_TYPE.ICYMI,
- location: i
- })
- }
- >
-
-
- )
- )}
-
- >
- )
- }}
-
-)
+export default ICYMI
diff --git a/src/views/Home/Sidebar/Tags/index.tsx b/src/views/Home/Sidebar/Tags/index.tsx
index a75111c453..7424e6fd36 100644
--- a/src/views/Home/Sidebar/Tags/index.tsx
+++ b/src/views/Home/Sidebar/Tags/index.tsx
@@ -1,9 +1,8 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
-import { Label, Tag, Translate } from '~/components'
-import { Query } from '~/components/GQL'
+import { Label, Spinner, Tag, Translate } from '~/components'
+import { QueryError } from '~/components/GQL'
import { ANALYTICS_EVENTS, FEED_TYPE, TEXT } from '~/common/enums'
import { analytics } from '~/common/utils'
@@ -31,49 +30,48 @@ const SIDEBAR_TAGS = gql`
${Tag.fragments.tag}
`
-export default () => (
- <>
-
- {({ data, loading, error }: QueryResult & { data: SidebarTags }) => {
- const edges = _get(data, 'viewer.recommendation.tags.edges', [])
+const Tags = () => {
+ const { data, loading, error } = useQuery(SIDEBAR_TAGS)
+ const edges = data && data.viewer && data.viewer.recommendation.tags.edges
- if (!edges || edges.length <= 0) {
- return null
- }
+ if (error) {
+ return
+ }
+
+ if (!edges || edges.length <= 0) {
+ return null
+ }
+
+ return (
+ <>
+
+
+ {loading && }
+
+
+ {edges.map(({ node, cursor }, i) => (
+ -
+ analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
+ type: FEED_TYPE.TAGS,
+ location: i
+ })
+ }
+ >
+
+
+ ))}
+
- return (
- <>
-
+
+ >
+ )
+}
-
- {edges.map(
- ({ node, cursor }: { node: any; cursor: any }, i: number) => (
- -
- analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
- type: FEED_TYPE.TAGS,
- location: i
- })
- }
- >
-
-
- )
- )}
-
- >
- )
- }}
-
-
- >
-)
+export default Tags
diff --git a/src/views/Home/Sidebar/Topics/index.tsx b/src/views/Home/Sidebar/Topics/index.tsx
index a1695b9eae..c3e7060dc0 100644
--- a/src/views/Home/Sidebar/Topics/index.tsx
+++ b/src/views/Home/Sidebar/Topics/index.tsx
@@ -1,10 +1,9 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
-import { Label, Translate } from '~/components'
+import { Label, Placeholder, Translate } from '~/components'
import { ArticleDigest } from '~/components/ArticleDigest'
-import { Query } from '~/components/GQL'
+import { QueryError } from '~/components/GQL'
import { ANALYTICS_EVENTS, FEED_TYPE, TEXT } from '~/common/enums'
import { analytics } from '~/common/utils'
@@ -37,51 +36,55 @@ export const SIDEBAR_TOPICS = gql`
${ArticleDigest.Sidebar.fragments.article}
`
-export default () => (
- <>
-
- {({ data, loading, error }: QueryResult & { data: SidebarTopics }) => {
- const edges = _get(data, 'viewer.recommendation.topics.edges', [])
+const Topics = () => {
+ const { data, loading, error } = useQuery(SIDEBAR_TOPICS)
+ const edges = data && data.viewer && data.viewer.recommendation.topics.edges
- if (!edges || edges.length <= 0) {
- return null
- }
+ if (loading) {
+ return
+ }
+
+ if (error) {
+ return
+ }
+
+ if (!edges || edges.length <= 0) {
+ return null
+ }
+
+ return (
+ <>
+
+
+
+ {edges
+ .filter(({ node }) => !!node.mediaHash)
+ .map(({ node, cursor }, i) => (
+ -
+ analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
+ type: FEED_TYPE.TOPICS,
+ location: i
+ })
+ }
+ >
+
+
+ ))}
+
- return (
- <>
-
+
+ >
+ )
+}
-
- {edges
- .filter(({ node }: { node: any }) => !!node.mediaHash)
- .map(
- ({ node, cursor }: { node: any; cursor: any }, i: number) => (
- -
- analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
- type: FEED_TYPE.TOPICS,
- location: i
- })
- }
- >
-
-
- )
- )}
-
- >
- )
- }}
-
-
- >
-)
+export default Topics
diff --git a/src/views/Home/Sidebar/ViewAllLink/index.tsx b/src/views/Home/Sidebar/ViewAllLink/index.tsx
index ba44e74d20..fcfee8bbaf 100644
--- a/src/views/Home/Sidebar/ViewAllLink/index.tsx
+++ b/src/views/Home/Sidebar/ViewAllLink/index.tsx
@@ -9,7 +9,7 @@ import ICON_ARROW_RIGHT_GREEN_SMALL from '~/static/icons/arrow-right-green-small
import styles from './styles.css'
-export default ({ type }: { type: 'authors' | 'tags' | 'topics' }) => {
+const ViewAllLink = ({ type }: { type: 'authors' | 'tags' | 'topics' }) => {
const { lang } = useContext(LanguageContext)
const pathMap = {
topics: PATHS.TOPICS,
@@ -40,8 +40,11 @@ export default ({ type }: { type: 'authors' | 'tags' | 'topics' }) => {
})}
textPlacement="left"
/>
+
)
}
+
+export default ViewAllLink
diff --git a/src/views/Home/Sidebar/index.tsx b/src/views/Home/Sidebar/index.tsx
index 4d784ce48c..0d4f90118c 100644
--- a/src/views/Home/Sidebar/index.tsx
+++ b/src/views/Home/Sidebar/index.tsx
@@ -11,16 +11,21 @@ export default () => (
+
+
+
+
+
>
)
diff --git a/src/views/Me/AppreciationTabs/index.tsx b/src/views/Me/AppreciationTabs/index.tsx
index 803d77a1e8..7147bd7b36 100644
--- a/src/views/Me/AppreciationTabs/index.tsx
+++ b/src/views/Me/AppreciationTabs/index.tsx
@@ -57,6 +57,7 @@ const AppreciationTabs: React.FC & {
{activity.appreciationsReceivedTotal}
+
>
)
diff --git a/src/views/Me/AppreciationsReceived/index.tsx b/src/views/Me/AppreciationsReceived/index.tsx
index e6a6751031..b713d36492 100644
--- a/src/views/Me/AppreciationsReceived/index.tsx
+++ b/src/views/Me/AppreciationsReceived/index.tsx
@@ -1,16 +1,15 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import { Footer, Head, InfiniteScroll, Spinner } from '~/components'
import EmptyAppreciation from '~/components/Empty/EmptyAppreciation'
-import { Query } from '~/components/GQL'
import { Transaction } from '~/components/TransactionDigest'
import { ANALYTICS_EVENTS, TEXT } from '~/common/enums'
import { analytics, mergeConnections } from '~/common/utils'
import AppreciationTabs from '../AppreciationTabs'
+import { MeAppreciationsReceived } from './__generated__/MeAppreciationsReceived'
import styles from './styles.css'
const ME_APPRECIATED_RECEIVED = gql`
@@ -40,81 +39,81 @@ const ME_APPRECIATED_RECEIVED = gql`
`
const AppreciationsReceived = () => {
- return (
-
-
-
+ const { data, loading, fetchMore } = useQuery(
+ ME_APPRECIATED_RECEIVED
+ )
-
- {({ data, loading, error, fetchMore }: QueryResult) => {
- if (loading) {
- return
- }
+ if (loading) {
+ return
+ }
- const connectionPath = 'viewer.activity.appreciationsReceived'
- const { edges, pageInfo } = _get(data, connectionPath, {})
- const loadMore = () => {
- analytics.trackEvent(ANALYTICS_EVENTS.LOAD_MORE, {
- type: 'appreciationsReceived',
- location: edges.length
- })
- return fetchMore({
- variables: {
- after: pageInfo.endCursor
- },
- updateQuery: (previousResult, { fetchMoreResult }) =>
- mergeConnections({
- oldData: previousResult,
- newData: fetchMoreResult,
- path: connectionPath
- })
- })
- }
+ if (!data || !data.viewer) {
+ return null
+ }
- if (!edges || edges.length <= 0) {
- return (
- <>
-
-
- >
- )
- }
+ const connectionPath = 'viewer.activity.appreciationsReceived'
+ const { edges, pageInfo } = data.viewer.activity.appreciationsReceived
- return (
- <>
-
-
-
- {edges.map(
- ({ node, cursor }: { node: any; cursor: any }) => (
- -
-
-
- )
- )}
-
-
- >
- )
- }}
-
-
+ if (!edges || edges.length <= 0 || !pageInfo) {
+ return (
+ <>
+
+
+ >
+ )
+ }
-
+ const loadMore = () => {
+ analytics.trackEvent(ANALYTICS_EVENTS.LOAD_MORE, {
+ type: 'appreciationsReceived',
+ location: edges.length
+ })
+ return fetchMore({
+ variables: {
+ after: pageInfo.endCursor
+ },
+ updateQuery: (previousResult, { fetchMoreResult }) =>
+ mergeConnections({
+ oldData: previousResult,
+ newData: fetchMoreResult,
+ path: connectionPath
+ })
+ })
+ }
-
-
+ return (
+ <>
+
+
+
+ {edges.map(({ node, cursor }) => (
+ -
+
+
+ ))}
+
+
+ >
)
}
-export default AppreciationsReceived
+export default () => (
+
+
+
+
+
+
+
+
+
+
+
+)
diff --git a/src/views/Me/AppreciationsSent/index.tsx b/src/views/Me/AppreciationsSent/index.tsx
index 2367a10d5f..479b6f08d7 100644
--- a/src/views/Me/AppreciationsSent/index.tsx
+++ b/src/views/Me/AppreciationsSent/index.tsx
@@ -1,16 +1,15 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import { Footer, Head, InfiniteScroll, Spinner } from '~/components'
import EmptyAppreciation from '~/components/Empty/EmptyAppreciation'
-import { Query } from '~/components/GQL'
import { Transaction } from '~/components/TransactionDigest'
import { ANALYTICS_EVENTS, TEXT } from '~/common/enums'
import { analytics, mergeConnections } from '~/common/utils'
import AppreciationTabs from '../AppreciationTabs'
+import { MeAppreciationsSent } from './__generated__/MeAppreciationsSent'
import styles from './styles.css'
const ME_APPRECIATIONS_SENT = gql`
@@ -40,81 +39,81 @@ const ME_APPRECIATIONS_SENT = gql`
`
const AppreciationsSent = () => {
- return (
-
-
-
+ const { data, loading, fetchMore } = useQuery(
+ ME_APPRECIATIONS_SENT
+ )
-
- {({ data, loading, error, fetchMore }: QueryResult) => {
- if (loading) {
- return
- }
+ if (loading) {
+ return
+ }
- const connectionPath = 'viewer.activity.appreciationsSent'
- const { edges, pageInfo } = _get(data, connectionPath, {})
- const loadMore = () => {
- analytics.trackEvent(ANALYTICS_EVENTS.LOAD_MORE, {
- type: 'appreciationsSent',
- location: edges.length
- })
- return fetchMore({
- variables: {
- after: pageInfo.endCursor
- },
- updateQuery: (previousResult, { fetchMoreResult }) =>
- mergeConnections({
- oldData: previousResult,
- newData: fetchMoreResult,
- path: connectionPath
- })
- })
- }
+ if (!data || !data.viewer) {
+ return null
+ }
- if (!edges || edges.length <= 0) {
- return (
- <>
-
-
- >
- )
- }
+ const connectionPath = 'viewer.activity.appreciationsSent'
+ const { edges, pageInfo } = data.viewer.activity.appreciationsSent
- return (
- <>
-
-
-
- {edges.map(
- ({ node, cursor }: { node: any; cursor: any }) => (
- -
-
-
- )
- )}
-
-
- >
- )
- }}
-
-
+ if (!edges || edges.length <= 0 || !pageInfo) {
+ return (
+ <>
+
+
+ >
+ )
+ }
-
+ const loadMore = () => {
+ analytics.trackEvent(ANALYTICS_EVENTS.LOAD_MORE, {
+ type: 'appreciationsSent',
+ location: edges.length
+ })
+ return fetchMore({
+ variables: {
+ after: pageInfo.endCursor
+ },
+ updateQuery: (previousResult, { fetchMoreResult }) =>
+ mergeConnections({
+ oldData: previousResult,
+ newData: fetchMoreResult,
+ path: connectionPath
+ })
+ })
+ }
-
-
+ return (
+ <>
+
+
+
+ {edges.map(({ node, cursor }) => (
+ -
+
+
+ ))}
+
+
+ >
)
}
-export default AppreciationsSent
+export default () => (
+
+
+
+
+
+
+
+
+
+
+
+)
diff --git a/src/views/Me/DraftDetail/Content/index.tsx b/src/views/Me/DraftDetail/Content/index.tsx
index 96622329cb..ecbf6c3589 100644
--- a/src/views/Me/DraftDetail/Content/index.tsx
+++ b/src/views/Me/DraftDetail/Content/index.tsx
@@ -4,7 +4,8 @@ import { useContext, useEffect, useState } from 'react'
import { fragments as EditorFragments } from '~/components/Editor/fragments'
import { HeaderContext } from '~/components/GlobalHeader/Context'
-import { Mutation } from '~/components/GQL'
+import { useMutation } from '~/components/GQL'
+import { SingleFileUpload } from '~/components/GQL/mutations/__generated__/SingleFileUpload'
import UPLOAD_FILE from '~/components/GQL/mutations/uploadFile'
import { LanguageContext } from '~/components/Language'
import { Placeholder } from '~/components/Placeholder'
@@ -13,6 +14,7 @@ import { TEXT } from '~/common/enums'
import { translate } from '~/common/utils'
import { DraftDetailQuery_node_Draft } from '../__generated__/DraftDetailQuery'
+import { UpdateDraft } from './__generated__/UpdateDraft'
import styles from './styles.css'
const Editor = dynamic(() => import('~/components/Editor'), {
@@ -51,10 +53,8 @@ const fragments = {
const DraftContent: React.FC<{ draft: DraftDetailQuery_node_Draft }> & {
fragments: typeof fragments
} = ({ draft }) => {
- if (!process.browser) {
- return null
- }
-
+ const [updateDraft] = useMutation(UPDATE_DRAFT)
+ const [singleFileUpload] = useMutation(UPLOAD_FILE)
const { lang } = useContext(LanguageContext)
const { updateHeaderState } = useContext(HeaderContext)
const [title, setTitle] = useState(draft.title)
@@ -66,97 +66,91 @@ const DraftContent: React.FC<{ draft: DraftDetailQuery_node_Draft }> & {
setTitle(draft.title)
}, [draft.title])
+ if (!process.browser) {
+ return null
+ }
+
return (
-
- {(updateDraft: any) => (
- <>
-
-
- {(singleFileUpload: any, { loading: uploading }: any) => (
- {
- const result = await singleFileUpload({
- variables: {
- input: {
- type: 'embed',
- entityType: 'draft',
- entityId: draft.id,
- ...input
- }
- }
- })
- if (result) {
- const {
- data: {
- singleFileUpload: { id, path }
- }
- } = result
- return { id, path }
- } else {
- throw new Error('upload not successful')
- }
- }}
- draft={draft}
- onSave={async (newDraft: {
- title?: string | null
- content?: string | null
- coverAssetId?: string | null
- }) => {
- updateHeaderState({
- type: 'draft',
- state: 'saving',
- draftId
- })
- try {
- await updateDraft({
- variables: { id: draft.id, ...newDraft }
- })
- updateHeaderState({
- type: 'draft',
- state: 'saved',
- draftId
- })
- } catch (e) {
- updateHeaderState({
- type: 'draft',
- state: 'saveFailed',
- draftId
- })
- }
- }}
- />
- )}
-
+ }
+ })
+ const { id, path } = (data && data.singleFileUpload) || {}
+
+ if (id && path) {
+ return { id, path }
+ } else {
+ throw new Error('upload not successful')
+ }
+ }}
+ draft={draft}
+ onSave={async (newDraft: {
+ title?: string | null
+ content?: string | null
+ coverAssetId?: string | null
+ }) => {
+ updateHeaderState({
+ type: 'draft',
+ state: 'saving',
+ draftId
+ })
+ try {
+ await updateDraft({
+ variables: { id: draft.id, ...newDraft }
+ })
+ updateHeaderState({
+ type: 'draft',
+ state: 'saved',
+ draftId
+ })
+ } catch (e) {
+ updateHeaderState({
+ type: 'draft',
+ state: 'saveFailed',
+ draftId
+ })
+ }
+ }}
+ />
-
- >
- )}
-
+
+ >
)
}
diff --git a/src/views/Me/DraftDetail/PublishState/PendingState.tsx b/src/views/Me/DraftDetail/PublishState/PendingState.tsx
index 628b56c683..265072b6bf 100644
--- a/src/views/Me/DraftDetail/PublishState/PendingState.tsx
+++ b/src/views/Me/DraftDetail/PublishState/PendingState.tsx
@@ -1,7 +1,8 @@
-import { Query } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import { Translate } from '~/components'
import { PublishStateDraft } from '~/components/GQL/fragments/__generated__/PublishStateDraft'
+import { DraftPublishState } from '~/components/GQL/queries/__generated__/DraftPublishState'
import DRAFT_PUBLISH_STATE from '~/components/GQL/queries/draftPublishState'
import { useCountdown } from '~/components/Hook'
import { Toast } from '~/components/Toast'
@@ -16,41 +17,38 @@ const PendingState = ({ draft }: { draft: PublishStateDraft }) => {
} = useCountdown({ timeLeft: Date.parse(scheduledAt) - Date.now() })
const isPublishing = !scheduledAt || !timeLeft || timeLeft <= 0
+ useQuery(DRAFT_PUBLISH_STATE, {
+ variables: { id: draft.id },
+ pollInterval: 1000 * 2,
+ errorPolicy: 'none',
+ fetchPolicy: 'network-only',
+ skip: !process.browser || !isPublishing
+ })
+
return (
-
- {() => (
-
- ) : (
-
- )
- }
- content={
-
- }
- buttonPlacement="bottom"
+
+ ) : (
+
+ )
+ }
+ content={
+
- )}
-
+ }
+ buttonPlacement="bottom"
+ />
)
}
diff --git a/src/views/Me/DraftDetail/PublishState/index.tsx b/src/views/Me/DraftDetail/PublishState/index.tsx
index 9b2aee73e5..d515a45d47 100644
--- a/src/views/Me/DraftDetail/PublishState/index.tsx
+++ b/src/views/Me/DraftDetail/PublishState/index.tsx
@@ -14,8 +14,11 @@ const PublishState = ({ draft }: { draft: PublishStateDraft }) => {
return (
{isPending && }
+
{isError && }
+
{isPublished && }
+
)
diff --git a/src/views/Me/DraftDetail/Sidebar/AddCover/index.tsx b/src/views/Me/DraftDetail/Sidebar/AddCover/index.tsx
index f4df115e92..79c5b7632a 100644
--- a/src/views/Me/DraftDetail/Sidebar/AddCover/index.tsx
+++ b/src/views/Me/DraftDetail/Sidebar/AddCover/index.tsx
@@ -5,10 +5,11 @@ import { useContext } from 'react'
import { Translate } from '~/components'
import { HeaderContext } from '~/components/GlobalHeader/Context'
-import { Mutation } from '~/components/GQL'
+import { useMutation } from '~/components/GQL'
import Collapsable from '../Collapsable'
import { AddCoverDraft } from './__generated__/AddCoverDraft'
+import { UpdateDraftCover } from './__generated__/UpdateDraftCover'
import styles from './styles.css'
const fragments = {
@@ -77,6 +78,7 @@ const CoverList = ({
}
const AddCover = ({ draft }: { draft: AddCoverDraft }) => {
+ const [update] = useMutation(UPDATE_COVER)
const { updateHeaderState } = useContext(HeaderContext)
const { id: draftId, cover, assets } = draft
const imageAssets = assets.filter(
@@ -101,19 +103,17 @@ const AddCover = ({ draft }: { draft: AddCoverDraft }) => {
zh_hans="选择一張圖片作為封面"
/>
+
-
- {(update: any) => (
-
- )}
-
+
+
)
diff --git a/src/views/Me/DraftDetail/Sidebar/AddTags/SearchTags.tsx b/src/views/Me/DraftDetail/Sidebar/AddTags/SearchTags.tsx
index 7281c7c66a..671aef085b 100644
--- a/src/views/Me/DraftDetail/Sidebar/AddTags/SearchTags.tsx
+++ b/src/views/Me/DraftDetail/Sidebar/AddTags/SearchTags.tsx
@@ -1,8 +1,7 @@
import gql from 'graphql-tag'
-import _debounce from 'lodash/debounce'
-import _get from 'lodash/get'
import { useContext, useState } from 'react'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
+import { useDebounce } from 'use-debounce/lib'
import {
Dropdown,
@@ -12,8 +11,8 @@ import {
Spinner,
Translate
} from '~/components'
-import { Query } from '~/components/GQL'
+import { INPUT_DEBOUNCE } from '~/common/enums'
import { numAbbr, translate } from '~/common/utils'
import {
@@ -83,7 +82,9 @@ const DropdownContent = ({
))}
+
{tags && tags.length > 0 && }
+
+
>
)
-const debouncedSetQuerySearch = _debounce((value, setQuerySearch) => {
- setQuerySearch(value)
-}, 300)
-
const SearchTags = ({ addTag }: { addTag: (tag: string) => void }) => {
const { lang } = useContext(LanguageContext)
const [search, setSearch] = useState('')
- const [querySearch, setQuerySearch] = useState('')
+ const [debouncedSearch] = useDebounce(search, INPUT_DEBOUNCE)
const [instance, setInstance] = useState(null)
const hideDropdown = () => {
if (instance) {
@@ -120,68 +118,59 @@ const SearchTags = ({ addTag }: { addTag: (tag: string) => void }) => {
}
const showDropdown = () => {
if (instance) {
- setTimeout(() => {
- instance.show()
- }, 100) // unknown bug, needs set a timeout
+ instance.show()
}
}
+ const { data, loading } = useQuery(SEARCH_TAGS, {
+ variables: { search: debouncedSearch },
+ skip: !debouncedSearch
+ })
+
return (
<>
- node
+ ) as SearchTagsQuery_search_edges_node_Tag[]
+ }
+ addTag={(tag: string) => {
+ addTag(tag)
+ setSearch('')
+ }}
+ hideDropdown={hideDropdown}
+ />
+ }
>
- {({ data, loading }: QueryResult & { data: SearchTagsQuery }) => {
- return (
- node
- )}
- addTag={(tag: string) => {
- addTag(tag)
- setSearch('')
- }}
- hideDropdown={hideDropdown}
- />
- }
- >
- {
- const value = e.target.value
- setSearch(value)
- debouncedSetQuerySearch(value, setQuerySearch)
- if (value) {
- showDropdown()
- } else {
- hideDropdown()
- }
- }}
- onFocus={() => search && showDropdown()}
- onClick={() => search && showDropdown()}
- value={search}
- placeholder={translate({
- zh_hant: '增加標籤…',
- zh_hans: '增加标签…',
- lang
- })}
- />
-
- )
- }}
-
+ {
+ const value = e.target.value
+ setSearch(value)
+ if (value) {
+ showDropdown()
+ } else {
+ hideDropdown()
+ }
+ }}
+ onFocus={() => search && showDropdown()}
+ onClick={() => search && showDropdown()}
+ value={search}
+ placeholder={translate({
+ zh_hant: '增加標籤…',
+ zh_hans: '增加标签…',
+ lang
+ })}
+ />
+
+
>
)
diff --git a/src/views/Me/DraftDetail/Sidebar/AddTags/Tag.tsx b/src/views/Me/DraftDetail/Sidebar/AddTags/Tag.tsx
index e7e21a4c95..24ab64dc45 100644
--- a/src/views/Me/DraftDetail/Sidebar/AddTags/Tag.tsx
+++ b/src/views/Me/DraftDetail/Sidebar/AddTags/Tag.tsx
@@ -12,6 +12,7 @@ interface TagProps {
const Tag = ({ tag, deleteTag }: TagProps) => (
{tag}
+
deleteTag(tag)}>
(
style={{ width: 14, height: 14 }}
/>
+
)
diff --git a/src/views/Me/DraftDetail/Sidebar/AddTags/index.tsx b/src/views/Me/DraftDetail/Sidebar/AddTags/index.tsx
index ff3ede04ad..1e98fcc2c8 100644
--- a/src/views/Me/DraftDetail/Sidebar/AddTags/index.tsx
+++ b/src/views/Me/DraftDetail/Sidebar/AddTags/index.tsx
@@ -5,10 +5,11 @@ import { useContext } from 'react'
import { Translate } from '~/components'
import { HeaderContext } from '~/components/GlobalHeader/Context'
-import { Mutation } from '~/components/GQL'
+import { useMutation } from '~/components/GQL'
import Collapsable from '../Collapsable'
import { AddTagsDraft } from './__generated__/AddTagsDraft'
+import { UpdateDraftTags } from './__generated__/UpdateDraftTags'
import SearchTags from './SearchTags'
import styles from './styles.css'
import Tag from './Tag'
@@ -34,8 +35,8 @@ const UPDATE_TAGS = gql`
`
const AddTags = ({ draft }: { draft: AddTagsDraft }) => {
+ const [updateTags] = useMutation(UPDATE_TAGS)
const { updateHeaderState } = useContext(HeaderContext)
-
const draftId = draft.id
const tags = draft.tags || []
const hasTags = tags.length > 0
@@ -45,6 +46,28 @@ const AddTags = ({ draft }: { draft: AddTagsDraft }) => {
'tags-container': true,
'u-area-disable': isPending || isPublished
})
+ const addTag = async (tag: string) => {
+ updateHeaderState({ type: 'draft', state: 'saving', draftId })
+ try {
+ await updateTags({
+ variables: { id: draft.id, tags: _uniq(tags.concat(tag)) }
+ })
+ updateHeaderState({ type: 'draft', state: 'saved', draftId })
+ } catch (e) {
+ updateHeaderState({ type: 'draft', state: 'saveFailed', draftId })
+ }
+ }
+ const deleteTag = async (tag: string) => {
+ updateHeaderState({ type: 'draft', state: 'saving', draftId })
+ try {
+ await updateTags({
+ variables: { id: draft.id, tags: tags.filter(it => it !== tag) }
+ })
+ updateHeaderState({ type: 'draft', state: 'saved', draftId })
+ } catch (e) {
+ updateHeaderState({ type: 'draft', state: 'saveFailed', draftId })
+ }
+ }
return (
{
/>
-
- {(updateTags: any) => {
- const addTag = async (tag: string) => {
- updateHeaderState({ type: 'draft', state: 'saving', draftId })
- try {
- await updateTags({
- variables: { id: draft.id, tags: _uniq(tags.concat(tag)) }
- })
- updateHeaderState({ type: 'draft', state: 'saved', draftId })
- } catch (e) {
- updateHeaderState({ type: 'draft', state: 'saveFailed', draftId })
- }
- }
- const deleteTag = async (tag: string) => {
- updateHeaderState({ type: 'draft', state: 'saving', draftId })
- try {
- await updateTags({
- variables: { id: draft.id, tags: tags.filter(it => it !== tag) }
- })
- updateHeaderState({ type: 'draft', state: 'saved', draftId })
- } catch (e) {
- updateHeaderState({ type: 'draft', state: 'saveFailed', draftId })
- }
- }
-
- return (
-
- {tags.map(tag => (
-
- ))}
-
-
- )
- }}
-
+
+ {tags.map(tag => (
+
+ ))}
+
+
diff --git a/src/views/Me/DraftDetail/Sidebar/Collapsable/index.tsx b/src/views/Me/DraftDetail/Sidebar/Collapsable/index.tsx
index b22801968e..076791facc 100644
--- a/src/views/Me/DraftDetail/Sidebar/Collapsable/index.tsx
+++ b/src/views/Me/DraftDetail/Sidebar/Collapsable/index.tsx
@@ -1,5 +1,4 @@
-import _get from 'lodash/get'
-import { FC, ReactNode, useState } from 'react'
+import { ReactNode, useState } from 'react'
import { Icon, TextIcon } from '~/components'
@@ -7,11 +6,10 @@ import ICON_COLLAPSE_BRANCH from '~/static/icons/collapse-branch.svg?sprite'
import styles from './styles.css'
-const Collapsable: FC<{ title: ReactNode; defaultCollapsed?: boolean }> = ({
- children,
- title,
- defaultCollapsed = true
-}) => {
+const Collapsable: React.FC<{
+ title: ReactNode
+ defaultCollapsed?: boolean
+}> = ({ children, title, defaultCollapsed = true }) => {
const [collapsed, toggleCollapse] = useState(defaultCollapsed)
return (
diff --git a/src/views/Me/DraftDetail/Sidebar/CollectArticles/index.tsx b/src/views/Me/DraftDetail/Sidebar/CollectArticles/index.tsx
index 815791dd19..6546cc9f97 100644
--- a/src/views/Me/DraftDetail/Sidebar/CollectArticles/index.tsx
+++ b/src/views/Me/DraftDetail/Sidebar/CollectArticles/index.tsx
@@ -1,18 +1,19 @@
import classNames from 'classnames'
import gql from 'graphql-tag'
-import _get from 'lodash/get'
import _uniq from 'lodash/uniq'
import dynamic from 'next/dynamic'
import { useContext } from 'react'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import { ArticleDigest, Spinner, Translate } from '~/components'
+import { DropdownDigestArticle } from '~/components/ArticleDigest/DropdownDigest/__generated__/DropdownDigestArticle'
import { HeaderContext } from '~/components/GlobalHeader/Context'
-import { Mutation, Query } from '~/components/GQL'
+import { QueryError, useMutation } from '~/components/GQL'
import Collapsable from '../Collapsable'
import { CollectArticlesDraft } from './__generated__/CollectArticlesDraft'
import { DraftCollectionQuery } from './__generated__/DraftCollectionQuery'
+import { SetDraftCollection } from './__generated__/SetDraftCollection'
import styles from './styles.css'
const CollectionEditor = dynamic(
@@ -78,9 +79,8 @@ const CollectArticles = ({ draft }: { draft: CollectArticlesDraft }) => {
container: true,
'u-area-disable': isPending || isPublished
})
-
- const handleCollectionChange = (setCollection: any) => async (
- articles: any[]
+ const handleCollectionChange = () => async (
+ articles: DropdownDigestArticle[]
) => {
updateHeaderState({
type: 'draft',
@@ -108,6 +108,20 @@ const CollectArticles = ({ draft }: { draft: CollectArticlesDraft }) => {
}
}
+ const [setCollection] = useMutation(SET_DRAFT_COLLECTION)
+ const { data, loading, error } = useQuery(
+ DRAFT_COLLECTION,
+ {
+ variables: { id: draftId }
+ }
+ )
+ const edges =
+ data &&
+ data.node &&
+ data.node.__typename === 'Draft' &&
+ data.node.collection &&
+ data.node.collection.edges
+
return (
}
@@ -121,29 +135,14 @@ const CollectArticles = ({ draft }: { draft: CollectArticlesDraft }) => {
-
- {({
- data,
- loading
- }: QueryResult & { data: DraftCollectionQuery }) => {
- const edges = _get(data, 'node.collection.edges')
+ {loading && }
- if (loading || !edges) {
- return
- }
+ {error && }
- return (
-
- {(setCollection: any) => (
- node)}
- onEdit={handleCollectionChange(setCollection)}
- />
- )}
-
- )
- }}
-
+ node)) || []}
+ onEdit={handleCollectionChange()}
+ />
diff --git a/src/views/Me/DraftDetail/Sidebar/DraftList/index.tsx b/src/views/Me/DraftDetail/Sidebar/DraftList/index.tsx
index b9aa0512e1..bb9dfcfd50 100644
--- a/src/views/Me/DraftDetail/Sidebar/DraftList/index.tsx
+++ b/src/views/Me/DraftDetail/Sidebar/DraftList/index.tsx
@@ -1,9 +1,7 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import { DraftDigest, Spinner, Translate } from '~/components'
-import { Query } from '~/components/GQL'
import { TEXT } from '~/common/enums'
@@ -30,31 +28,28 @@ const ME_DRAFTS_SIDEBAR = gql`
${DraftDigest.Sidebar.fragments.draft}
`
-const DraftList = ({ currentId }: { currentId: string }) => (
-
- }
- >
-
- {({ data, loading }: QueryResult & { data: MeDraftsSidebar }) => {
- const edges = _get(data, 'viewer.drafts.edges')
-
- if (loading || !edges) {
- return
- }
+const DraftList = ({ currentId }: { currentId: string }) => {
+ const { data, loading } = useQuery(ME_DRAFTS_SIDEBAR)
+ const edges = data && data.viewer && data.viewer.drafts.edges
- return edges
+ return (
+
+ }
+ >
+ {loading && }
+ {edges &&
+ edges
.filter(
({ node }: MeDraftsSidebar_viewer_drafts_edges) =>
node.id !== currentId
)
.map(({ node }: MeDraftsSidebar_viewer_drafts_edges, i: number) => (
- ))
- }}
-
-
-)
+ ))}
+
+ )
+}
export default DraftList
diff --git a/src/views/Me/DraftDetail/index.tsx b/src/views/Me/DraftDetail/index.tsx
index bf66a12b25..05e6a9345a 100644
--- a/src/views/Me/DraftDetail/index.tsx
+++ b/src/views/Me/DraftDetail/index.tsx
@@ -1,17 +1,16 @@
import gql from 'graphql-tag'
import { useRouter } from 'next/router'
import { useContext, useEffect } from 'react'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import { fragments as EditorFragments } from '~/components/Editor/fragments'
-import EmptyDraft from '~/components/Empty/EmptyDraft'
import { HeaderContext } from '~/components/GlobalHeader/Context'
-import { Query } from '~/components/GQL'
+import { QueryError } from '~/components/GQL'
import { Head } from '~/components/Head'
-import { Translate } from '~/components/Language'
import { PublishModal } from '~/components/Modal/PublishModal'
import { ModalInstance } from '~/components/ModalManager'
import { Placeholder } from '~/components/Placeholder'
+import Throw404 from '~/components/Throw404'
import { getQuery } from '~/common/utils'
@@ -40,57 +39,58 @@ const DRAFT_DETAIL = gql`
const DraftDetail = () => {
const router = useRouter()
const id = getQuery({ router, key: 'id' })
-
- if (!id) {
- return null
- }
-
const { updateHeaderState } = useContext(HeaderContext)
useEffect(() => {
updateHeaderState({ type: 'draft', state: '', draftId: id })
-
return () => updateHeaderState({ type: 'default' })
}, [])
+ const { data, loading, error } = useQuery(DRAFT_DETAIL, {
+ variables: { id }
+ })
+
+ if (error) {
+ return
+ }
+
+ if (!loading && (!data || !data.node || data.node.__typename !== 'Draft')) {
+ return
+ }
+
+ const draft =
+ data && data.node && data.node.__typename === 'Draft' && data.node
+
return (
-
- {({ data, loading, error }: QueryResult & { data: DraftDetailQuery }) => (
-
-
-
-
- {loading && }
- {!loading && data && data.node && (
- <>
-
-
- >
- )}
- {!loading && (error || (data && !data.node)) && (
-
- }
- />
- )}
-
-
-
-
-
- {(props: ModalInstanceProps) => (
-
- )}
-
-
-
-
+
+
+
+
+ {loading && }
+
+ {!loading && draft && (
+ <>
+
+
+ >
+ )}
+
+
+
+
+ {draft && (
+
+ {(props: ModalInstanceProps) => (
+
+ )}
+
)}
-
+
+
+
)
}
diff --git a/src/views/Me/Notifications/index.tsx b/src/views/Me/Notifications/index.tsx
index 080da86bf0..d778eb0f9f 100644
--- a/src/views/Me/Notifications/index.tsx
+++ b/src/views/Me/Notifications/index.tsx
@@ -1,6 +1,5 @@
-import _get from 'lodash/get'
import { useEffect } from 'react'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import {
Footer,
@@ -11,8 +10,10 @@ import {
Translate
} from '~/components'
import EmptyNotice from '~/components/Empty/EmptyNotice'
-import { Mutation, Query } from '~/components/GQL'
+import { useMutation } from '~/components/GQL'
+import { MarkAllNoticesAsRead } from '~/components/GQL/mutations/__generated__/MarkAllNoticesAsRead'
import MARK_ALL_NOTICES_AS_READ from '~/components/GQL/mutations/markAllNoticesAsRead'
+import { MeNotifications } from '~/components/GQL/queries/__generated__/MeNotifications'
import { ME_NOTIFICATIONS } from '~/components/GQL/queries/notice'
import updateViewerUnreadNoticeCount from '~/components/GQL/updates/viewerUnreadNoticeCount'
import NoticeDigest from '~/components/NoticeDigest'
@@ -21,7 +22,67 @@ import { mergeConnections } from '~/common/utils'
import styles from './styles.css'
-const Notifications = () => (
+const Notifications = () => {
+ const [markAllNoticesAsRead] = useMutation(
+ MARK_ALL_NOTICES_AS_READ,
+ {
+ update: updateViewerUnreadNoticeCount
+ }
+ )
+ const { data, loading, fetchMore } = useQuery<
+ MeNotifications,
+ { first: number; after?: number }
+ >(ME_NOTIFICATIONS, {
+ variables: { first: 20 }
+ })
+
+ useEffect(() => {
+ if (!loading) {
+ markAllNoticesAsRead()
+ }
+ }, [])
+
+ const connectionPath = 'viewer.notices'
+ const { edges, pageInfo } = (data && data.viewer && data.viewer.notices) || {}
+
+ if (loading) {
+ return
+ }
+
+ if (!edges || edges.length <= 0 || !pageInfo) {
+ return
+ }
+
+ const loadMore = () =>
+ fetchMore({
+ variables: {
+ first: 20,
+ after: pageInfo.endCursor
+ },
+ updateQuery: (previousResult, { fetchMoreResult }) =>
+ mergeConnections({
+ oldData: previousResult,
+ newData: fetchMoreResult,
+ path: connectionPath
+ })
+ })
+
+ return (
+
+
+ {edges.map(({ node, cursor }) => (
+ -
+
+
+ ))}
+
+
+
+
+ )
+}
+
+export default () => (
@@ -31,63 +92,7 @@ const Notifications = () => (
/>
-
- {({ data, loading, error, fetchMore }: QueryResult) => {
- if (loading) {
- return
- }
-
- const connectionPath = 'viewer.notices'
- const { edges, pageInfo } = _get(data, connectionPath, {})
- const loadMore = () =>
- fetchMore({
- variables: {
- first: 20,
- after: pageInfo.endCursor
- },
- updateQuery: (previousResult, { fetchMoreResult }) =>
- mergeConnections({
- oldData: previousResult,
- newData: fetchMoreResult,
- path: connectionPath
- })
- })
-
- if (!edges || edges.length <= 0) {
- return
- }
-
- return (
-
- {(markAllNoticesAsRead: any) => {
- useEffect(() => {
- markAllNoticesAsRead()
- }, [])
-
- return (
-
-
- {edges.map(
- ({ node, cursor }: { node: any; cursor: any }) => (
- -
-
-
- )
- )}
-
-
- )
- }}
-
- )
- }}
-
+
@@ -98,5 +103,3 @@ const Notifications = () => (
)
-
-export default Notifications
diff --git a/src/views/Me/Settings/Account/AccountSettings.tsx b/src/views/Me/Settings/Account/AccountSettings.tsx
index 988ddcfab7..d171774f71 100644
--- a/src/views/Me/Settings/Account/AccountSettings.tsx
+++ b/src/views/Me/Settings/Account/AccountSettings.tsx
@@ -1,4 +1,3 @@
-import _get from 'lodash/get'
import { useContext } from 'react'
import { Button, PageHeader, Translate } from '~/components'
@@ -51,7 +50,7 @@ const ChangePasswrodButton = () => (
)
-export default () => {
+const AccountSettings = () => {
const viewer = useContext(ViewerContext)
return (
@@ -89,7 +88,7 @@ export default () => {
zh_hans={TEXT.zh_hans.email}
/>
- {_get(viewer, 'info.email')}
+ {viewer.info.email}
@@ -122,3 +121,5 @@ export default () => {
)
}
+
+export default AccountSettings
diff --git a/src/views/Me/Settings/Account/LanguageSwitch.tsx b/src/views/Me/Settings/Account/LanguageSwitch.tsx
index 1b5fdd349d..bfeea96b9f 100644
--- a/src/views/Me/Settings/Account/LanguageSwitch.tsx
+++ b/src/views/Me/Settings/Account/LanguageSwitch.tsx
@@ -76,6 +76,7 @@ const DropdownContent: React.FC<{ hideDropdown: () => void }> = ({
{textMap.zh_hant}
+
void }> = ({
{textMap.zh_hans}
+
)
diff --git a/src/views/Me/Settings/Account/SettingsAccount.tsx b/src/views/Me/Settings/Account/SettingsAccount.tsx
index 45f0973a45..a5b1865444 100644
--- a/src/views/Me/Settings/Account/SettingsAccount.tsx
+++ b/src/views/Me/Settings/Account/SettingsAccount.tsx
@@ -1,5 +1,3 @@
-import _get from 'lodash/get'
-
import { Head } from '~/components'
import { TEXT } from '~/common/enums'
diff --git a/src/views/Me/Settings/Account/UISettings.tsx b/src/views/Me/Settings/Account/UISettings.tsx
index ff3c4dfdca..cfe085e868 100644
--- a/src/views/Me/Settings/Account/UISettings.tsx
+++ b/src/views/Me/Settings/Account/UISettings.tsx
@@ -1,5 +1,3 @@
-import _get from 'lodash/get'
-
import { PageHeader, Translate } from '~/components'
import { TEXT } from '~/common/enums'
diff --git a/src/views/Me/Settings/Account/WalletSettings.tsx b/src/views/Me/Settings/Account/WalletSettings.tsx
index 91d69d733a..2802cf3381 100644
--- a/src/views/Me/Settings/Account/WalletSettings.tsx
+++ b/src/views/Me/Settings/Account/WalletSettings.tsx
@@ -1,16 +1,15 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
import { useContext } from 'react'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import { Icon, PageHeader, TextIcon, Translate } from '~/components'
-import { Query } from '~/components/GQL'
import { ModalSwitch } from '~/components/ModalManager'
import { ViewerContext } from '~/components/Viewer'
import { TEXT } from '~/common/enums'
import ICON_ARROW_RIGHT_GREEN from '~/static/icons/arrow-right-green.svg?sprite'
+import { ViewerLikeInfo } from './__generated__/ViewerLikeInfo'
import styles from './styles.css'
const VIEWER_LIKE_INFO = gql`
@@ -39,7 +38,73 @@ const SetupLikerIdButton = () => (
const WalletSetting = () => {
const viewer = useContext(ViewerContext)
- const likerId = _get(viewer, 'likerId')
+ const likerId = viewer.likerId
+ const { data, loading, error } = useQuery(VIEWER_LIKE_INFO, {
+ errorPolicy: 'none'
+ })
+ const LIKE =
+ data && data.viewer && data.viewer.status && data.viewer.status.LIKE
+
+ if (loading || error || !LIKE) {
+ return null
+ }
+
+ const USDPrice = (LIKE.rateUSD * LIKE.total).toFixed(2)
+
+ return (
+
+ )
+}
+
+const WalletSettings = () => {
+ const viewer = useContext(ViewerContext)
+ const likerId = viewer.likerId
return (
@@ -61,75 +126,11 @@ const WalletSetting = () => {
-
- {({ data, loading, error }: QueryResult) => {
- const LIKE = _get(data, 'viewer.status.LIKE')
-
- if (loading || error || !LIKE) {
- return null
- }
-
- const USDPrice = (LIKE.rateUSD * LIKE.total).toFixed(2)
-
- return (
-
- )
- }}
-
+
)
}
-export default WalletSetting
+export default WalletSettings
diff --git a/src/views/Me/Settings/Blocked/SettingsBlocked.tsx b/src/views/Me/Settings/Blocked/SettingsBlocked.tsx
new file mode 100644
index 0000000000..588b9f7a16
--- /dev/null
+++ b/src/views/Me/Settings/Blocked/SettingsBlocked.tsx
@@ -0,0 +1,131 @@
+import gql from 'graphql-tag'
+import { useQuery } from 'react-apollo'
+
+import {
+ Head,
+ InfiniteScroll,
+ PageHeader,
+ Spinner,
+ Translate
+} from '~/components'
+import EmptyWarning from '~/components/Empty/EmptyWarning'
+import { QueryError } from '~/components/GQL'
+import { UserDigest } from '~/components/UserDigest'
+
+import { ANALYTICS_EVENTS, FEED_TYPE, TEXT } from '~/common/enums'
+import { analytics, mergeConnections } from '~/common/utils'
+
+import { ViewerBlockList } from './__generated__/ViewerBlockList'
+
+const VIEWER_BLOCK_LIST = gql`
+ query ViewerBlockList($after: String) {
+ viewer {
+ id
+ blockList(input: { first: 10, after: $after }) {
+ pageInfo {
+ startCursor
+ endCursor
+ hasNextPage
+ }
+ edges {
+ cursor
+ node {
+ ...UserDigestFullDescUser
+ }
+ }
+ }
+ }
+ }
+ ${UserDigest.FullDesc.fragments.user}
+`
+
+const SettingsBlocked = () => {
+ const { data, loading, error, fetchMore } = useQuery(
+ VIEWER_BLOCK_LIST
+ )
+
+ if (loading) {
+ return
+ }
+
+ if (error) {
+ return
+ }
+
+ const connectionPath = 'viewer.blockList'
+ const { edges, pageInfo } =
+ (data && data.viewer && data.viewer.blockList) || {}
+
+ const filteredUsers = (edges || []).filter(({ node }) => node.isBlocked)
+
+ if (!edges || edges.length <= 0 || filteredUsers.length <= 0 || !pageInfo) {
+ return (
+
+ }
+ />
+ )
+ }
+
+ const loadMore = () => {
+ analytics.trackEvent(ANALYTICS_EVENTS.LOAD_MORE, {
+ type: FEED_TYPE.ALL_AUTHORS,
+ location: edges.length
+ })
+ return fetchMore({
+ variables: {
+ after: pageInfo.endCursor
+ },
+ updateQuery: (previousResult, { fetchMoreResult }) =>
+ mergeConnections({
+ oldData: previousResult,
+ newData: fetchMoreResult,
+ path: connectionPath
+ })
+ })
+ }
+
+ return (
+
+
+ {filteredUsers.map(({ node, cursor }, i) => (
+ -
+ analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
+ type: FEED_TYPE.ALL_AUTHORS,
+ location: i
+ })
+ }
+ >
+
+
+ ))}
+
+
+ )
+}
+
+export default () => (
+ <>
+
+
+
+ }
+ is="h2"
+ />
+
+
+ >
+)
diff --git a/src/views/Me/Settings/Blocked/index.tsx b/src/views/Me/Settings/Blocked/index.tsx
new file mode 100644
index 0000000000..fb3fdca1cf
--- /dev/null
+++ b/src/views/Me/Settings/Blocked/index.tsx
@@ -0,0 +1,15 @@
+import SettingsTab from '../SettingsTab'
+import SettingsBlocked from './SettingsBlocked'
+
+export default () => (
+
+
+
+)
diff --git a/src/views/Me/Settings/Notification/SettingsNotification.tsx b/src/views/Me/Settings/Notification/SettingsNotification.tsx
index a41d6e0ba2..1de838ef6d 100644
--- a/src/views/Me/Settings/Notification/SettingsNotification.tsx
+++ b/src/views/Me/Settings/Notification/SettingsNotification.tsx
@@ -1,13 +1,13 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import { Head, PageHeader, Translate } from '~/components'
-import { Mutation, Query } from '~/components/GQL'
+import { useMutation } from '~/components/GQL'
import { Switch } from '~/components/Switch'
import { TEXT } from '~/common/enums'
+import { UpdateViewerNotification } from './__generated__/UpdateViewerNotification'
import { ViewerNotificationSettings } from './__generated__/ViewerNotificationSettings'
import styles from './styles.css'
@@ -125,126 +125,127 @@ const settingsMap = {
]
}
-const SettingsNotification = () => (
-
- {({ data }: QueryResult & { data: ViewerNotificationSettings }) => {
- const settings = _get(data, 'viewer.settings.notification', {})
-
- return (
-
- {(updateNotification: any) => {
- const onChange = (type: string) =>
- updateNotification({
- variables: {
- type,
- enabled: !settings[type]
- },
- optimisticResponse: {
- updateNotificationSetting: {
- id: data.viewer.id,
- settings: {
- notification: {
- [type]: !settings[type],
- __typename: 'NotificationSetting'
- },
- __typename: 'UserSettings'
- },
- __typename: 'User'
- }
- }
- })
-
- return (
- <>
-
-
-
-
-
- }
- is="h2"
- />
-
- {settingsMap.me.map(setting => (
-
- {setting.title}
- onChange(setting.key)}
- />
-
- ))}
-
-
-
- }
- is="h2"
- />
-
- {settingsMap.others.map(setting => (
-
- {setting.title}
- onChange(setting.key)}
- />
-
- ))}
-
-
-
-
-
- }
- is="h2"
- />
-
- {settingsMap.article.map(setting => (
-
- {setting.title}
- onChange(setting.key)}
- />
-
- ))}
-
-
-
- }
- is="h2"
- />
-
- {settingsMap.comment.map(setting => (
-
- {setting.title}
- onChange(setting.key)}
- />
-
- ))}
-
-
-
-
- >
- )
- }}
-
- )
- }}
-
-)
+const SettingsNotification = () => {
+ const [updateNotification] = useMutation(
+ UPDATE_VIEWER_NOTIFICATION
+ )
+ const { data } = useQuery(
+ VIEWER_NOTIFICATION_SETTINGS
+ )
+ const settings: any =
+ (data && data.viewer && data.viewer.settings.notification) || {}
+ const id = data && data.viewer && data.viewer.id
+
+ const onChange = (type: string) => {
+ if (!id) {
+ return
+ }
+ updateNotification({
+ variables: {
+ type,
+ enabled: !settings[type]
+ },
+ optimisticResponse: {
+ updateNotificationSetting: {
+ id,
+ settings: {
+ notification: {
+ ...settings,
+ [type]: !settings[type],
+ __typename: 'NotificationSetting'
+ },
+ __typename: 'UserSettings'
+ },
+ __typename: 'User'
+ }
+ }
+ })
+ }
+
+ return (
+ <>
+
+
+
+
+ }
+ is="h2"
+ />
+
+ {settingsMap.me.map(setting => (
+
+ {setting.title}
+ onChange(setting.key)}
+ />
+
+ ))}
+
+
+
+ }
+ is="h2"
+ />
+
+ {settingsMap.others.map(setting => (
+
+ {setting.title}
+ onChange(setting.key)}
+ />
+
+ ))}
+
+
+
+
+
+ }
+ is="h2"
+ />
+
+ {settingsMap.article.map(setting => (
+
+ {setting.title}
+ onChange(setting.key)}
+ />
+
+ ))}
+
+
+
+ }
+ is="h2"
+ />
+
+ {settingsMap.comment.map(setting => (
+
+ {setting.title}
+ onChange(setting.key)}
+ />
+
+ ))}
+
+
+
+
+ >
+ )
+}
export default SettingsNotification
diff --git a/src/views/Me/Settings/SettingsTab/index.tsx b/src/views/Me/Settings/SettingsTab/index.tsx
index babcf357c0..0e90eef85d 100644
--- a/src/views/Me/Settings/SettingsTab/index.tsx
+++ b/src/views/Me/Settings/SettingsTab/index.tsx
@@ -20,6 +20,7 @@ const SettingsTabs = () => {
+
@@ -32,6 +33,17 @@ const SettingsTabs = () => {
+
+
+
+
+
+
+
+
)
}
diff --git a/src/views/Misc/About/index.tsx b/src/views/Misc/About/index.tsx
index 0d6a8c16e5..8e64f9b525 100644
--- a/src/views/Misc/About/index.tsx
+++ b/src/views/Misc/About/index.tsx
@@ -12,8 +12,9 @@ import Reports from './Reports'
import Slogan from './Slogan'
import styles from './styles.css'
-export default () => {
+const About = () => {
const { updateHeaderState } = useContext(HeaderContext)
+
useEffect(() => {
updateHeaderState({ type: 'about', bgColor: 'transparent' })
return () => updateHeaderState({ type: 'default' })
@@ -37,3 +38,5 @@ export default () => {
)
}
+
+export default About
diff --git a/src/views/Misc/FAQ/index.tsx b/src/views/Misc/FAQ/index.tsx
index ce7d4903ad..893dc157f3 100644
--- a/src/views/Misc/FAQ/index.tsx
+++ b/src/views/Misc/FAQ/index.tsx
@@ -10,7 +10,7 @@ import { translate } from '~/common/utils'
import MiscTab from '../MiscTab'
import content from './content'
-export default () => {
+const FAQ = () => {
const { lang } = useContext(LanguageContext)
return (
@@ -50,3 +50,5 @@ export default () => {
)
}
+
+export default FAQ
diff --git a/src/views/Misc/Guide/index.tsx b/src/views/Misc/Guide/index.tsx
index dfa6e2f862..4777b0bc9a 100644
--- a/src/views/Misc/Guide/index.tsx
+++ b/src/views/Misc/Guide/index.tsx
@@ -9,7 +9,7 @@ import { translate } from '~/common/utils'
import MiscTab from '../MiscTab'
import content from './content'
-export default () => {
+const Guide = () => {
const { lang } = useContext(LanguageContext)
return (
@@ -41,8 +41,11 @@ export default () => {
className="u-content"
/>
+
)
}
+
+export default Guide
diff --git a/src/views/OAuth/Authorize/index.tsx b/src/views/OAuth/Authorize/index.tsx
index b0b8c9570c..6fe5169f08 100644
--- a/src/views/OAuth/Authorize/index.tsx
+++ b/src/views/OAuth/Authorize/index.tsx
@@ -4,16 +4,16 @@ import Link from 'next/link'
import { useRouter } from 'next/router'
import queryString from 'query-string'
import { useContext } from 'react'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import { LanguageContext, Modal, Spinner, Translate } from '~/components'
-import { Query } from '~/components/GQL'
import OAuth from '~/components/OAuth'
import Throw404 from '~/components/Throw404'
import { PATHS, TEXT } from '~/common/enums'
import { appendTarget, getQuery, toReadableScope } from '~/common/utils'
+import { OAuthClientInfo } from './__generated__/OAuthClientInfo'
import styles from './styles.css'
const {
@@ -44,118 +44,114 @@ const OAuthAuthorize = () => {
const scope = getQuery({ router, key: 'scope' })
const redirectUri = getQuery({ router, key: 'redirect_uri' })
+ const { data, loading } = useQuery(OAUTH_CLIENT_INFO, {
+ variables: { id: clientId }
+ })
+
if (!clientId) {
return
}
- return (
-
- {({ data, loading }: QueryResult) => {
- if (loading) {
- return (
-
-
-
-
-
- )
- }
+ if (loading) {
+ return (
+
+
+
+
+
+ )
+ }
- if (!data || !data.oauthClient || !data.oauthClient.id) {
- return
- }
+ if (!data || !data.oauthClient || !data.oauthClient.id) {
+ return
+ }
- const { avatar, website, name, scope: scopes } = data.oauthClient
+ const { avatar, website, name, scope: scopes } = data.oauthClient
- return (
-
-
-
- {name}
+ return (
+
+
+
+ {name}
+
+
+ >
+ }
+ titleAlign="left"
+ >
+
+
+ {state && }
+ {scope && }
+ {redirectUri && (
+
+ )}
+
+
+
+
+ -
+
+
+ {scopes &&
+ scopes.map((s: any) => {
+ const readableScope = toReadableScope({
+ scope: s,
+ lang
+ })
+
+ if (!readableScope) {
+ return null
+ }
+
+ return - {readableScope}
+ })}
+
+
+
+
+
+
+
+
+ {/* FIXME: only render at CSR to get correct `appendTarget` */}
+ {process.browser && (
+
+
+
-
- >
- }
- titleAlign="left"
- >
-
-
- {state && }
- {scope && }
- {redirectUri && (
-
- )}
-
-
-
-
- -
-
-
- {scopes.map((s: any) => {
- const readableScope = toReadableScope({
- scope: s,
- lang
- })
-
- if (!readableScope) {
- return null
- }
-
- return - {readableScope}
- })}
-
-
-
-
-
-
-
-
- {/* FIXME: only render at CSR to get correct `appendTarget` */}
- {process.browser && (
-
-
-
-
-
- )}
-
-
-
-
-
-
-
-
- )
- }}
-
+
+ )}
+
+
+
+
+
+
+
+
+
)
}
diff --git a/src/views/OAuth/Callback/Failure/index.tsx b/src/views/OAuth/Callback/Failure/index.tsx
index fe64211510..0d61f0e5c2 100644
--- a/src/views/OAuth/Callback/Failure/index.tsx
+++ b/src/views/OAuth/Callback/Failure/index.tsx
@@ -62,6 +62,7 @@ const OAuthCallbackFailure = () => {
)}
+
)
diff --git a/src/views/OAuth/Callback/Success/index.tsx b/src/views/OAuth/Callback/Success/index.tsx
index 228cbe2c16..6f5d0af5ed 100644
--- a/src/views/OAuth/Callback/Success/index.tsx
+++ b/src/views/OAuth/Callback/Success/index.tsx
@@ -40,6 +40,7 @@ const OAuthCallbackSuccess = () => {
+
)
diff --git a/src/views/Search/SearchArticles/index.tsx b/src/views/Search/SearchArticles/index.tsx
index 1340133365..2466f72702 100644
--- a/src/views/Search/SearchArticles/index.tsx
+++ b/src/views/Search/SearchArticles/index.tsx
@@ -1,6 +1,6 @@
import gql from 'graphql-tag'
import _get from 'lodash/get'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import {
ArticleDigest,
@@ -8,10 +8,9 @@ import {
LoadMore,
PageHeader,
Placeholder,
- Responsive,
Translate
} from '~/components'
-import { Query } from '~/components/GQL'
+import { useResponsive } from '~/components/Hook'
import { ANALYTICS_EVENTS, FEED_TYPE, TEXT } from '~/common/enums'
import { analytics, mergeConnections } from '~/common/utils'
@@ -48,108 +47,93 @@ const SEARCH_ARTICLES = gql`
`
const SearchArticles = ({ q }: { q: string }) => {
- return (
-
- {({
- data,
- loading,
- error,
- fetchMore
- }: QueryResult & { data: SeachArticles }) => {
- if (loading && !_get(data, 'search')) {
- return
- }
+ const isMediumUp = useResponsive({ type: 'medium-up' })()
+ const { data, loading, fetchMore } = useQuery(
+ SEARCH_ARTICLES,
+ {
+ variables: { key: q, first: 10 },
+ notifyOnNetworkStatusChange: true
+ }
+ )
- const connectionPath = 'search'
- const { edges, pageInfo } = _get(data, connectionPath, {})
- const loadMore = () => {
- analytics.trackEvent(ANALYTICS_EVENTS.LOAD_MORE, {
- type: FEED_TYPE.SEARCH_ARTICLE,
- location: edges.length,
- entrance: q
- })
- return fetchMore({
- variables: {
- after: pageInfo.endCursor
- },
- updateQuery: (previousResult, { fetchMoreResult }) =>
- mergeConnections({
- oldData: previousResult,
- newData: fetchMoreResult,
- path: connectionPath
- })
- })
- }
+ if (loading && !(data && data.search)) {
+ return
+ }
- if (!edges || edges.length <= 0) {
- return (
-
- }
- />
- )
+ const connectionPath = 'search'
+ const { edges, pageInfo } = (data && data.search) || {}
+
+ if (!edges || edges.length <= 0 || !pageInfo) {
+ return (
+
}
+ />
+ )
+ }
+
+ const loadMore = () => {
+ analytics.trackEvent(ANALYTICS_EVENTS.LOAD_MORE, {
+ type: FEED_TYPE.SEARCH_ARTICLE,
+ location: edges.length,
+ entrance: q
+ })
+ return fetchMore({
+ variables: {
+ after: pageInfo.endCursor
+ },
+ updateQuery: (previousResult, { fetchMoreResult }) =>
+ mergeConnections({
+ oldData: previousResult,
+ newData: fetchMoreResult,
+ path: connectionPath
+ })
+ })
+ }
- return (
-
- {(match: boolean) => (
-
+
+ }
+ />
+
+ {edges.map(
+ ({ node, cursor }, i) =>
+ node.__typename === 'Article' && (
+ -
+ analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
+ type: FEED_TYPE.SEARCH_ARTICLE,
+ location: i,
+ entrance: q
+ })
+ }
>
-
- }
- />
-
- {edges.map(
- (
- { node, cursor }: { node: any; cursor: any },
- i: number
- ) => (
- -
- analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
- type: FEED_TYPE.SEARCH_ARTICLE,
- location: i,
- entrance: q
- })
- }
- >
-
-
- )
- )}
-
+
+
+ )
+ )}
+
- {!match && pageInfo.hasNextPage && (
-
- )}
-
- )}
-
- )
- }}
-
+ {!isMediumUp && pageInfo.hasNextPage && (
+
+ )}
+
)
}
diff --git a/src/views/Search/SearchPageHeader/index.tsx b/src/views/Search/SearchPageHeader/index.tsx
index bc5ff1a956..8529ced9cd 100644
--- a/src/views/Search/SearchPageHeader/index.tsx
+++ b/src/views/Search/SearchPageHeader/index.tsx
@@ -1,4 +1,3 @@
-import _get from 'lodash/get'
import Link from 'next/link'
import { Icon, TextIcon, Translate } from '~/components'
@@ -48,9 +47,11 @@ const SearchPageHeader = ({
{q}
+
+
)
diff --git a/src/views/Search/SearchTags/index.tsx b/src/views/Search/SearchTags/index.tsx
index 687397b4f2..c374fff5e9 100644
--- a/src/views/Search/SearchTags/index.tsx
+++ b/src/views/Search/SearchTags/index.tsx
@@ -1,6 +1,5 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import {
InfiniteScroll,
@@ -9,14 +8,16 @@ import {
Tag,
Translate
} from '~/components'
-import { Query } from '~/components/GQL'
import { ANALYTICS_EVENTS, FEED_TYPE, TEXT } from '~/common/enums'
import { analytics, mergeConnections } from '~/common/utils'
import EmptySearch from '../EmptySearch'
import ViewAll from '../ViewAll'
-import { SeachTags } from './__generated__/SeachTags'
+import {
+ SeachTags,
+ SeachTags_search_edges_node_Tag
+} from './__generated__/SeachTags'
import styles from './styles.css'
const SEARCH_TAGS = gql`
@@ -69,123 +70,117 @@ const EmptySearchResult = () => {
}
const SearchTag = ({ q, isAggregate }: { q: string; isAggregate: boolean }) => {
- return (
- <>
-
- {({
- data,
- loading,
- error,
- fetchMore
- }: QueryResult & { data: SeachTags }) => {
- if (loading) {
- return
- }
+ const { data, loading, fetchMore } = useQuery(SEARCH_TAGS, {
+ variables: { key: q, first: isAggregate ? 3 : 20 }
+ })
- const connectionPath = 'search'
- const { edges, pageInfo } = _get(data, connectionPath, {})
- const loadMore = () => {
- analytics.trackEvent(ANALYTICS_EVENTS.LOAD_MORE, {
- type: FEED_TYPE.SEARCH_TAG,
- location: edges.length
- })
- return fetchMore({
- variables: {
- after: pageInfo.endCursor
- },
- updateQuery: (previousResult, { fetchMoreResult }) =>
- mergeConnections({
- oldData: previousResult,
- newData: fetchMoreResult,
- path: connectionPath
- })
- })
- }
- const leftEdges = edges.filter((_: any, i: number) => i % 2 === 0)
- const rightEdges = edges.filter((_: any, i: number) => i % 2 === 1)
+ if (loading) {
+ return
+ }
- if (data.search.edges.length <= 0) {
- return isAggregate ? null :
- }
+ const connectionPath = 'search'
+ const { edges, pageInfo } = (data && data.search) || {}
- if (isAggregate) {
- return (
-
-
-
- {data.search.edges.map(
- (
- { node, cursor }: { node: any; cursor: any },
- i: number
- ) => (
- -
- analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
- type: FEED_TYPE.SEARCH_TAG,
- location: i,
- entrance: q
- })
- }
- >
-
-
- )
- )}
-
-
- )
- }
+ if (!edges || edges.length <= 0 || !pageInfo) {
+ return null
+ }
+
+ const loadMore = () => {
+ analytics.trackEvent(ANALYTICS_EVENTS.LOAD_MORE, {
+ type: FEED_TYPE.SEARCH_TAG,
+ location: edges.length
+ })
+ return fetchMore({
+ variables: {
+ after: pageInfo.endCursor
+ },
+ updateQuery: (previousResult, { fetchMoreResult }) =>
+ mergeConnections({
+ oldData: previousResult,
+ newData: fetchMoreResult,
+ path: connectionPath
+ })
+ })
+ }
+ const leftEdges = edges.filter((_, i) => i % 2 === 0)
+ const rightEdges = edges.filter((_, i) => i % 2 === 1)
- return (
-
-
+ }
+
+ if (isAggregate) {
+ return (
+
+
+
+
+ {edges.map(({ node, cursor }, i) => (
+ -
+ analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
+ type: FEED_TYPE.SEARCH_TAG,
+ location: i,
+ entrance: q
+ })
+ }
+ >
+
+
+ ))}
+
+
+
+
+ )
+ }
+
+ return (
+
+
+
+
+
+ {leftEdges.map(({ node, cursor }) => (
+ -
+
+
+ ))}
+
+
+ {rightEdges.map(({ node, cursor }, i) => (
+ -
+ analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
+ type: FEED_TYPE.SEARCH_TAG,
+ location: i,
+ entrance: q
+ })
+ }
>
-
-
-
- {leftEdges.map(
- ({ node, cursor }: { node: any; cursor: any }) => (
- -
-
-
- )
- )}
-
-
- {rightEdges.map(
- (
- { node, cursor }: { node: any; cursor: any },
- i: number
- ) => (
- -
- analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
- type: FEED_TYPE.SEARCH_TAG,
- location: i,
- entrance: q
- })
- }
- >
-
-
- )
- )}
-
-
-
-
- )
- }}
-
+
+
+ ))}
+
+
+
+
- >
+
)
}
diff --git a/src/views/Search/SearchUsers/index.tsx b/src/views/Search/SearchUsers/index.tsx
index 79f8b4d13c..769a490902 100644
--- a/src/views/Search/SearchUsers/index.tsx
+++ b/src/views/Search/SearchUsers/index.tsx
@@ -1,6 +1,5 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import {
InfiniteScroll,
@@ -9,7 +8,6 @@ import {
Translate,
UserDigest
} from '~/components'
-import { Query } from '~/components/GQL'
import { ANALYTICS_EVENTS, FEED_TYPE, TEXT } from '~/common/enums'
import { analytics, mergeConnections } from '~/common/utils'
@@ -74,82 +72,70 @@ const SearchUser = ({
q: string
isAggregate: boolean
}) => {
- return (
- <>
-
- {({
- data,
- loading,
- error,
- fetchMore
- }: QueryResult & { data: SeachUsers }) => {
- if (loading) {
- return
- }
+ const { data, loading, fetchMore } = useQuery(SEARCH_USERS, {
+ variables: { key: q, first: isAggregate ? 3 : 10 }
+ })
- const connectionPath = 'search'
- const { edges, pageInfo } = _get(data, connectionPath, {})
- const loadMore = () => {
- analytics.trackEvent(ANALYTICS_EVENTS.LOAD_MORE, {
- type: FEED_TYPE.SEARCH_USER,
- location: edges.length,
- entrance: q
- })
- return fetchMore({
- variables: {
- after: pageInfo.endCursor
- },
- updateQuery: (previousResult, { fetchMoreResult }) =>
- mergeConnections({
- oldData: previousResult,
- newData: fetchMoreResult,
- path: connectionPath
- })
- })
- }
+ if (loading) {
+ return
+ }
- if (!edges || edges.length <= 0) {
- return isAggregate ? null :
- }
+ const connectionPath = 'search'
+ const { edges, pageInfo } = (data && data.search) || {}
+
+ if (!edges || edges.length <= 0 || !pageInfo) {
+ return isAggregate ? null :
+ }
+
+ const loadMore = () => {
+ analytics.trackEvent(ANALYTICS_EVENTS.LOAD_MORE, {
+ type: FEED_TYPE.SEARCH_USER,
+ location: edges.length,
+ entrance: q
+ })
+ return fetchMore({
+ variables: {
+ after: pageInfo.endCursor
+ },
+ updateQuery: (previousResult, { fetchMoreResult }) =>
+ mergeConnections({
+ oldData: previousResult,
+ newData: fetchMoreResult,
+ path: connectionPath
+ })
+ })
+ }
+
+ return (
+
+
+
+
+ {edges.map(
+ ({ node, cursor }, i) =>
+ node.__typename === 'User' && (
+ -
+ analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
+ type: FEED_TYPE.SEARCH_USER,
+ location: i,
+ entrance: q
+ })
+ }
+ >
+
+
+ )
+ )}
+
+
- return (
-
-
-
-
- {edges.map(
- (
- { node, cursor }: { node: any; cursor: any },
- i: number
- ) => (
- -
- analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
- type: FEED_TYPE.SEARCH_USER,
- location: i,
- entrance: q
- })
- }
- >
-
-
- )
- )}
-
-
-
- )
- }}
-
- >
+
)
}
diff --git a/src/views/Search/index.tsx b/src/views/Search/index.tsx
index 05200f916c..8ea9f58049 100644
--- a/src/views/Search/index.tsx
+++ b/src/views/Search/index.tsx
@@ -1,6 +1,7 @@
import { useRouter } from 'next/router'
-import { Footer, Head, Responsive, SearchBar } from '~/components'
+import { Footer, Head, SearchBar } from '~/components'
+import { useResponsive } from '~/components/Hook'
import { getQuery } from '~/common/utils'
@@ -30,6 +31,7 @@ const EmptySeachPage = () => {
}
const Search = () => {
+ const isMedium = useResponsive({ type: 'medium' })()
const router = useRouter()
const type = getQuery({ router, key: 'type' })
const q = getQuery({ router, key: 'q' })
@@ -46,11 +48,11 @@ const Search = () => {
-
+ {isMedium && (
-
+ )}
@@ -67,6 +69,7 @@ const Search = () => {
+
)
diff --git a/src/views/TagDetail/index.tsx b/src/views/TagDetail/index.tsx
index 2256a45cb1..a18aacf174 100644
--- a/src/views/TagDetail/index.tsx
+++ b/src/views/TagDetail/index.tsx
@@ -1,7 +1,6 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
import { useRouter } from 'next/router'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import {
ArticleDigest,
@@ -12,8 +11,7 @@ import {
Placeholder
} from '~/components'
import EmptyTag from '~/components/Empty/EmptyTag'
-import { Query } from '~/components/GQL'
-import Throw404 from '~/components/Throw404'
+import { QueryError } from '~/components/GQL'
import { ANALYTICS_EVENTS, FEED_TYPE } from '~/common/enums'
import { analytics, mergeConnections } from '~/common/utils'
@@ -54,94 +52,87 @@ const TAG_DETAIL = gql`
const TagDetail = () => {
const router = useRouter()
- if (!router || !router.query || !router.query.id) {
+ const { data, loading, error, fetchMore } = useQuery(
+ TAG_DETAIL,
+ {
+ variables: { id: router.query.id }
+ }
+ )
+
+ if (loading) {
+ return
+ }
+
+ if (error) {
+ return
+ }
+
+ if (!data || !data.node || data.node.__typename !== 'Tag') {
+ return
+ }
+
+ const id = data.node.id
+ const connectionPath = 'node.articles'
+ const { edges, pageInfo } = data.node.articles || {}
+
+ if (!edges || edges.length <= 0 || !pageInfo) {
return
}
+ const loadMore = () => {
+ analytics.trackEvent(ANALYTICS_EVENTS.LOAD_MORE, {
+ type: FEED_TYPE.TAG_DETAIL,
+ location: edges.length,
+ entrance: id
+ })
+ return fetchMore({
+ variables: {
+ after: pageInfo.endCursor
+ },
+ updateQuery: (previousResult, { fetchMoreResult }) =>
+ mergeConnections({
+ oldData: previousResult,
+ newData: fetchMoreResult,
+ path: connectionPath
+ })
+ })
+ }
+
return (
-
-
-
- {({
- data,
- loading,
- error,
- fetchMore
- }: QueryResult & { data: TagDetailArticles }) => {
- if (loading) {
- return
- }
+ <>
+
- if (!data.node) {
- return
- }
+
- const connectionPath = 'node.articles'
- const { edges, pageInfo } = _get(data, connectionPath, {})
- const loadMore = () => {
- analytics.trackEvent(ANALYTICS_EVENTS.LOAD_MORE, {
- type: FEED_TYPE.TAG_DETAIL,
- location: edges.length,
- entrance: data.node.id
- })
- return fetchMore({
- variables: {
- after: pageInfo.endCursor
- },
- updateQuery: (previousResult, { fetchMoreResult }) =>
- mergeConnections({
- oldData: previousResult,
- newData: fetchMoreResult,
- path: connectionPath
+
+
+
+ {edges.map(({ node, cursor }, i) => (
+ -
+ analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
+ type: FEED_TYPE.TAG_DETAIL,
+ location: i,
+ entrance: id
})
- })
- }
+ }
+ >
+
+
+ ))}
+
+
+
+ >
+ )
+}
- return (
- <>
-
-
-
-
-
-
-
- {edges.map(
- (
- { node, cursor }: { node: any; cursor: any },
- i: number
- ) => (
- -
- analytics.trackEvent(
- ANALYTICS_EVENTS.CLICK_FEED,
- {
- type: FEED_TYPE.TAG_DETAIL,
- location: i,
- entrance: data.node.id
- }
- )
- }
- >
-
-
- )
- )}
-
-
-
- >
- )
- }}
-
+export default () => {
+ return (
+
+
+
)
}
-
-export default TagDetail
diff --git a/src/views/Tags/index.tsx b/src/views/Tags/index.tsx
index 8041423199..8ea0b5db3e 100644
--- a/src/views/Tags/index.tsx
+++ b/src/views/Tags/index.tsx
@@ -1,6 +1,5 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import {
Footer,
@@ -11,7 +10,8 @@ import {
Tag,
Translate
} from '~/components'
-import { Query } from '~/components/GQL'
+import EmptyTag from '~/components/Empty/EmptyTag'
+import { QueryError } from '~/components/GQL'
import { ANALYTICS_EVENTS, FEED_TYPE, TEXT } from '~/common/enums'
import { analytics, mergeConnections } from '~/common/utils'
@@ -43,116 +43,113 @@ const ALL_TAGSS = gql`
${Tag.fragments.tag}
`
-const Tags = () => (
-
-
-
-
-
- }
- />
-
-
-
- {({
- data,
- loading,
- error,
- fetchMore
- }: QueryResult & { data: AllTags }) => {
- if (loading) {
- return
- }
+const Tags = () => {
+ const { data, loading, error, fetchMore } = useQuery(ALL_TAGSS)
- const connectionPath = 'viewer.recommendation.tags'
- const { edges, pageInfo } = _get(data, connectionPath, {})
- const loadMore = () => {
- analytics.trackEvent(ANALYTICS_EVENTS.LOAD_MORE, {
- type: FEED_TYPE.TAGS,
- location: edges.length
- })
- return fetchMore({
- variables: {
- after: pageInfo.endCursor
- },
- updateQuery: (previousResult, { fetchMoreResult }) =>
- mergeConnections({
- oldData: previousResult,
- newData: fetchMoreResult,
- path: connectionPath
- })
- })
- }
- const leftEdges = edges.filter((_: any, i: number) => i % 2 === 0)
- const rightEdges = edges.filter((_: any, i: number) => i % 2 === 1)
-
- return (
-
-
-
- {leftEdges.map(
- (
- { node, cursor }: { node: any; cursor: any },
- i: number
- ) => (
- -
- analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
- type: FEED_TYPE.ALL_TAGS,
- location: i * 2
- })
- }
- >
-
-
- )
- )}
-
-
- {rightEdges.map(
- (
- { node, cursor }: { node: any; cursor: any },
- i: number
- ) => (
- -
- analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
- type: FEED_TYPE.ALL_TAGS,
- location: i * 2 + 1
- })
- }
- >
-
-
- )
- )}
-
-
-
- )
+ if (loading) {
+ return
+ }
+
+ if (error) {
+ return
+ }
+
+ const connectionPath = 'viewer.recommendation.tags'
+ const { edges, pageInfo } =
+ (data && data.viewer && data.viewer.recommendation.tags) || {}
+
+ if (!edges || edges.length <= 0 || !pageInfo) {
+ return
+ }
+
+ const loadMore = () => {
+ analytics.trackEvent(ANALYTICS_EVENTS.LOAD_MORE, {
+ type: FEED_TYPE.TAGS,
+ location: edges.length
+ })
+ return fetchMore({
+ variables: {
+ after: pageInfo.endCursor
+ },
+ updateQuery: (previousResult, { fetchMoreResult }) =>
+ mergeConnections({
+ oldData: previousResult,
+ newData: fetchMoreResult,
+ path: connectionPath
+ })
+ })
+ }
+ const leftEdges = edges.filter((_: any, i: number) => i % 2 === 0)
+ const rightEdges = edges.filter((_: any, i: number) => i % 2 === 1)
+
+ return (
+
+
+
+ {leftEdges.map(({ node, cursor }, i) => (
+ -
+ analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
+ type: FEED_TYPE.ALL_TAGS,
+ location: i * 2
+ })
+ }
+ >
+
+
+ ))}
+
+
+ {rightEdges.map(({ node, cursor }, i) => (
+ -
+ analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
+ type: FEED_TYPE.ALL_TAGS,
+ location: i * 2 + 1
+ })
+ }
+ >
+
+
+ ))}
+
+
+
+ )
+}
+
+export default () => {
+ return (
+
+
+
-
-
+ />
+
+
+ }
+ />
-
+
+
-
-
-)
+
-export default Tags
+
+
+ )
+}
diff --git a/src/views/Topics/index.tsx b/src/views/Topics/index.tsx
index 3af0f9c83b..6a58138e4c 100644
--- a/src/views/Topics/index.tsx
+++ b/src/views/Topics/index.tsx
@@ -1,6 +1,5 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import {
ArticleDigest,
@@ -11,7 +10,8 @@ import {
Placeholder,
Translate
} from '~/components'
-import { Query } from '~/components/GQL'
+import EmptyArticle from '~/components/Empty/EmptyArticle'
+import { QueryError } from '~/components/GQL'
import { ANALYTICS_EVENTS, FEED_TYPE, TEXT } from '~/common/enums'
import { analytics, mergeConnections } from '~/common/utils'
@@ -47,98 +47,97 @@ const ALL_TOPICSS = gql`
${ArticleDigest.Feed.fragments.article}
`
-const Topics = () => (
-
-
-
+const Topics = () => {
+ const { data, loading, error, fetchMore } = useQuery(ALL_TOPICSS)
-
- }
- />
+ if (loading) {
+ return
+ }
-
-
- {({
- data,
- loading,
- error,
- fetchMore
- }: QueryResult & { data: AllTopics }) => {
- if (loading) {
- return
- }
+ if (error) {
+ return
+ }
- const connectionPath = 'viewer.recommendation.topics'
- const { edges, pageInfo } = _get(data, connectionPath, {})
- const loadMore = () => {
- analytics.trackEvent(ANALYTICS_EVENTS.LOAD_MORE, {
- type: FEED_TYPE.TOPICS,
- location: edges.length
- })
- return fetchMore({
- variables: {
- after: pageInfo.endCursor
- },
- updateQuery: (previousResult, { fetchMoreResult }) =>
- mergeConnections({
- oldData: previousResult,
- newData: fetchMoreResult,
- path: connectionPath
- })
+ const connectionPath = 'viewer.recommendation.topics'
+ const { edges, pageInfo } =
+ (data && data.viewer && data.viewer.recommendation.topics) || {}
+
+ if (!edges || edges.length <= 0 || !pageInfo) {
+ return
+ }
+
+ const loadMore = () => {
+ analytics.trackEvent(ANALYTICS_EVENTS.LOAD_MORE, {
+ type: FEED_TYPE.TOPICS,
+ location: edges.length
+ })
+ return fetchMore({
+ variables: {
+ after: pageInfo.endCursor
+ },
+ updateQuery: (previousResult, { fetchMoreResult }) =>
+ mergeConnections({
+ oldData: previousResult,
+ newData: fetchMoreResult,
+ path: connectionPath
+ })
+ })
+ }
+
+ return (
+
+
+ {edges.map(({ node, cursor }, i) => (
+ -
+ analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
+ type: FEED_TYPE.ALL_TOPICS,
+ location: i
})
}
+ >
+
+
+ ))}
+
+
+ )
+}
- return (
-
-
- {edges.map(
- (
- { node, cursor }: { node: any; cursor: any },
- i: number
- ) => (
- -
- analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
- type: FEED_TYPE.ALL_TOPICS,
- location: i
- })
- }
- >
-
-
- )
- )}
-
-
- )
+export default () => {
+ return (
+
+
+
-
-
+ />
+
+
+ }
+ />
-
-
-)
+
+
-export default Topics
+
+
+ )
+}
diff --git a/src/views/User/Articles/UserArticles.tsx b/src/views/User/Articles/UserArticles.tsx
index 38120e66f0..0e0947da1a 100644
--- a/src/views/User/Articles/UserArticles.tsx
+++ b/src/views/User/Articles/UserArticles.tsx
@@ -1,6 +1,5 @@
-import _get from 'lodash/get'
import { useRouter } from 'next/router'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import {
ArticleDigest,
@@ -10,7 +9,7 @@ import {
Placeholder
} from '~/components'
import EmptyArticle from '~/components/Empty/EmptyArticle'
-import { Query } from '~/components/GQL'
+import { QueryError } from '~/components/GQL'
import { UserArticles as UserArticlesTypes } from '~/components/GQL/queries/__generated__/UserArticles'
import USER_ARTICLES from '~/components/GQL/queries/userArticles'
import { Translate } from '~/components/Language'
@@ -24,31 +23,31 @@ import ICON_DOT_DIVIDER from '~/static/icons/dot-divider.svg?sprite'
import styles from './styles.css'
const ArticleSummaryInfo = ({ data }: { data: UserArticlesTypes }) => {
- const { articleCount: articles, totalWordCount: words } = _get(
- data,
- 'user.status',
- {
- articleCount: 0,
- totalWordCount: 0
- }
- )
+ const { articleCount: articles, totalWordCount: words } = (data &&
+ data.user &&
+ data.user.status) || {
+ articleCount: 0,
+ totalWordCount: 0
+ }
+
return (
- <>
-
-
- {articles}
-
-
-
- {words}
-
-
+
+
+ {articles}
+
+
+
+
+
+ {words}
+
+
- >
+
)
}
@@ -56,101 +55,104 @@ const UserArticles = () => {
const router = useRouter()
const userName = getQuery({ router, key: 'userName' })
+ const { data, loading, error, fetchMore } = useQuery(
+ USER_ARTICLES,
+ { variables: { userName } }
+ )
+
if (!userName) {
return
}
+ if (loading) {
+ return
+ }
+
+ if (error) {
+ return
+ }
+
+ if (!data || !data.user) {
+ return
+ }
+
+ const user = data.user
+ const connectionPath = 'user.articles'
+ const { edges, pageInfo } = data.user.articles
+
+ if (!edges || edges.length <= 0 || !pageInfo) {
+ return null
+ }
+
+ const loadMore = () => {
+ analytics.trackEvent(ANALYTICS_EVENTS.LOAD_MORE, {
+ type: FEED_TYPE.USER_ARTICLE,
+ location: edges.length
+ })
+ return fetchMore({
+ variables: {
+ after: pageInfo.endCursor
+ },
+ updateQuery: (previousResult, { fetchMoreResult }) =>
+ mergeConnections({
+ oldData: previousResult,
+ newData: fetchMoreResult,
+ path: connectionPath
+ })
+ })
+ }
+
+ const CustomHead = () => (
+
+ )
+
+ if (!edges || edges.length <= 0) {
+ return (
+ <>
+
+
+
+ >
+ )
+ }
+
return (
-
- {({
- data,
- loading,
- error,
- fetchMore
- }: QueryResult & { data: UserArticlesTypes }) => {
- if (loading) {
- return
- }
-
- const connectionPath = 'user.articles'
- const { edges, pageInfo } = _get(data, connectionPath, {})
- const loadMore = () => {
- analytics.trackEvent(ANALYTICS_EVENTS.LOAD_MORE, {
- type: FEED_TYPE.USER_ARTICLE,
- location: edges.length
- })
- return fetchMore({
- variables: {
- after: pageInfo.endCursor
- },
- updateQuery: (previousResult, { fetchMoreResult }) =>
- mergeConnections({
- oldData: previousResult,
- newData: fetchMoreResult,
- path: connectionPath
- })
- })
- }
-
- const CustomHead = () => (
-
- )
-
- if (!edges || edges.length <= 0) {
- return (
- <>
-
-
-
- >
- )
- }
-
- return (
- <>
-
-
-
+
+
+
+
+ {edges.map(({ node, cursor }, i) => (
+ -
+ analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
+ type: FEED_TYPE.USER_ARTICLE,
+ location: i
+ })
+ }
>
-
- {edges.map(
- ({ node, cursor }: { node: any; cursor: any }, i: number) => (
- -
- analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
- type: FEED_TYPE.USER_ARTICLE,
- location: i
- })
- }
- >
-
-
- )
- )}
-
-
- >
- )
- }}
-
+
+
+ ))}
+
+
+ >
)
}
diff --git a/src/views/User/Articles/styles.css b/src/views/User/Articles/styles.css
index 2a7fe1fa22..6cfe3edff4 100644
--- a/src/views/User/Articles/styles.css
+++ b/src/views/User/Articles/styles.css
@@ -2,14 +2,13 @@
@mixin border-bottom-grey;
display: flex;
align-items: center;
-
- color: var(--color-grey);
padding-bottom: var(--spacing-default);
margin-bottom: var(--spacing-default);
- & > span {
+ color: var(--color-grey);
+
+ & .num {
color: var(--color-grey-darker);
- font-weight: 600;
- margin: 0 0.3rem;
+ font-weight: var(--font-weight-semibold);
}
}
diff --git a/src/views/User/Bookmarks/MeBookmarks.tsx b/src/views/User/Bookmarks/MeBookmarks.tsx
index ec52d72964..e0f2bc99ce 100644
--- a/src/views/User/Bookmarks/MeBookmarks.tsx
+++ b/src/views/User/Bookmarks/MeBookmarks.tsx
@@ -1,10 +1,9 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import { ArticleDigest, InfiniteScroll, Placeholder } from '~/components'
import EmptyBookmark from '~/components/Empty/EmptyBookmark'
-import { Query } from '~/components/GQL'
+import { QueryError } from '~/components/GQL'
import { mergeConnections } from '~/common/utils'
@@ -37,53 +36,51 @@ const ME_BOOKMARK_FEED = gql`
${ArticleDigest.Feed.fragments.article}
`
-export default () => {
- return (
-
- {({
- data,
- loading,
- error,
- fetchMore
- }: QueryResult & { data: MeBookmarkFeed }) => {
- if (loading) {
- return
- }
+const MeBookmarks = () => {
+ const { data, loading, error, fetchMore } = useQuery(
+ ME_BOOKMARK_FEED
+ )
- const connectionPath = 'viewer.subscriptions'
- const { edges, pageInfo } = _get(data, connectionPath, {})
- const loadMore = () =>
- fetchMore({
- variables: {
- after: pageInfo.endCursor
- },
- updateQuery: (previousResult, { fetchMoreResult }) =>
- mergeConnections({
- oldData: previousResult,
- newData: fetchMoreResult,
- path: connectionPath
- })
- })
+ if (loading) {
+ return
+ }
- if (edges <= 0) {
- return
- }
+ if (error) {
+ return
+ }
- return (
-
-
- {edges.map(({ node, cursor }: { node: any; cursor: any }) => (
- -
-
-
- ))}
-
-
- )
- }}
-
+ const connectionPath = 'viewer.subscriptions'
+ const { edges, pageInfo } =
+ (data && data.viewer && data.viewer.subscriptions) || {}
+
+ if (!edges || edges.length <= 0 || !pageInfo || edges.length <= 0) {
+ return
+ }
+
+ const loadMore = () =>
+ fetchMore({
+ variables: {
+ after: pageInfo.endCursor
+ },
+ updateQuery: (previousResult, { fetchMoreResult }) =>
+ mergeConnections({
+ oldData: previousResult,
+ newData: fetchMoreResult,
+ path: connectionPath
+ })
+ })
+
+ return (
+
+
+ {edges.map(({ node, cursor }) => (
+ -
+
+
+ ))}
+
+
)
}
+
+export default MeBookmarks
diff --git a/src/views/User/Comments/UserComments/index.tsx b/src/views/User/Comments/UserComments/index.tsx
index 8edeb8648d..52c8bfa99e 100644
--- a/src/views/User/Comments/UserComments/index.tsx
+++ b/src/views/User/Comments/UserComments/index.tsx
@@ -1,13 +1,12 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
import Link from 'next/link'
import { useRouter } from 'next/router'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import { Head, Icon, InfiniteScroll, Placeholder } from '~/components'
import { CommentDigest } from '~/components/CommentDigest'
import EmptyComment from '~/components/Empty/EmptyComment'
-import { Query } from '~/components/GQL'
+import { QueryError } from '~/components/GQL'
import {
filterComments,
@@ -80,117 +79,133 @@ const UserCommentsWrap = () => {
const router = useRouter()
const userName = getQuery({ router, key: 'userName' })
- return (
-
- {({ data, loading, error }: any) => {
- if (loading) {
- return
- }
+ const { data, loading, error } = useQuery(USER_ID, {
+ variables: { userName }
+ })
+
+ if (loading) {
+ return
+ }
- return (
- <>
-
-
- >
- )
- }}
-
+ if (error) {
+ return
+ }
+
+ if (!data || !data.user) {
+ return null
+ }
+
+ return (
+ <>
+
+
+ >
)
}
const UserComments = ({ user }: UserIdUser) => {
+ const { data, loading, error, fetchMore } = useQuery(
+ USER_COMMENT_FEED,
+ {
+ variables: { id: user && user.id }
+ }
+ )
+
if (!user || !user.id) {
return null
}
+ if (loading) {
+ return
+ }
+
+ if (error) {
+ return
+ }
+
+ const connectionPath = 'node.commentedArticles'
+ const { edges, pageInfo } =
+ (data &&
+ data.node &&
+ data.node.__typename === 'User' &&
+ data.node.commentedArticles &&
+ data.node.commentedArticles) ||
+ {}
+
+ if (!edges || edges.length <= 0 || !pageInfo) {
+ return
+ }
+
+ const loadMore = () =>
+ fetchMore({
+ variables: {
+ after: pageInfo.endCursor
+ },
+ updateQuery: (previousResult, { fetchMoreResult }) =>
+ mergeConnections({
+ oldData: previousResult,
+ newData: fetchMoreResult,
+ path: connectionPath
+ })
+ })
+
return (
-
- {({
- data,
- loading,
- error,
- fetchMore
- }: QueryResult & { data: UserCommentFeed }) => {
- if (loading) {
- return
- }
+
+
+ {edges.map(articleEdge => {
+ const commentEdges = articleEdge.node.comments.edges
+
+ if (!commentEdges) {
+ return null
+ }
- const connectionPath = 'node.commentedArticles'
- const { edges, pageInfo } = _get(data, connectionPath, {})
- const loadMore = () =>
- fetchMore({
- variables: {
- after: pageInfo.endCursor
- },
- updateQuery: (previousResult, { fetchMoreResult }) =>
- mergeConnections({
- oldData: previousResult,
- newData: fetchMoreResult,
- path: connectionPath
- })
+ const articlePath = toPath({
+ page: 'articleDetail',
+ userName: articleEdge.node.author.userName || '',
+ slug: articleEdge.node.slug,
+ mediaHash: articleEdge.node.mediaHash || ''
})
+ const filteredComments = filterComments(
+ (commentEdges || []).map(({ node }) => node)
+ )
- if (!edges || edges.length <= 0) {
- return
- }
+ return (
+ -
+
+
+
+ {articleEdge.node.title}
+
+
+
+
+
+
+ {filteredComments &&
+ filteredComments.map(comment => (
+ -
+
+
+ ))}
+
+
+ )
+ })}
- return (
-
-
- {edges.map((articleEdge: { node: any; cursor: any }) => {
- const commentEdges = _get(articleEdge, 'node.comments.edges')
- const articlePath = toPath({
- page: 'articleDetail',
- userName: articleEdge.node.author.userName,
- slug: articleEdge.node.slug,
- mediaHash: articleEdge.node.mediaHash
- })
- const filteredComments = filterComments(
- (commentEdges || []).map(({ node }: { node: any }) => node)
- )
-
- return (
- -
-
-
-
- {articleEdge.node.title}
-
-
-
-
-
-
- {filteredComments &&
- filteredComments.map(comment => (
- -
-
-
- ))}
-
-
- )
- })}
-
-
-
- )
- }}
-
+
+
+
)
}
diff --git a/src/views/User/Drafts/MeDrafts.tsx b/src/views/User/Drafts/MeDrafts.tsx
index 3865070c29..4aa632b257 100644
--- a/src/views/User/Drafts/MeDrafts.tsx
+++ b/src/views/User/Drafts/MeDrafts.tsx
@@ -1,10 +1,9 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import { DraftDigest, InfiniteScroll, Placeholder } from '~/components'
import EmptyDraft from '~/components/Empty/EmptyDraft'
-import { Query } from '~/components/GQL'
+import { QueryError } from '~/components/GQL'
import { mergeConnections } from '~/common/utils'
@@ -33,53 +32,50 @@ const ME_DRAFTS_FEED = gql`
${DraftDigest.Feed.fragments.draft}
`
-export default () => {
- return (
-
- {({
- data,
- loading,
- error,
- fetchMore
- }: QueryResult & { data: MeDraftFeed }) => {
- if (loading) {
- return
- }
+const MeDrafts = () => {
+ const { data, loading, error, fetchMore } = useQuery(
+ ME_DRAFTS_FEED
+ )
- const connectionPath = 'viewer.drafts'
- const { edges, pageInfo } = _get(data, connectionPath, {})
- const loadMore = () =>
- fetchMore({
- variables: {
- after: pageInfo.endCursor
- },
- updateQuery: (previousResult, { fetchMoreResult }) =>
- mergeConnections({
- oldData: previousResult,
- newData: fetchMoreResult,
- path: connectionPath
- })
- })
+ if (loading) {
+ return
+ }
- if (!edges || edges.length <= 0) {
- return
- }
+ if (error) {
+ return
+ }
- return (
-
-
- {edges.map(({ node, cursor }: { node: any; cursor: any }) => (
- -
-
-
- ))}
-
-
- )
- }}
-
+ const connectionPath = 'viewer.drafts'
+ const { edges, pageInfo } = (data && data.viewer && data.viewer.drafts) || {}
+
+ if (!edges || edges.length <= 0 || !pageInfo) {
+ return
+ }
+
+ const loadMore = () =>
+ fetchMore({
+ variables: {
+ after: pageInfo.endCursor
+ },
+ updateQuery: (previousResult, { fetchMoreResult }) =>
+ mergeConnections({
+ oldData: previousResult,
+ newData: fetchMoreResult,
+ path: connectionPath
+ })
+ })
+
+ return (
+
+
+ {edges.map(({ node, cursor }) => (
+ -
+
+
+ ))}
+
+
)
}
+
+export default MeDrafts
diff --git a/src/views/User/Followees/UserFollowees.tsx b/src/views/User/Followees/UserFollowees.tsx
index d71aab7779..aa1ffd1ea2 100644
--- a/src/views/User/Followees/UserFollowees.tsx
+++ b/src/views/User/Followees/UserFollowees.tsx
@@ -1,11 +1,10 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
import { useRouter } from 'next/router'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
-import { Head, InfiniteScroll, Placeholder } from '~/components'
-import EmptyFollowee from '~/components/Empty/EmptyFollowee'
-import { Query } from '~/components/GQL'
+import { Head, InfiniteScroll, Placeholder, Translate } from '~/components'
+import EmptyWarning from '~/components/Empty/EmptyWarning'
+import { QueryError } from '~/components/GQL'
import { UserDigest } from '~/components/UserDigest'
import { ANALYTICS_EVENTS, FEED_TYPE } from '~/common/enums'
@@ -39,79 +38,81 @@ const USER_FOLLOWEES_FEED = gql`
const UserFollowees = () => {
const router = useRouter()
const userName = getQuery({ router, key: 'userName' })
+ const { data, loading, error, fetchMore } = useQuery(
+ USER_FOLLOWEES_FEED,
+ {
+ variables: { userName }
+ }
+ )
- return (
-
- {({
- data,
- loading,
- error,
- fetchMore
- }: QueryResult & { data: UserFolloweeFeed }) => {
- if (loading) {
- return
- }
+ if (loading || !data || !data.user) {
+ return
+ }
- const connectionPath = 'user.followees'
- const { edges, pageInfo } = _get(data, connectionPath, {})
- const loadMore = () => {
- analytics.trackEvent(ANALYTICS_EVENTS.LOAD_MORE, {
- type: FEED_TYPE.FOLLOWEE,
- location: edges.length,
- entrance: data.user.id
- })
- return fetchMore({
- variables: {
- after: pageInfo.endCursor
- },
- updateQuery: (previousResult, { fetchMoreResult }) =>
- mergeConnections({
- oldData: previousResult,
- newData: fetchMoreResult,
- path: connectionPath
- })
- })
- }
+ if (error) {
+ return
+ }
+
+ const user = data.user
+ const connectionPath = 'user.followees'
+ const { edges, pageInfo } = user.followees
- if (!edges || edges.length <= 0) {
- return
+ if (!edges || edges.length <= 0 || !pageInfo) {
+ return (
+
}
+ />
+ )
+ }
- return (
- <>
-
- {
+ analytics.trackEvent(ANALYTICS_EVENTS.LOAD_MORE, {
+ type: FEED_TYPE.FOLLOWEE,
+ location: edges.length,
+ entrance: user.id
+ })
+ return fetchMore({
+ variables: {
+ after: pageInfo.endCursor
+ },
+ updateQuery: (previousResult, { fetchMoreResult }) =>
+ mergeConnections({
+ oldData: previousResult,
+ newData: fetchMoreResult,
+ path: connectionPath
+ })
+ })
+ }
+
+ return (
+ <>
+
+
+
+ {edges.map(({ node, cursor }, i) => (
+ -
+ analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
+ type: FEED_TYPE.FOLLOWEE,
+ location: i,
+ entrance: user.id
+ })
+ }
>
-
- {edges.map(
- ({ node, cursor }: { node: any; cursor: any }, i: number) => (
- -
- analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
- type: FEED_TYPE.FOLLOWEE,
- location: i,
- entrance: data.user.id
- })
- }
- >
-
-
- )
- )}
-
-
- >
- )
- }}
-
+
+
+ ))}
+
+
+ >
)
}
diff --git a/src/views/User/Followers/UserFollowers.tsx b/src/views/User/Followers/UserFollowers.tsx
index 16cb3fdb7c..e4623f34cb 100644
--- a/src/views/User/Followers/UserFollowers.tsx
+++ b/src/views/User/Followers/UserFollowers.tsx
@@ -1,11 +1,10 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
import { useRouter } from 'next/router'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
-import { Head, InfiniteScroll, Placeholder } from '~/components'
-import EmptyFollower from '~/components/Empty/EmptyFollower'
-import { Query } from '~/components/GQL'
+import { Head, InfiniteScroll, Placeholder, Translate } from '~/components'
+import EmptyWarning from '~/components/Empty/EmptyWarning'
+import { QueryError } from '~/components/GQL'
import { UserDigest } from '~/components/UserDigest'
import { ANALYTICS_EVENTS, FEED_TYPE } from '~/common/enums'
@@ -39,79 +38,81 @@ const USER_FOLLOWERS_FEED = gql`
const UserFollowers = () => {
const router = useRouter()
const userName = getQuery({ router, key: 'userName' })
+ const { data, loading, error, fetchMore } = useQuery(
+ USER_FOLLOWERS_FEED,
+ {
+ variables: { userName }
+ }
+ )
- return (
-
- {({
- data,
- loading,
- error,
- fetchMore
- }: QueryResult & { data: UserFollowerFeed }) => {
- if (loading) {
- return
- }
+ if (loading || !data || !data.user) {
+ return
+ }
- const connectionPath = 'user.followers'
- const { edges, pageInfo } = _get(data, connectionPath, {})
- const loadMore = () => {
- analytics.trackEvent(ANALYTICS_EVENTS.LOAD_MORE, {
- type: FEED_TYPE.FOLLOWER,
- location: edges.length,
- entrance: data.user.id
- })
- return fetchMore({
- variables: {
- after: pageInfo.endCursor
- },
- updateQuery: (previousResult, { fetchMoreResult }) =>
- mergeConnections({
- oldData: previousResult,
- newData: fetchMoreResult,
- path: connectionPath
- })
- })
- }
+ if (error) {
+ return
+ }
+
+ const user = data.user
+ const connectionPath = 'user.followers'
+ const { edges, pageInfo } = user.followers
- if (!edges || edges.length <= 0) {
- return
+ if (!edges || edges.length <= 0 || !pageInfo) {
+ return (
+
}
+ />
+ )
+ }
- return (
- <>
-
- {
+ analytics.trackEvent(ANALYTICS_EVENTS.LOAD_MORE, {
+ type: FEED_TYPE.FOLLOWER,
+ location: edges.length,
+ entrance: user.id
+ })
+ return fetchMore({
+ variables: {
+ after: pageInfo.endCursor
+ },
+ updateQuery: (previousResult, { fetchMoreResult }) =>
+ mergeConnections({
+ oldData: previousResult,
+ newData: fetchMoreResult,
+ path: connectionPath
+ })
+ })
+ }
+
+ return (
+ <>
+
+
+
+ {edges.map(({ node, cursor }, i) => (
+ -
+ analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
+ type: FEED_TYPE.FOLLOWER,
+ location: i,
+ entrance: user.id
+ })
+ }
>
-
- {edges.map(
- ({ node, cursor }: { node: any; cursor: any }, i: number) => (
- -
- analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
- type: FEED_TYPE.FOLLOWER,
- location: i,
- entrance: data.user.id
- })
- }
- >
-
-
- )
- )}
-
-
- >
- )
- }}
-
+
+
+ ))}
+
+
+ >
)
}
diff --git a/src/views/User/History/MeHistory.tsx b/src/views/User/History/MeHistory.tsx
index 3326df3b68..de611acc2e 100644
--- a/src/views/User/History/MeHistory.tsx
+++ b/src/views/User/History/MeHistory.tsx
@@ -1,10 +1,9 @@
import gql from 'graphql-tag'
-import _get from 'lodash/get'
-import { QueryResult } from 'react-apollo'
+import { useQuery } from 'react-apollo'
import { ArticleDigest, InfiniteScroll, Placeholder } from '~/components'
import EmptyHistory from '~/components/Empty/EmptyHistory'
-import { Query } from '~/components/GQL'
+import { QueryError } from '~/components/GQL'
import { ANALYTICS_EVENTS, FEED_TYPE } from '~/common/enums'
import { analytics, mergeConnections } from '~/common/utils'
@@ -42,71 +41,68 @@ const ME_HISTORY_FEED = gql`
${ArticleDigest.Feed.fragments.article}
`
-export default () => {
- return (
-
- {({
- data,
- loading,
- fetchMore
- }: QueryResult & { data: MeHistoryFeed }) => {
- if (loading) {
- return
- }
+const MeHistory = () => {
+ const { data, loading, error, fetchMore } = useQuery(
+ ME_HISTORY_FEED
+ )
- const connectionPath = 'viewer.activity.history'
- const { edges, pageInfo } = _get(data, connectionPath, {})
- const loadMore = () => {
- analytics.trackEvent(ANALYTICS_EVENTS.LOAD_MORE, {
- type: FEED_TYPE.READ_HISTORY,
- location: edges.length
- })
- return fetchMore({
- variables: {
- after: pageInfo.endCursor
- },
- updateQuery: (previousResult, { fetchMoreResult }) =>
- mergeConnections({
- oldData: previousResult,
- newData: fetchMoreResult,
- path: connectionPath
- })
- })
- }
+ if (loading) {
+ return
+ }
- if (!edges || edges.length <= 0) {
- return
- }
+ if (error) {
+ return
+ }
+
+ const connectionPath = 'viewer.activity.history'
+ const { edges, pageInfo } =
+ (data && data.viewer && data.viewer.activity.history) || {}
- return (
-
+ }
+
+ const loadMore = () => {
+ analytics.trackEvent(ANALYTICS_EVENTS.LOAD_MORE, {
+ type: FEED_TYPE.READ_HISTORY,
+ location: edges.length
+ })
+ return fetchMore({
+ variables: {
+ after: pageInfo.endCursor
+ },
+ updateQuery: (previousResult, { fetchMoreResult }) =>
+ mergeConnections({
+ oldData: previousResult,
+ newData: fetchMoreResult,
+ path: connectionPath
+ })
+ })
+ }
+
+ return (
+
+
+ {edges.map(({ node, cursor }, i) => (
+ -
+ analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
+ type: FEED_TYPE.READ_HISTORY,
+ location: i
+ })
+ }
>
-
- {edges.map(
- ({ node, cursor }: { node: any; cursor: any }, i: number) => (
- -
- analytics.trackEvent(ANALYTICS_EVENTS.CLICK_FEED, {
- type: FEED_TYPE.READ_HISTORY,
- location: i
- })
- }
- >
-
-
- )
- )}
-
-
- )
- }}
-
+
+
+ ))}
+
+
)
}
+
+export default MeHistory
diff --git a/tslint.json b/tslint.json
index 87dfda55a1..6d828f9033 100644
--- a/tslint.json
+++ b/tslint.json
@@ -1,15 +1,21 @@
{
- "extends": ["tslint:latest", "tslint-react", "tslint-config-prettier"],
+ "extends": [
+ "tslint:latest",
+ "tslint-react",
+ "tslint-config-prettier",
+ "tslint-react-hooks"
+ ],
"rules": {
"no-implicit-dependencies": [
true,
[
"~",
- "@testing-library/react",
"tippy.js",
"dotenv",
"apollo-cache",
- "svg-sprite-loader"
+ "svg-sprite-loader",
+ "@testing-library",
+ "@apollo"
]
],
"no-bitwise": false,
@@ -27,8 +33,7 @@
"import-sources-order": "case-insensitive",
"named-imports-order": "case-insensitive",
"grouped-imports": true,
- "groups": [
- {
+ "groups": [{
"name": "local",
"match": "^[.]",
"order": 20
@@ -50,6 +55,7 @@
}
]
}
- ]
+ ],
+ "react-hooks-nesting": "error"
}
}