diff --git a/.env.dev b/.env.dev index 280de1027a..4889620963 100644 --- a/.env.dev +++ b/.env.dev @@ -6,11 +6,12 @@ # ### -NEXT_PUBLIC_SITE_DOMAIN=https://web-develop.matters.news -NEXT_PUBLIC_ASSET_DOMAIN=https://assets-develop.matters.news +NEXT_PUBLIC_SITE_DOMAIN=web-develop.matters.news +NEXT_PUBLIC_ASSET_DOMAIN=assets-develop.matters.news NEXT_PUBLIC_API_URL=https://server-develop.matters.news/graphql -NEXT_PUBLIC_WS_URL=wss://server-develop.matters.news/graphql -NEXT_PUBLIC_OAUTH_URL=https://server-develop.matters.news/oauth +NEXT_PUBLIC_OAUTH_URL=https://auth-server-develop.matters.news/oauth +NEXT_PUBLIC_OAUTH_API_URL=https://auth-server-develop.matters.news/graphql +NEXT_PUBLIC_OAUTH_SITE_DOMAIN=auth-develop.matters.news NEXT_PUBLIC_SEGMENT_KEY=3gE20MjzN9qncFqlKV0pDvNO7Cp2gWU3 NEXT_PUBLIC_FB_APP_ID=823885921293850 NEXT_PUBLIC_SENTRY_DSN=https://409be482d1da4670879048d7d943c38e@sentry.matters.one/2 diff --git a/.env.local.example b/.env.local.example index f46c65843a..e29211bcf3 100644 --- a/.env.local.example +++ b/.env.local.example @@ -5,11 +5,12 @@ # ### -NEXT_PUBLIC_SITE_DOMAIN=https://web-develop.matters.news -NEXT_PUBLIC_ASSET_DOMAIN=https://assets-develop.matters.news +NEXT_PUBLIC_SITE_DOMAIN=web-develop.matters.news +NEXT_PUBLIC_ASSET_DOMAIN=assets-develop.matters.news NEXT_PUBLIC_API_URL=https://server-develop.matters.news/graphql -NEXT_PUBLIC_WS_URL=wss://server-develop.matters.news/graphql -NEXT_PUBLIC_OAUTH_URL=https://server-develop.matters.news/oauth +NEXT_PUBLIC_OAUTH_URL=https://auth-server-develop.matters.news/oauth +NEXT_PUBLIC_OAUTH_API_URL=https://auth-server-develop.matters.news/graphql +NEXT_PUBLIC_OAUTH_SITE_DOMAIN=auth-develop.matters.news NEXT_PUBLIC_SEGMENT_KEY=3gE20MjzN9qncFqlKV0pDvNO7Cp2gWU3 NEXT_PUBLIC_FB_APP_ID=823885921293850 NEXT_PUBLIC_SENTRY_DSN= diff --git a/.env.prod b/.env.prod index 3332d6664f..6c9f896249 100644 --- a/.env.prod +++ b/.env.prod @@ -6,11 +6,12 @@ # ### -NEXT_PUBLIC_SITE_DOMAIN=https://matters.news -NEXT_PUBLIC_ASSET_DOMAIN=https://assets.matters.news +NEXT_PUBLIC_SITE_DOMAIN=matters.news +NEXT_PUBLIC_ASSET_DOMAIN=assets.matters.news NEXT_PUBLIC_API_URL=https://server.matters.news/graphql -NEXT_PUBLIC_WS_URL=wss://server.matters.news/graphql -NEXT_PUBLIC_OAUTH_URL=https://server.matters.news/oauth +NEXT_PUBLIC_OAUTH_URL=https://auth-server.matters.news/oauth +NEXT_PUBLIC_OAUTH_API_URL=https://auth-server.matters.news/graphql +NEXT_PUBLIC_OAUTH_SITE_DOMAIN=auth.matters.news NEXT_PUBLIC_SEGMENT_KEY=Yk2ao5JvhOCyvCh9SCVBT1iTN4kfTpy7 NEXT_PUBLIC_FB_APP_ID=1415638158583454 NEXT_PUBLIC_SENTRY_DSN=https://7f664c71fc9b4ee2bbe75fdf766b6228@sentry.matters.one/3 diff --git a/package-lock.json b/package-lock.json index 4340c469c4..97d18dd096 100644 --- a/package-lock.json +++ b/package-lock.json @@ -174,27 +174,17 @@ } }, "@apollo/federation": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@apollo/federation/-/federation-0.17.0.tgz", - "integrity": "sha512-vSW/M8+SGdu5xALsA/RL37GgB+wNFZpXCyPAcg3b68c8x7uoQHgYwqwUu7D+GnAGeOpDUrNnFPdKAYW7elYkyQ==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@apollo/federation/-/federation-0.19.0.tgz", + "integrity": "sha512-8cd8ftHgqaseDTN7RJrROT6FT1xy8RV2Qb9BGhhqPVMHqf08GtidBqQTk6hv1UDR0qu/TRZA6J4Kh7oXeMrPQg==", "dev": true, "requires": { - "apollo-graphql": "^0.4.0", + "apollo-graphql": "^0.5.0", "apollo-server-env": "^2.4.5", "core-js": "^3.4.0", "lodash.xorby": "^4.7.0" }, "dependencies": { - "apollo-graphql": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/apollo-graphql/-/apollo-graphql-0.4.5.tgz", - "integrity": "sha512-0qa7UOoq7E71kBYE7idi6mNQhHLVdMEDInWk6TNw3KsSWZE2/I68gARP84Mj+paFTO5NYuw1Dht66PVX76Cc2w==", - "dev": true, - "requires": { - "apollo-env": "^0.6.5", - "lodash.sortby": "^4.7.0" - } - }, "core-js": { "version": "3.6.5", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", @@ -2145,13 +2135,13 @@ } }, "@firebase/database": { - "version": "0.6.9", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.9.tgz", - "integrity": "sha512-+X2dNFDpcLEcDRdXp2Hgkf0RnNz3AOIC+Y7UFMQYadm9buB+snXomlnlkMzOj6o+Cp3V7GnpBrKKeeFqzF6wGQ==", + "version": "0.6.10", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.10.tgz", + "integrity": "sha512-Hc8zIPAroIbAoRe6xFCI5oFHubcHKoDsbYE3J5G1/BhT6DnEUSoLgx8kJ2npybVSCVyb8BvsD6swh17DGEz+0g==", "requires": { "@firebase/auth-interop-types": "0.1.5", "@firebase/component": "0.1.17", - "@firebase/database-types": "0.5.1", + "@firebase/database-types": "0.5.2", "@firebase/logger": "0.2.6", "@firebase/util": "0.3.0", "faye-websocket": "0.11.3", @@ -2159,26 +2149,34 @@ } }, "@firebase/database-types": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.5.1.tgz", - "integrity": "sha512-onQxom1ZBYBJ648w/VNRzUewovEDAH7lvnrrpCd69ukkyrMk6rGEO/PQ9BcNEbhlNtukpsqRS0oNOFlHs0FaSA==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.5.2.tgz", + "integrity": "sha512-ap2WQOS3LKmGuVFKUghFft7RxXTyZTDr0Xd8y2aqmWsbJVjgozi0huL/EUMgTjGFrATAjcf2A7aNs8AKKZ2a8g==", "requires": { "@firebase/app-types": "0.6.1" } }, "@firebase/firestore": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-1.16.2.tgz", - "integrity": "sha512-iIkAL860oD/QA1uYI9JBbWqBYFWd+DnuSj//BIbOGn3DNAruDFy07g8re1vn+0MMas9bMk6CZATJNCFPeH8AsQ==", + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-1.16.3.tgz", + "integrity": "sha512-Lwc/QqzY3zEijJoI3vgWoRnffkTd09VXjaLHoqa9wfDoQe4WL1s9w0KrXCkTfAScjpV3rd447QxeoJwvZ0UTeg==", "requires": { "@firebase/component": "0.1.17", "@firebase/firestore-types": "1.12.0", "@firebase/logger": "0.2.6", "@firebase/util": "0.3.0", - "@firebase/webchannel-wrapper": "0.2.41", + "@firebase/webchannel-wrapper": "0.3.0", "@grpc/grpc-js": "^1.0.0", "@grpc/proto-loader": "^0.5.0", + "node-fetch": "2.6.0", "tslib": "^1.11.1" + }, + "dependencies": { + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + } } }, "@firebase/firestore-types": { @@ -2326,9 +2324,9 @@ } }, "@firebase/webchannel-wrapper": { - "version": "0.2.41", - "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.2.41.tgz", - "integrity": "sha512-XcdMT5PSZHiuf7LJIhzKIe+RyYa25S3LHRRvLnZc6iFjwXkrSDJ8J/HWO6VT8d2ZTbawp3VcLEjRF/VN8glCrA==" + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.3.0.tgz", + "integrity": "sha512-VniCGPIgSGNEgOkh5phb3iKmSGIzcwrccy3IomMFRWPCMiCk2y98UQNJEoDs1yIHtZMstVjYWKYxnunIGzC5UQ==" }, "@grpc/grpc-js": { "version": "1.1.3", @@ -3550,9 +3548,9 @@ } }, "@matters/matters-editor": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/@matters/matters-editor/-/matters-editor-0.1.15.tgz", - "integrity": "sha512-YJvpe4/Y6/MOhv/pT1ybc/XH/kNQRxJHqci9DjFIeNhtubh99bdVNPY9xXYbeZHo4WexbFdHUDtVtMV9zjFtmA==", + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/@matters/matters-editor/-/matters-editor-0.1.16.tgz", + "integrity": "sha512-UH/PTKJyR6VokLeReipHT+nxuVFIOpExVXwDpxQQEGCV1ohH+txlF8XFWWUptD40aAgbSicpcuU912ok+cZdQA==", "requires": { "classnames": "^2.2.6", "react-quill": "^1.3.3" @@ -3792,9 +3790,9 @@ }, "dependencies": { "@oclif/plugin-help": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@oclif/plugin-help/-/plugin-help-3.1.0.tgz", - "integrity": "sha512-orSWpXGlJaX16eSjAtI8scA8QhrjQOaCSHodEx52t18JKbIVzG8jcngugyWAOB/V4jhPl0rdiVk9XFsaIIiG2g==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@oclif/plugin-help/-/plugin-help-3.2.0.tgz", + "integrity": "sha512-7jxtpwVWAVbp1r46ZnTK/uF+FeZc6y4p1XcGaIUuPAp7wx6NJhIRN/iMT9UfNFX/Cz7mq+OyJz+E+i0zrik86g==", "dev": true, "requires": { "@oclif/command": "^1.5.20", @@ -3802,16 +3800,16 @@ "chalk": "^2.4.1", "indent-string": "^4.0.0", "lodash.template": "^4.4.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0", - "widest-line": "^2.0.1", + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "widest-line": "^3.1.0", "wrap-ansi": "^4.0.0" } }, "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, "debug": { @@ -3823,6 +3821,18 @@ "ms": "^2.1.1" } }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -3836,23 +3846,14 @@ "dev": true }, "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "dev": true, "requires": { - "ansi-regex": "^4.1.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" } }, "wrap-ansi": { @@ -3866,10 +3867,10 @@ "strip-ansi": "^4.0.0" }, "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, "string-width": { @@ -4082,6 +4083,42 @@ "ansi-regex": "^4.1.0" } }, + "widest-line": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", + "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", + "dev": true, + "requires": { + "string-width": "^2.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, "wrap-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-4.0.0.tgz", @@ -4278,9 +4315,9 @@ "dev": true }, "tslib": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.0.tgz", - "integrity": "sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", + "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==", "dev": true } } @@ -6365,9 +6402,9 @@ "dev": true }, "@types/lodash": { - "version": "4.14.158", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.158.tgz", - "integrity": "sha512-InCEXJNTv/59yO4VSfuvNrZHt7eeNtWQEgnieIA+mIC+MOWM9arOWG2eQ8Vhk6NbOre6/BidiXhkZYeDY9U35w==", + "version": "4.14.159", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.159.tgz", + "integrity": "sha512-gF7A72f7WQN33DpqOWw9geApQPh4M3PxluMtaHxWHXEGSN12/WbcEk/eNSqWNQcQhF66VSZ06vCF94CrHwXJDg==", "dev": true }, "@types/long": { @@ -7031,9 +7068,9 @@ } }, "apollo": { - "version": "2.30.1", - "resolved": "https://registry.npmjs.org/apollo/-/apollo-2.30.1.tgz", - "integrity": "sha512-poh2tja2U8U5bGMxfPQjQP1voz2ZaNm/attwC8zpobdeLoT43LeQfFSTqCVmCMZJAfbMa40Cb54yV66DP2w9fw==", + "version": "2.30.2", + "resolved": "https://registry.npmjs.org/apollo/-/apollo-2.30.2.tgz", + "integrity": "sha512-CKQVvfjbeFPVew9q/dGuRNpohenK7oBTKNnwDs89ImEGoPDd7BpXRQUu/mNjUANoDpd2ztYRwO+ZxxVE8P4+sA==", "dev": true, "requires": { "@apollographql/apollo-tools": "^0.4.8", @@ -7045,21 +7082,21 @@ "@oclif/plugin-not-found": "1.2.4", "@oclif/plugin-plugins": "1.9.0", "@oclif/plugin-warn-if-update-available": "1.7.0", - "apollo-codegen-core": "^0.37.7", - "apollo-codegen-flow": "^0.35.7", - "apollo-codegen-scala": "^0.36.7", - "apollo-codegen-swift": "^0.37.7", - "apollo-codegen-typescript": "^0.37.7", + "apollo-codegen-core": "^0.37.8", + "apollo-codegen-flow": "^0.35.8", + "apollo-codegen-scala": "^0.36.8", + "apollo-codegen-swift": "^0.37.8", + "apollo-codegen-typescript": "^0.37.8", "apollo-env": "^0.6.5", "apollo-graphql": "^0.5.0", - "apollo-language-server": "^1.23.2", + "apollo-language-server": "^1.23.3", "chalk": "2.4.2", "cli-ux": "5.4.9", "env-ci": "3.2.2", "gaze": "1.1.3", "git-parse": "1.0.4", "git-rev-sync": "2.0.0", - "git-url-parse": "^11.1.2", + "git-url-parse": "11.1.2", "glob": "7.1.5", "graphql": "14.0.2 - 14.2.0 || ^14.3.1 || ^15.0.0", "graphql-tag": "2.10.4", @@ -7162,31 +7199,43 @@ } }, "apollo-codegen-core": { - "version": "0.37.7", - "resolved": "https://registry.npmjs.org/apollo-codegen-core/-/apollo-codegen-core-0.37.7.tgz", - "integrity": "sha512-7AMnzS+X7z91eUSctc0mQoQzVJrrKo+zLXevMDkGyTH+q541dYfpAdKQ5nffPcb1ZwwOONZCyl8kc8faJzD0Kw==", + "version": "0.37.8", + "resolved": "https://registry.npmjs.org/apollo-codegen-core/-/apollo-codegen-core-0.37.8.tgz", + "integrity": "sha512-5mIF75iJCGaPXPvWthQjrWKb6YD7KRioODAM6OF5r+foN70Mli2S3g7wCsnmQ0J1TtstntHVnXykHsWClj4NIg==", "dev": true, "requires": { - "@babel/generator": "7.10.4", + "@babel/generator": "7.11.0", "@babel/parser": "^7.1.3", "@babel/types": "7.10.4", "apollo-env": "^0.6.5", - "apollo-language-server": "^1.23.2", + "apollo-language-server": "^1.23.3", "ast-types": "^0.13.0", "common-tags": "^1.5.1", "recast": "^0.19.0" }, "dependencies": { "@babel/generator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.4.tgz", - "integrity": "sha512-toLIHUIAgcQygFZRAQcsLQV3CBuX6yOIru1kJk/qqqvcRmZrYe6WavZTSG+bB8MxhnL9YPf+pKQfuiP161q7ng==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.0.tgz", + "integrity": "sha512-fEm3Uzw7Mc9Xi//qU20cBKatTfs2aOtKqmvy/Vm7RkJEGFQ4xc9myCfbXxqK//ZS8MR/ciOHw6meGASJuKmDfQ==", "dev": true, "requires": { - "@babel/types": "^7.10.4", + "@babel/types": "^7.11.0", "jsesc": "^2.5.1", - "lodash": "^4.17.13", "source-map": "^0.5.0" + }, + "dependencies": { + "@babel/types": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.0.tgz", + "integrity": "sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-validator-identifier": { @@ -7215,29 +7264,41 @@ } }, "apollo-codegen-flow": { - "version": "0.35.7", - "resolved": "https://registry.npmjs.org/apollo-codegen-flow/-/apollo-codegen-flow-0.35.7.tgz", - "integrity": "sha512-q7GsbHE0UtqXFat8wGyidUJRdGkbtfUqCtuQkV5qKOOnudFR32G7dz+6i/Z9R5IqOqWVMpxLq7UeiYRiz8c1dg==", + "version": "0.35.8", + "resolved": "https://registry.npmjs.org/apollo-codegen-flow/-/apollo-codegen-flow-0.35.8.tgz", + "integrity": "sha512-nqTIvqmidL3/k6k0HcJOQfr/vG6a3i2CCKa+w1UwAR9zRJn487lvT3QGSRNuYaohB/wBdGtNk3a1V2FvFyH/xg==", "dev": true, "requires": { - "@babel/generator": "7.10.4", + "@babel/generator": "7.11.0", "@babel/types": "7.10.4", - "apollo-codegen-core": "^0.37.7", + "apollo-codegen-core": "^0.37.8", "change-case": "^4.0.0", "common-tags": "^1.5.1", "inflected": "^2.0.3" }, "dependencies": { "@babel/generator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.4.tgz", - "integrity": "sha512-toLIHUIAgcQygFZRAQcsLQV3CBuX6yOIru1kJk/qqqvcRmZrYe6WavZTSG+bB8MxhnL9YPf+pKQfuiP161q7ng==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.0.tgz", + "integrity": "sha512-fEm3Uzw7Mc9Xi//qU20cBKatTfs2aOtKqmvy/Vm7RkJEGFQ4xc9myCfbXxqK//ZS8MR/ciOHw6meGASJuKmDfQ==", "dev": true, "requires": { - "@babel/types": "^7.10.4", + "@babel/types": "^7.11.0", "jsesc": "^2.5.1", - "lodash": "^4.17.13", "source-map": "^0.5.0" + }, + "dependencies": { + "@babel/types": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.0.tgz", + "integrity": "sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-validator-identifier": { @@ -7266,53 +7327,65 @@ } }, "apollo-codegen-scala": { - "version": "0.36.7", - "resolved": "https://registry.npmjs.org/apollo-codegen-scala/-/apollo-codegen-scala-0.36.7.tgz", - "integrity": "sha512-x8EWMOrW4e/kl5QFUHCJsJzemPk1Fa0hMCyjgnlBGQHBrAkHzc33qMMs6WTGvLLL8x8sMvqxCX+NiE/jgtYEvg==", + "version": "0.36.8", + "resolved": "https://registry.npmjs.org/apollo-codegen-scala/-/apollo-codegen-scala-0.36.8.tgz", + "integrity": "sha512-6/ulLUqlKUTac5mZNAaFtzYn5LnJbXeMLftCl1NL4dyzFRn4rj+gZjDRP5e74GfDCqLAMSPyVt/6OFBxFKrs7w==", "dev": true, "requires": { - "apollo-codegen-core": "^0.37.7", + "apollo-codegen-core": "^0.37.8", "change-case": "^4.0.0", "common-tags": "^1.5.1", "inflected": "^2.0.3" } }, "apollo-codegen-swift": { - "version": "0.37.7", - "resolved": "https://registry.npmjs.org/apollo-codegen-swift/-/apollo-codegen-swift-0.37.7.tgz", - "integrity": "sha512-97uCfBt3UVq0hlAWIBZpQoZjgdeKGObxsNp2L2R5ldMLoD3cQzjzuUDJGG1DoAsn5RMqv2gGNEk5QZMrWhidLw==", + "version": "0.37.8", + "resolved": "https://registry.npmjs.org/apollo-codegen-swift/-/apollo-codegen-swift-0.37.8.tgz", + "integrity": "sha512-qRqXZg2fLBm5OhJopVwh3Y3RLXeurwAj+bH3xcpxiAX05qci9MVOxwej05nmSu4GUv1csJSLDn93ZjQfByabkQ==", "dev": true, "requires": { - "apollo-codegen-core": "^0.37.7", + "apollo-codegen-core": "^0.37.8", "change-case": "^4.0.0", "common-tags": "^1.5.1", "inflected": "^2.0.3" } }, "apollo-codegen-typescript": { - "version": "0.37.7", - "resolved": "https://registry.npmjs.org/apollo-codegen-typescript/-/apollo-codegen-typescript-0.37.7.tgz", - "integrity": "sha512-LIx1tsWqRrhTcYcRPjhbzBwSaCbMK3UKSN+AlOzNDvG/Rm6wFutHznj14kn/iqcIHmCbGGuFNjiZNbLwCJ3SyQ==", + "version": "0.37.8", + "resolved": "https://registry.npmjs.org/apollo-codegen-typescript/-/apollo-codegen-typescript-0.37.8.tgz", + "integrity": "sha512-cYysqRnW2h1xKFEZH0o636IeKvWBeeTDZmNZEyfMcl2TtyMRd0cOFUmA0gokX0JK/QzvJuTJgBKD9le9W8wV2w==", "dev": true, "requires": { - "@babel/generator": "7.10.4", + "@babel/generator": "7.11.0", "@babel/types": "7.10.4", - "apollo-codegen-core": "^0.37.7", + "apollo-codegen-core": "^0.37.8", "change-case": "^4.0.0", "common-tags": "^1.5.1", "inflected": "^2.0.3" }, "dependencies": { "@babel/generator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.4.tgz", - "integrity": "sha512-toLIHUIAgcQygFZRAQcsLQV3CBuX6yOIru1kJk/qqqvcRmZrYe6WavZTSG+bB8MxhnL9YPf+pKQfuiP161q7ng==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.0.tgz", + "integrity": "sha512-fEm3Uzw7Mc9Xi//qU20cBKatTfs2aOtKqmvy/Vm7RkJEGFQ4xc9myCfbXxqK//ZS8MR/ciOHw6meGASJuKmDfQ==", "dev": true, "requires": { - "@babel/types": "^7.10.4", + "@babel/types": "^7.11.0", "jsesc": "^2.5.1", - "lodash": "^4.17.13", "source-map": "^0.5.0" + }, + "dependencies": { + "@babel/types": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.0.tgz", + "integrity": "sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-validator-identifier": { @@ -7387,12 +7460,12 @@ } }, "apollo-language-server": { - "version": "1.23.2", - "resolved": "https://registry.npmjs.org/apollo-language-server/-/apollo-language-server-1.23.2.tgz", - "integrity": "sha512-2EfnA0DUVhGk018XYPb44EM+KuBnAqdciRD+j9BuUT3+nVq7pc8pjjcS7M8r5ea8hnOYrAoxC6f4I2YNdhjHJg==", + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/apollo-language-server/-/apollo-language-server-1.23.3.tgz", + "integrity": "sha512-WbK5tlqDpyy5TKjo1tjLIAeeTjlpZpvXURB1Qlx7ZfTqaG1846u9lgYpKw6Ydx/+1Viy60K8z530/gOgI/KmvQ==", "dev": true, "requires": { - "@apollo/federation": "0.17.0", + "@apollo/federation": "0.19.0", "@apollographql/apollo-tools": "^0.4.8", "@apollographql/graphql-language-service-interface": "^2.0.2", "@endemolshinegroup/cosmiconfig-typescript-loader": "^1.0.0", @@ -7689,13 +7762,14 @@ } }, "asn1.js": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", - "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", "requires": { "bn.js": "^4.0.0", "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" }, "dependencies": { "bn.js": { @@ -8054,6 +8128,7 @@ "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, "requires": { "core-js": "^2.4.0", "regenerator-runtime": "^0.11.0" @@ -8062,12 +8137,14 @@ "core-js": { "version": "2.6.11", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", - "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==" + "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==", + "dev": true }, "regenerator-runtime": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true } } }, @@ -8556,11 +8633,6 @@ "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", "dev": true }, - "bowser": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.9.0.tgz", - "integrity": "sha512-2ld76tuLBNFekRgmJfT2+3j5MIrP6bFict8WAIT3beq+srz1gcKNAdNKMqHqauQt63NmAa88HfP1/Ypa9Er3HA==" - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -9003,11 +9075,6 @@ } } }, - "camelize": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", - "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" - }, "caniuse-lite": { "version": "1.0.30001066", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001066.tgz", @@ -9618,9 +9685,9 @@ } }, "tslib": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.0.tgz", - "integrity": "sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", + "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==", "dev": true } } @@ -9745,9 +9812,9 @@ } }, "clsx": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.0.tgz", - "integrity": "sha512-3avwM37fSK5oP6M5rQ9CNe99lwxhXDOeSWVPAOYF6OazUTgZCMb0yWlJpmdD74REy1gkEaFiub2ULv4fq9GUhA==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", + "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==" }, "co": { "version": "4.6.0", @@ -9827,9 +9894,9 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, "commitizen": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/commitizen/-/commitizen-4.1.2.tgz", - "integrity": "sha512-LBxTQKHbVgroMz9ohpm86N+GfJobonGyvDc3zBGdZazbwCLz2tqLa48Rf2TnAdKx7/06W1i1R3SXUt5QW97qVQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/commitizen/-/commitizen-4.2.1.tgz", + "integrity": "sha512-nZsp8IThkDu7C+93BFD/mLShb9Gd6Wsaf90tpKE3x/6u5y/Q52kzanIJpGr0qvIsJ5bCMpgKtr3Lbu3miEJfaA==", "dev": true, "requires": { "cachedir": "2.2.0", @@ -9840,9 +9907,9 @@ "find-root": "1.1.0", "fs-extra": "8.1.0", "glob": "7.1.4", - "inquirer": "6.5.0", + "inquirer": "6.5.2", "is-utf8": "^0.2.1", - "lodash": "4.17.15", + "lodash": "^4.17.20", "minimist": "1.2.5", "strip-bom": "4.0.0", "strip-json-comments": "3.0.1" @@ -9874,9 +9941,9 @@ } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", "dev": true }, "strip-bom": { @@ -10019,11 +10086,6 @@ "safe-buffer": "5.1.2" } }, - "content-security-policy-builder": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/content-security-policy-builder/-/content-security-policy-builder-2.1.0.tgz", - "integrity": "sha512-/MtLWhJVvJNkA9dVLAp6fg9LxD2gfI6R2Fi1hPmfjYXSahJJzcfvoeDOxSyp4NvxMuwWv3WMssE9o31DoULHrQ==" - }, "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", @@ -10759,9 +10821,9 @@ }, "dependencies": { "caniuse-lite": { - "version": "1.0.30001112", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001112.tgz", - "integrity": "sha512-J05RTQlqsatidif/38aN3PGULCLrg8OYQOlJUKbeYVzC2mGZkZLIztwRlB3MtrfLmawUmjFlNJvy/uhwniIe1Q==" + "version": "1.0.30001113", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001113.tgz", + "integrity": "sha512-qMvjHiKH21zzM/VDZr6oosO6Ri3U0V2tC015jRXjOecwQCJtsU5zklTNTk31jQbIOP8gha0h1ccM/g0ECP+4BA==" }, "cssnano-preset-simple": { "version": "1.1.7", @@ -10837,7 +10899,8 @@ "csstype": { "version": "2.6.10", "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.10.tgz", - "integrity": "sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w==" + "integrity": "sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w==", + "dev": true }, "cucumber": { "version": "6.0.5", @@ -11004,11 +11067,6 @@ "assert-plus": "^1.0.0" } }, - "dasherize": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dasherize/-/dasherize-2.0.0.tgz", - "integrity": "sha1-bYCcnNDPe7iVLYD8hPoT1H3bEwg=" - }, "data-uri-to-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.0.tgz", @@ -11449,12 +11507,19 @@ "dev": true }, "dom-helpers": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.1.4.tgz", - "integrity": "sha512-TjMyeVUvNEnOnhzs6uAn9Ya47GmMo3qq7m+Lr/3ON0Rs5kHvb8I+SQYjLUSYn7qhEm0QjW0yrBkvz9yOrwwz1A==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.0.tgz", + "integrity": "sha512-Ru5o9+V8CpunKnz5LGgWXkmrH/20cGKwcHwS4m73zIvs54CN9epEmT/HLqFJW3kXpakAFkEdzgy1hzlJe3E4OQ==", "requires": { "@babel/runtime": "^7.8.7", - "csstype": "^2.6.7" + "csstype": "^3.0.2" + }, + "dependencies": { + "csstype": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.2.tgz", + "integrity": "sha512-ofovWglpqoqbfLNOTBNZLSbMuGrblAf1efvvArGKOZMBrIoJeu5UsAipQolkijtyQx5MtAzT/J9IHj/CEY1mJw==" + } } }, "dom-serializer": { @@ -11530,11 +11595,6 @@ } } }, - "dont-sniff-mimetype": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/dont-sniff-mimetype/-/dont-sniff-mimetype-1.1.0.tgz", - "integrity": "sha512-ZjI4zqTaxveH2/tTlzS1wFp+7ncxNZaIEWYg3lzZRHkKf5zPT/MnEG6WL0BhHMJUabkh8GeU5NL5j+rEUCb7Ug==" - }, "dot-case": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.3.tgz", @@ -12600,11 +12660,6 @@ "pend": "~1.2.0" } }, - "feature-policy": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/feature-policy/-/feature-policy-0.3.0.tgz", - "integrity": "sha512-ZtijOTFN7TzCujt1fnNhfWPFPSHeZkesff9AXZj+UEjYBynWNUIYpC87Ve4wHzyexQsImicLu7WsC2LHq7/xrQ==" - }, "figgy-pudding": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", @@ -12769,16 +12824,16 @@ "integrity": "sha512-ZPsLgjziFRbUb5tXWpEMtWp4XFnzSah8SiNfl3aoURDZ+2zi2tuIOYUULqDBV+Cb6paN+raWT+Q2qpOaCbX/Yw==" }, "firebase": { - "version": "7.17.1", - "resolved": "https://registry.npmjs.org/firebase/-/firebase-7.17.1.tgz", - "integrity": "sha512-g2Wkk2fz8VoeSrxv2PIQizm2j74EtbpxQ+wd2AvH2iEF5LRaJOsk3zVBtIlyJIQ3vGTmlutIxtyyoDAQcPO9TA==", + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-7.17.2.tgz", + "integrity": "sha512-Vj60jedalU7Hr5r7N7JWRcO7AYDFsc5hRar+GbTRL5Q4HaCyWyjD3Z/T2R/Cx09zzXH6cVnf8DVgGKoJKOUBmQ==", "requires": { "@firebase/analytics": "0.4.1", "@firebase/app": "0.6.9", "@firebase/app-types": "0.6.1", "@firebase/auth": "0.14.9", - "@firebase/database": "0.6.9", - "@firebase/firestore": "1.16.2", + "@firebase/database": "0.6.10", + "@firebase/firestore": "1.16.3", "@firebase/functions": "0.4.49", "@firebase/installations": "0.4.15", "@firebase/messaging": "0.6.21", @@ -13226,9 +13281,9 @@ } }, "git-up": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/git-up/-/git-up-4.0.1.tgz", - "integrity": "sha512-LFTZZrBlrCrGCG07/dm1aCjjpL1z9L3+5aEeI9SBhAqSc+kiA9Or1bgZhQFNppJX6h/f5McrvJt1mQXTFm6Qrw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/git-up/-/git-up-4.0.2.tgz", + "integrity": "sha512-kbuvus1dWQB2sSW4cbfTeGpCMd8ge9jx9RKnhXhuJ7tnvT+NIrTVfYZxjtflZddQYcmdOTlkAcjmx7bor+15AQ==", "dev": true, "requires": { "is-ssh": "^1.3.0", @@ -13614,50 +13669,9 @@ } }, "helmet": { - "version": "3.23.3", - "resolved": "https://registry.npmjs.org/helmet/-/helmet-3.23.3.tgz", - "integrity": "sha512-U3MeYdzPJQhtvqAVBPntVgAvNSOJyagwZwyKsFdyRa8TV3pOKVFljalPOCxbw5Wwf2kncGhmP0qHjyazIdNdSA==", - "requires": { - "depd": "2.0.0", - "dont-sniff-mimetype": "1.1.0", - "feature-policy": "0.3.0", - "helmet-crossdomain": "0.4.0", - "helmet-csp": "2.10.0", - "hide-powered-by": "1.1.0", - "hpkp": "2.0.0", - "hsts": "2.2.0", - "nocache": "2.1.0", - "referrer-policy": "1.2.0", - "x-xss-protection": "1.3.0" - }, - "dependencies": { - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - } - } - }, - "helmet-crossdomain": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/helmet-crossdomain/-/helmet-crossdomain-0.4.0.tgz", - "integrity": "sha512-AB4DTykRw3HCOxovD1nPR16hllrVImeFp5VBV9/twj66lJ2nU75DP8FPL0/Jp4jj79JhTfG+pFI2MD02kWJ+fA==" - }, - "helmet-csp": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/helmet-csp/-/helmet-csp-2.10.0.tgz", - "integrity": "sha512-Rz953ZNEFk8sT2XvewXkYN0Ho4GEZdjAZy4stjiEQV3eN7GDxg1QKmYggH7otDyIA7uGA6XnUMVSgeJwbR5X+w==", - "requires": { - "bowser": "2.9.0", - "camelize": "1.0.0", - "content-security-policy-builder": "2.1.0", - "dasherize": "2.0.0" - } - }, - "hide-powered-by": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/hide-powered-by/-/hide-powered-by-1.1.0.tgz", - "integrity": "sha512-Io1zA2yOA1YJslkr+AJlWSf2yWFkKjvkcL9Ni1XSUqnGLr/qRQe2UI3Cn/J9MsJht7yEVCe0SscY1HgVMujbgg==" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-4.1.0.tgz", + "integrity": "sha512-KWy75fYN8hOG2Rhl8e5B3WhOzb0by1boQum85TiddIE9iu6gV+TXbUjVC17wfej0o/ZUpqB9kxM0NFCZRMzf+Q==" }, "hmac-drbg": { "version": "1.0.1", @@ -13692,26 +13706,6 @@ "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", "dev": true }, - "hpkp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hpkp/-/hpkp-2.0.0.tgz", - "integrity": "sha1-EOFCJk52IVpdMMROxD3mTe5tFnI=" - }, - "hsts": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/hsts/-/hsts-2.2.0.tgz", - "integrity": "sha512-ToaTnQ2TbJkochoVcdXYm4HOCliNozlviNsg+X2XQLQvZNI/kCHR9rZxVYpJB3UPcHz80PgxRyWQ7PdU1r+VBQ==", - "requires": { - "depd": "2.0.0" - }, - "dependencies": { - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - } - } - }, "html-encoding-sniffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", @@ -14406,9 +14400,9 @@ "dev": true }, "inquirer": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.0.tgz", - "integrity": "sha512-scfHejeG/lVZSpvCXpsB4j/wQNPM5JC8kiElOI0OUTwmc1RTpXr4H32/HOlQHcZiYl2z2VElwuCVDRG8vFmbnA==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", + "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", "dev": true, "requires": { "ansi-escapes": "^3.2.0", @@ -14810,9 +14804,9 @@ "dev": true }, "is-ssh": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.3.1.tgz", - "integrity": "sha512-0eRIASHZt1E68/ixClI8bp2YK2wmBPVWEismTs6M+M099jKgrzl/3E976zIbImSIob48N2/XGe9y7ZiYdImSlg==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.3.2.tgz", + "integrity": "sha512-elEw0/0c2UscLrNG+OAorbP539E3rhliKPg+hDMWN9VwrDXfYK+4PBEykDPfxlYYtQvl84TascnQyobfQLHEhQ==", "dev": true, "requires": { "protocols": "^1.1.0" @@ -18416,26 +18410,6 @@ "json5": "^1.0.1" } }, - "normalize-url": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", - "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", - "requires": { - "object-assign": "^4.0.1", - "prepend-http": "^1.0.0", - "query-string": "^4.1.0", - "sort-keys": "^1.0.0" - } - }, - "query-string": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", - "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", - "requires": { - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" - } - }, "schema-utils": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", @@ -19075,14 +19049,14 @@ } }, "caniuse-lite": { - "version": "1.0.30001112", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001112.tgz", - "integrity": "sha512-J05RTQlqsatidif/38aN3PGULCLrg8OYQOlJUKbeYVzC2mGZkZLIztwRlB3MtrfLmawUmjFlNJvy/uhwniIe1Q==" + "version": "1.0.30001113", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001113.tgz", + "integrity": "sha512-qMvjHiKH21zzM/VDZr6oosO6Ri3U0V2tC015jRXjOecwQCJtsU5zklTNTk31jQbIOP8gha0h1ccM/g0ECP+4BA==" }, "electron-to-chromium": { - "version": "1.3.527", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.527.tgz", - "integrity": "sha512-ZlB9ySKOnS4g2Ja/TWDz4Q79NZhKV+Vsgntg85zLN08t+QsN1hK/zeDrcqwysSfbfGRVtvai6QYMczeNNUUgUA==" + "version": "1.3.529", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.529.tgz", + "integrity": "sha512-n3sriLldqNyjBlosbnPftjCY+m1dVOY307I1Y0HaHAqDGe3hRvK7ksJwWd+qs599ybR4jobCo1+7zXM9GyNMSA==" }, "node-releases": { "version": "1.1.60", @@ -19288,11 +19262,6 @@ "lower-case": "^1.1.1" } }, - "nocache": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/nocache/-/nocache-2.1.0.tgz", - "integrity": "sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q==" - }, "node-addon-api": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.1.tgz", @@ -19511,10 +19480,26 @@ "dev": true }, "normalize-url": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", - "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==", - "dev": true + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", + "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", + "requires": { + "object-assign": "^4.0.1", + "prepend-http": "^1.0.0", + "query-string": "^4.1.0", + "sort-keys": "^1.0.0" + }, + "dependencies": { + "query-string": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", + "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", + "requires": { + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + } + } + } }, "npm-conf": { "version": "1.1.3", @@ -20160,13 +20145,12 @@ } }, "parse-asn1": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz", - "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", "requires": { - "asn1.js": "^4.0.0", + "asn1.js": "^5.2.0", "browserify-aes": "^1.0.0", - "create-hash": "^1.1.0", "evp_bytestokey": "^1.0.0", "pbkdf2": "^3.0.3", "safe-buffer": "^5.1.1" @@ -20203,9 +20187,9 @@ "dev": true }, "parse-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-4.0.1.tgz", - "integrity": "sha512-d7yhga0Oc+PwNXDvQ0Jv1BuWkLVPXcAoQ/WREgd6vNNoKYaW52KI+RdOFjI63wjkmps9yUE8VS4veP+AgpQ/hA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-4.0.2.tgz", + "integrity": "sha512-HSqVz6iuXSiL8C1ku5Gl1Z5cwDd9Wo0q8CoffdAghP6bz8pJa1tcMC+m4N+z6VAS8QdksnIGq1TB6EgR4vPR6w==", "dev": true, "requires": { "is-ssh": "^1.3.0", @@ -20213,15 +20197,23 @@ } }, "parse-url": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-5.0.1.tgz", - "integrity": "sha512-flNUPP27r3vJpROi0/R3/2efgKkyXqnXwyP1KQ2U0SfFRgdizOdWfvrrvJg1LuOoxs7GQhmxJlq23IpQ/BkByg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-5.0.2.tgz", + "integrity": "sha512-Czj+GIit4cdWtxo3ISZCvLiUjErSo0iI3wJ+q9Oi3QuMYTI6OZu+7cewMWZ+C1YAnKhYTk6/TLuhIgCypLthPA==", "dev": true, "requires": { "is-ssh": "^1.3.0", "normalize-url": "^3.3.0", "parse-path": "^4.0.0", "protocols": "^1.4.0" + }, + "dependencies": { + "normalize-url": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", + "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==", + "dev": true + } } }, "parse5": { @@ -21643,9 +21635,9 @@ } }, "protocols": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/protocols/-/protocols-1.4.7.tgz", - "integrity": "sha512-Fx65lf9/YDn3hUX08XUc0J8rSux36rEsyiv21ZGUC1mOyeM3lTRpZLcrm8aAolzS4itwVfm7TAPyxC2E5zd6xg==", + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/protocols/-/protocols-1.4.8.tgz", + "integrity": "sha512-IgjKyaUSjsROSO8/D49Ab7hP8mJgTYcqApOqdPhLoPxAplXmkp+zRvsrSQjFn5by0rhm4VH0GAUELIPpx7B1yg==", "dev": true }, "proxy-addr": { @@ -22053,9 +22045,9 @@ "integrity": "sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg==" }, "react-remove-scroll": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.3.0.tgz", - "integrity": "sha512-UqVimLeAe+5EHXKfsca081hAkzg3WuDmoT9cayjBegd6UZVhlTEchleNp9J4TMGkb/ftLve7ARB5Wph+HJ7A5g==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.4.0.tgz", + "integrity": "sha512-BZIO3GaEs0Or1OhA5C//n1ibUP1HdjJmqUVUsOCMxwoIpaCocbB9TFKwHOkBa/nyYy3slirqXeiPYGwdSDiseA==", "requires": { "react-remove-scroll-bar": "^2.1.0", "react-style-singleton": "^2.1.0", @@ -22103,15 +22095,15 @@ "integrity": "sha512-vHQkaa7oUbSDTAcFk9huQXa7E8KPrZH91erPuOMoqZT513qvtbb/SzTQ33lHc71/kOoJkMbzOkc4uoA4sT7Ogg==" }, "react-virtualized": { - "version": "9.21.2", - "resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.21.2.tgz", - "integrity": "sha512-oX7I7KYiUM7lVXQzmhtF4Xg/4UA5duSA+/ZcAvdWlTLFCoFYq1SbauJT5gZK9cZS/wdYR6TPGpX/dqzvTqQeBA==", + "version": "9.22.2", + "resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.22.2.tgz", + "integrity": "sha512-5j4h4FhxTdOpBKtePSs1yk6LDNT4oGtUwjT7Nkh61Z8vv3fTG/XeOf8J4li1AYaexOwTXnw0HFVxsV0GBUqwRw==", "requires": { - "babel-runtime": "^6.26.0", - "clsx": "^1.0.1", - "dom-helpers": "^5.0.0", - "loose-envify": "^1.3.0", - "prop-types": "^15.6.0", + "@babel/runtime": "^7.7.2", + "clsx": "^1.0.4", + "dom-helpers": "^5.1.3", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2", "react-lifecycles-compat": "^3.0.4" } }, @@ -22317,11 +22309,6 @@ "symbol-observable": "^1.2.0" } }, - "referrer-policy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/referrer-policy/-/referrer-policy-1.2.0.tgz", - "integrity": "sha512-LgQJIuS6nAy1Jd88DCQRemyE3mS+ispwlqMk3b0yjZ257fI1v9c+/p6SD5gP5FGyXUIgrNOAfmyioHwZtYv2VA==" - }, "reflect.ownkeys": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz", @@ -26623,12 +26610,37 @@ } }, "widest-line": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", - "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", "dev": true, "requires": { - "string-width": "^2.1.1" + "string-width": "^4.0.0" + }, + "dependencies": { + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + } } }, "word-wrap": { @@ -27015,11 +27027,6 @@ "async-limiter": "~1.0.0" } }, - "x-xss-protection": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/x-xss-protection/-/x-xss-protection-1.3.0.tgz", - "integrity": "sha512-kpyBI9TlVipZO4diReZMAHWtS0MMa/7Kgx8hwG/EuZLiA6sg4Ah/4TRdASHhRRN3boobzcYgFRUFSgHRge6Qhg==" - }, "xml-name-validator": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", diff --git a/package.json b/package.json index 8a0875054d..5339f10f37 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matters-web", - "version": "3.13.0", + "version": "3.14.0", "description": "codebase of Matters' website", "sideEffects": false, "author": "Matters ", @@ -37,7 +37,7 @@ "@apollo/react-hooks": "^3.1.3", "@apollo/react-ssr": "^3.1.3", "@matters/apollo-upload-client": "^11.1.0", - "@matters/matters-editor": "^0.1.15", + "@matters/matters-editor": "^0.1.16", "@reach/alert": "^0.10.5", "@reach/dialog": "^0.10.5", "@reach/visually-hidden": "^0.10.4", @@ -59,11 +59,11 @@ "date-fns": "^2.15.0", "express": "^4.17.1", "fingerprintjs2": "^2.1.2", - "firebase": "^7.17.1", + "firebase": "^7.17.2", "formik": "^2.1.5", "graphql": "^14.7.0", "graphql-tag": "^2.11.0", - "helmet": "^3.23.3", + "helmet": "^4.1.0", "isomorphic-unfetch": "^3.0.0", "jump.js": "^1.0.2", "lodash": "^4.17.19", @@ -79,10 +79,10 @@ "react-copy-to-clipboard": "^5.0.2", "react-dom": "^16.13.1", "react-focus-lock": "^2.4.1", - "react-remove-scroll": "^2.3.0", + "react-remove-scroll": "^2.4.0", "react-spring": "^9.0.0-rc.3", "react-use-gesture": "^7.0.15", - "react-virtualized": "^9.21.2", + "react-virtualized": "^9.22.2", "react-waypoint": "^9.0.3", "subscriptions-transport-ws": "^0.9.17", "use-debounce": "^3.4.3", @@ -100,7 +100,7 @@ "@types/helmet": "0.0.47", "@types/jest": "^26.0.8", "@types/jump.js": "^1.0.3", - "@types/lodash": "^4.14.158", + "@types/lodash": "^4.14.159", "@types/nprogress": "0.2.0", "@types/pulltorefreshjs": "^0.1.3", "@types/react": "^16.9.43", @@ -113,7 +113,7 @@ "@types/styled-jsx": "^2.2.8", "@types/validator": "^13.1.0", "@zeit/next-bundle-analyzer": "^0.1.2", - "apollo": "^2.30.1", + "apollo": "^2.30.2", "babel-jest": "^26.1.0", "babel-plugin-dynamic-import-node": "^2.3.3", "babel-polyfill": "^6.26.0", diff --git a/src/common/enums/errorCode.ts b/src/common/enums/errorCode.ts index 44f02cd55b..8ef0e6a4d2 100644 --- a/src/common/enums/errorCode.ts +++ b/src/common/enums/errorCode.ts @@ -15,6 +15,8 @@ export const ERROR_CODES = { // Auth UNAUTHENTICATED: 'UNAUTHENTICATED', FORBIDDEN: 'FORBIDDEN', + FORBIDDEN_BY_STATE: 'FORBIDDEN_BY_STATE', + FORBIDDEN_BY_TARGET_STATE: 'FORBIDDEN_BY_TARGET_STATE', TOKEN_INVALID: 'TOKEN_INVALID', RATE_LIMIT_EXCEEDED: 'RATE_LIMIT_EXCEEDED', diff --git a/src/common/enums/externalLinks.ts b/src/common/enums/externalLinks.ts index 18a9f9b57e..a1ac564b8a 100644 --- a/src/common/enums/externalLinks.ts +++ b/src/common/enums/externalLinks.ts @@ -3,7 +3,9 @@ export const EXTERNAL_LINKS = { WEIBO: 'https://weibo.com/6695370718/profile?topnav=1&wvr=6', TELEGRAM: 'https://t.me/joinchat/BXzlWUhXaWNZ-TXJZJCzDQ', CIVIC_LIKER_SUPPORT: - 'https://help.like.co/en/articles/2600562-%E4%BB%80%E9%BA%BC%E6%98%AF%E8%AE%9A%E8%B3%9E%E5%85%AC%E6%B0%91', + 'https://docs.like.co/v/zh/user-guide/civic-liker?utm_source=Matters&utm_medium=website', CIVIC_LIKER_JOIN: 'https://liker.land/civic?is_popup=1&utm_source=Matters&utm_medium=website&utm_campaign=regular_funnel', + SUPER_LIKE: + 'https://docs.like.co/v/zh/user-guide/likecoin-button/superlike?utm_source=Matters&utm_medium=website&utm_campaign=superlike_funnel', } diff --git a/src/common/enums/text.ts b/src/common/enums/text.ts index 11f2a7ed17..c40ecd603e 100644 --- a/src/common/enums/text.ts +++ b/src/common/enums/text.ts @@ -32,12 +32,13 @@ export const TEXT = { block: '封鎖', blockUser: '封鎖用戶', bookmark: '收藏', - cancel: '取消', callbackClose: '請回到原頁面繼續操作', + cancel: '取消', change: '修改', changeEmail: '修改電子信箱', changePassword: '修改密碼', changeUserName: '修改 Matters ID', + clear: '清空', close: '關閉', CODE_EXPIRED: '驗證碼已過期', CODE_INVALID: '驗證碼不正確', @@ -111,6 +112,8 @@ export const TEXT = { followingMe: '追蹤我的', followingYou: '追蹤了你', FORBIDDEN: '你尚無權限進行該操作', + FORBIDDEN_BY_STATE: '你無權限進行該操作', + FORBIDDEN_BY_TARGET_STATE: '你無法對此對象進行該操作', forgetPassword: '忘記密碼', frequentSearch: '熱門搜尋', guide: '玩轉 Matters 實用指南', @@ -122,6 +125,9 @@ export const TEXT = { hintTerm: '我們的用戶協議和隱私政策發生了更改,請閱讀並同意後繼續使用。', hintUserDescription: '建議 50 字以內,最長 200 字', hintUserName: '4-15 個字元,僅支持英文、數字及下劃線', + hintAddTag: + '通過添加標籤幫助讀者更好地找到你的作品。如果沒有合適的標籤,你可以創建新的。', + hintEditCollection: '關聯自己或他人的作品,幫助讀者更好地發現內容。', history: '足跡', hkd: '港幣', hottestArticles: '熱門作品', @@ -219,6 +225,7 @@ export const TEXT = { successChangeUserName: 'Matters ID 修改成功', successCollapseComment: '評論已被你成功闔上', successCopy: '複製成功', + successDonation: '支持送達', successEditUserProfile: '資料已保存', successLogin: '登入成功', successLogout: '登出成功', @@ -272,6 +279,8 @@ export const TEXT = { viewModeComfortable: '標準(小圖)', viewModeCompact: '緊湊(無圖)', viewModeDefault: '默認(大圖)', + walletBalance: '錢包餘額', + walletBalanceInsufficient: '錢包餘額不足', waitingForPublish: '正在等待發佈,星際通道有點擁擠', write: '創作', yourEmail: '你的電子信箱', @@ -313,6 +322,7 @@ export const TEXT = { changeEmail: '修改邮箱', changePassword: '修改密码', changeUserName: '修改 Matters ID', + clear: '清空', close: '关闭', CODE_EXPIRED: '验证码已过期', CODE_INVALID: '验证码不正确', @@ -386,6 +396,8 @@ export const TEXT = { followingMe: '追踪我的', followingYou: '追踪了你', FORBIDDEN: '你尚无权限进行该操作', + FORBIDDEN_BY_STATE: '你无权限进行该操作', + FORBIDDEN_BY_TARGET_STATE: '你无法对此对象进行该操作', forgetPassword: '忘记密码', frequentSearch: '热门搜索', guide: '玩转 Matters 实用指南', @@ -397,6 +409,9 @@ export const TEXT = { hintTerm: '我们的用户协议和隐私政策发生了更改,请阅读并同意后继续使用。', hintUserDescription: '建议 50 字以内,最长 200 字', hintUserName: '4-15 位,仅支持英文、数字及下划线', + hintAddTag: + '通过添加标签帮助读者更好地找到你的作品。如果没有合适的标签,你可以创建新的。', + hintEditCollection: '关联自己或他人的作品,帮助读者更好地发现内容。', history: '足迹', hkd: '港币', hottestArticles: '热门作品', @@ -494,6 +509,7 @@ export const TEXT = { successChangeUserName: 'Matters ID 修改成功', successCollapseComment: '评论已被你成功折叠', successCopy: '复制成功', + successDonation: '支持送达', successEditUserProfile: '资料已保存', successLogin: '上站成功', successLogout: '登出成功', @@ -549,6 +565,8 @@ export const TEXT = { viewModeComfortable: '标准(小图)', viewModeCompact: '紧凑(无图)', viewModeDefault: '默认(大图)', + walletBalance: '钱包余额', + walletBalanceInsufficient: '钱包余额不足', waitingForPublish: '正在等待发布,星际通道有点拥挤', write: '创作', yourEmail: '你的邮箱', diff --git a/src/common/styles/mixins/mixins.css b/src/common/styles/mixins/mixins.css index 21540ce86a..90e7c810fc 100644 --- a/src/common/styles/mixins/mixins.css +++ b/src/common/styles/mixins/mixins.css @@ -20,10 +20,10 @@ justify-content: flex-start; } -@define-mixin flex-center-start { +@define-mixin flex-center-end { display: flex; align-items: center; - justify-content: flex-start; + justify-content: flex-end; } @define-mixin flex-start-space-between { diff --git a/src/common/utils/analytics.ts b/src/common/utils/analytics.ts index 2192a99a7f..3f19024ba9 100644 --- a/src/common/utils/analytics.ts +++ b/src/common/utils/analytics.ts @@ -123,6 +123,7 @@ type ArticleFeedType = | 'authors' // author feed on home page | 'collection' | 'follow-article' + | 'followee-donated-article' | 'hottest' | 'icymi' | 'newest' @@ -137,6 +138,7 @@ type ArticleFeedType = | 'topics' | 'user_article' | 'wallet' + | 'related_donations' type CommentFeedType = 'follow-comment' | 'user_comment' diff --git a/src/common/utils/firebase.ts b/src/common/utils/firebase.ts new file mode 100644 index 0000000000..9fb712427d --- /dev/null +++ b/src/common/utils/firebase.ts @@ -0,0 +1,18 @@ +const FIREBASE_CONFIG = process.env.NEXT_PUBLIC_FIREBASE_CONFIG + ? JSON.parse( + Buffer.from(process.env.NEXT_PUBLIC_FIREBASE_CONFIG, 'base64').toString() + ) + : {} + +export const initializeFirebase = async () => { + const firebase = await import('firebase/app') + + // FIXME: https://github.com/zeit/next.js/issues/1999 + if (firebase.apps.length || !process.browser) { + return + } + + firebase.initializeApp(FIREBASE_CONFIG) + + return firebase +} diff --git a/src/common/utils/index.ts b/src/common/utils/index.ts index 7a249cdeeb..49ce4b0300 100644 --- a/src/common/utils/index.ts +++ b/src/common/utils/index.ts @@ -21,3 +21,4 @@ export * from './oauth' export * from './form' export * from './push' export * from './payment' +export * from './firebase' diff --git a/src/common/utils/push.tsx b/src/common/utils/push.tsx index 0d5f56786f..2aa7f5d545 100644 --- a/src/common/utils/push.tsx +++ b/src/common/utils/push.tsx @@ -4,14 +4,10 @@ import gql from 'graphql-tag' import { Translate, Viewer } from '~/components' import { ADD_TOAST, STORE_KEY_PUSH } from '~/common/enums' +import { initializeFirebase } from '~/common/utils' import { ToggleSubscribePush } from './__generated__/ToggleSubscribePush' -const FIREBASE_CONFIG = process.env.NEXT_PUBLIC_FIREBASE_CONFIG - ? JSON.parse( - Buffer.from(process.env.NEXT_PUBLIC_FIREBASE_CONFIG, 'base64').toString() - ) - : {} const isProd = process.env.NODE_ENV === 'production' const TOGGLE_SUBSCRIBE_PUSH = gql` @@ -35,20 +31,12 @@ export const initializePush = async ({ return } - const firebase = await import('firebase/app') + const firebase = await initializeFirebase() await import('firebase/messaging') cachedClient = client - /** - * Init Firebase - */ - // FIXME: https://github.com/zeit/next.js/issues/1999 - if (firebase.apps.length || !process.browser) { - return - } - - if (firebase.messaging.isSupported()) { + if (firebase?.messaging.isSupported()) { client.writeData({ id: 'ClientPreference:local', data: { @@ -62,9 +50,6 @@ export const initializePush = async ({ return } - // Init Firebase App - firebase.initializeApp(FIREBASE_CONFIG) - // Init FCM const messaging = firebase.messaging() messaging.usePublicVapidKey(process.env.NEXT_PUBLIC_FCM_VAPID_KEY || '') @@ -99,7 +84,7 @@ export const initializePush = async ({ const isNotificationGranted = window.Notification && Notification.permission === 'granted' - if (!viewer.id || !isNotificationGranted) { + if (!viewer.isAuthed || !isNotificationGranted) { return } diff --git a/src/common/utils/types/index.ts b/src/common/utils/types/index.ts index 99d7ec3ca2..23d9c3f2da 100644 --- a/src/common/utils/types/index.ts +++ b/src/common/utils/types/index.ts @@ -55,6 +55,7 @@ export default gql` article comment tag + donation } enum ViewMode { diff --git a/src/common/utils/url.ts b/src/common/utils/url.ts index 90b3e598c0..f4dc2c07c5 100644 --- a/src/common/utils/url.ts +++ b/src/common/utils/url.ts @@ -47,7 +47,9 @@ export const changeExt = ({ key, ext }: { key: string; ext?: 'webp' }) => { } export const toSizedImageURL = ({ url, size, ext }: ToSizedImageURLProps) => { - const assetDomain = process.env.NEXT_PUBLIC_ASSET_DOMAIN || '' + const assetDomain = process.env.NEXT_PUBLIC_ASSET_DOMAIN + ? `https://${process.env.NEXT_PUBLIC_ASSET_DOMAIN}` + : '' const isOutsideLink = url.indexOf(assetDomain) < 0 const isGIF = /gif/i.test(url) diff --git a/src/common/utils/withApollo.ts b/src/common/utils/withApollo.ts index 76b670ae5c..7ea76a9878 100644 --- a/src/common/utils/withApollo.ts +++ b/src/common/utils/withApollo.ts @@ -10,6 +10,7 @@ import { onError } from 'apollo-link-error' import { createPersistedQueryLink } from 'apollo-link-persisted-queries' import http from 'http' import https from 'https' +import _get from 'lodash/get' import withApollo from 'next-with-apollo' import { @@ -31,27 +32,34 @@ const fragmentMatcher = new IntrospectionFragmentMatcher({ const isProd = process.env.NODE_ENV === 'production' -// toggle http for local dev -const agent = - (process.env.NEXT_PUBLIC_API_URL || '').split(':')[0] === 'http' - ? new http.Agent() - : new https.Agent({ - rejectUnauthorized: isProd, // allow access to https:...matters.news in localhost - }) - // links const persistedQueryLink = createPersistedQueryLink({ useGETForHashedQueries: true, }) -const httpLink = ({ headers }: { [key: string]: any }) => - createUploadLink({ - uri: process.env.NEXT_PUBLIC_API_URL, +const httpLink = ({ headers, host }: { [key: string]: any }) => { + const isOAuthSite = process.env.NEXT_PUBLIC_OAUTH_SITE_DOMAIN === host + + const apiUrl = isOAuthSite + ? process.env.NEXT_PUBLIC_OAUTH_API_URL + : process.env.NEXT_PUBLIC_API_URL + + // toggle http for local dev + const agent = + (apiUrl || '').split(':')[0] === 'http' + ? new http.Agent() + : new https.Agent({ + rejectUnauthorized: isProd, // allow access to https:...matters.news in localhost + }) + + return createUploadLink({ + uri: apiUrl, headers, fetchOptions: { agent, }, }) +} const errorLink = onError(({ graphQLErrors, networkError }) => { if (graphQLErrors) { @@ -129,12 +137,14 @@ const agentHashLink = setContext((_, { headers }) => { } }) -export default withApollo(({ ctx, headers, initialState }) => { +export default withApollo(({ ctx, headers, initialState, ...rest }) => { const cache = new InMemoryCache({ fragmentMatcher }) cache.restore(initialState || {}) // setupPersistCache(cache) + const host = ctx?.req?.headers.host || _get(window, 'location.host') + const client = new ApolloClient({ link: ApolloLink.from([ persistedQueryLink, @@ -142,7 +152,7 @@ export default withApollo(({ ctx, headers, initialState }) => { sentryLink, agentHashLink, authLink, - httpLink({ headers }), + httpLink({ headers, host }), ]), cache, resolvers, diff --git a/src/components/Analytics/index.ts b/src/components/Analytics/index.ts index 2380ff6d42..afbfe300a3 100644 --- a/src/components/Analytics/index.ts +++ b/src/components/Analytics/index.ts @@ -1,8 +1,9 @@ import gql from 'graphql-tag' import _get from 'lodash/get' +import { useEffect, useRef } from 'react' import { ANALYTIC_TYPES, ANALYTICS, GA_TRACKING_ID } from '~/common/enums' -import { deferTry } from '~/common/utils' +import { deferTry, initializeFirebase } from '~/common/utils' import { useEventListener } from '../Hook' @@ -11,24 +12,17 @@ import { AnalyticsUser } from './__generated__/AnalyticsUser' declare global { interface Window { gtag: any - firebaseAnalytics: firebase.analytics.Analytics & { - logEvent: ( - eventName: string, - eventParams?: { - [key: string]: any - }, - options?: firebase.analytics.AnalyticsCallOptions - ) => void - } } } -const handleAnalytics = ({ +const handleAnalytics = async ({ detail, user, + analytics, }: { detail: CustomEvent['detail'] user: AnalyticsUser | {} + analytics?: firebase.analytics.Analytics }) => { // get the information out of the tracked event const { type, args } = detail @@ -45,11 +39,11 @@ const handleAnalytics = ({ page_referrer: referrer, }) - window.firebaseAnalytics.logEvent('page_view', { + analytics?.logEvent('page_view', { page_referrer: referrer, }) } else { - window.firebaseAnalytics.logEvent(args[0], args[1]) + analytics?.logEvent(args[0], args[1]) } } @@ -61,7 +55,7 @@ const handleAnalytics = ({ window.gtag('config', GA_TRACKING_ID, { user_id: id, }) - window.firebaseAnalytics.setUserId(id, { global: true }) + analytics?.setUserId(id, { global: true }) } else { // visitor } @@ -69,8 +63,24 @@ const handleAnalytics = ({ } export const AnalyticsListener = ({ user }: { user: AnalyticsUser | {} }) => { + const analyticsRef = useRef() + + const initAnalytics = async () => { + const firebase = await initializeFirebase() + await import('firebase/analytics') + + const analytics = firebase?.analytics() + analyticsRef.current = analytics + } + + useEffect(() => { + initAnalytics() + }, []) + useEventListener(ANALYTICS, (detail: CustomEvent['detail']) => { - deferTry(() => handleAnalytics({ detail, user })) + deferTry(() => + handleAnalytics({ detail, user, analytics: analyticsRef.current }) + ) }) return null } diff --git a/src/components/Appreciation/index.tsx b/src/components/Appreciation/index.tsx index 3baa9ac1bf..663576ebd9 100644 --- a/src/components/Appreciation/index.tsx +++ b/src/components/Appreciation/index.tsx @@ -12,7 +12,7 @@ import { import { toPath } from '~/common/utils' -import styles from '../Transaction/styles.css' +import styles from './styles.css' import { DigestAppreciation } from './__generated__/DigestAppreciation' diff --git a/src/components/Appreciation/styles.css b/src/components/Appreciation/styles.css new file mode 100644 index 0000000000..0f292bf13c --- /dev/null +++ b/src/components/Appreciation/styles.css @@ -0,0 +1,41 @@ +.container { + @mixin flex-start-space-between; + + font-size: var(--font-size-sm); +} + +.left { + & :global(> * + *) { + margin-top: var(--spacing-x-tight); + } + + & .sender { + @mixin flex-center-start; + + font-size: var(--font-size-sm); + line-height: 1; + color: var(--color-grey-dark); + } + + & .content { + font-size: var(--font-size-md); + font-weight: var(--font-weight-medium); + color: var(--color-black); + } + + & footer { + & :global(h2) { + @mixin line-clamp; + } + } +} + +.right { + flex-shrink: 0; + margin-left: var(--spacing-base); + text-align: right; + + & .num { + margin-bottom: var(--spacing-x-tight); + } +} diff --git a/src/components/ArticleDigest/DropdownActions/index.tsx b/src/components/ArticleDigest/DropdownActions/index.tsx index c3cd9ed49f..7e27d94a4d 100644 --- a/src/components/ArticleDigest/DropdownActions/index.tsx +++ b/src/components/ArticleDigest/DropdownActions/index.tsx @@ -15,10 +15,11 @@ import { IconMore, IconSize, Menu, + Translate, ViewerContext, } from '~/components' -import { TEXT } from '~/common/enums' +import { ADD_TOAST, TEXT } from '~/common/enums' import { getQuery } from '~/common/utils' import AppreciatorsButton from './AppreciatorsButton' @@ -194,6 +195,17 @@ const DropdownActions = (props: DropdownActionsProps) => { isEditor || isCreator || viewer.info.email === 'hi@matters.news' } + const forbid = () => { + window.dispatchEvent( + new CustomEvent(ADD_TOAST, { + detail: { + color: 'red', + content: , + }, + }) + ) + } + const controls = { hasAppreciators: article.appreciationsReceived.totalCount > 0, hasFingerprint: isActive || isArticleAuthor, @@ -227,7 +239,9 @@ const DropdownActions = (props: DropdownActionsProps) => { {...controls} openFingerprintDialog={openFingerprintDialog} openAppreciatorsDialog={openAppreciatorsDialog} - openArchiveDialog={openArchiveDialog} + openArchiveDialog={ + viewer.isFrozen ? forbid : openArchiveDialog + } /> )} diff --git a/src/components/ArticleDigest/Feed/index.tsx b/src/components/ArticleDigest/Feed/index.tsx index 17b9426f95..1fa462a173 100644 --- a/src/components/ArticleDigest/Feed/index.tsx +++ b/src/components/ArticleDigest/Feed/index.tsx @@ -6,6 +6,7 @@ import React from 'react' import { Card, IconPinMedium, Img, TextIcon, Translate } from '~/components' import CLIENT_PREFERENCE from '~/components/GQL/queries/clientPreference' import { UserDigest } from '~/components/UserDigest' +import { UserDigestMiniProps } from '~/components/UserDigest/Mini' import { stripHtml, toPath } from '~/common/utils' @@ -21,12 +22,12 @@ import { ArticleDigestFeedArticlePublic } from './__generated__/ArticleDigestFee export type ArticleDigestFeedControls = { onClick?: () => any - - inFollowFeed?: boolean } & FooterActionsControls type ArticleDigestFeedProps = { article: ArticleDigestFeedArticlePublic + + actor?: (props: Partial) => React.ReactNode extraHeader?: React.ReactNode } & ArticleDigestFeedControls @@ -77,10 +78,10 @@ const BaseArticleDigestFeed = ({ inTagDetailLatest, inTagDetailSelected, inUserArticles, - inFollowFeed, onClick, + actor, extraHeader, }: ArticleDigestFeedProps) => { const { data } = useQuery(CLIENT_PREFERENCE, { @@ -120,18 +121,18 @@ const BaseArticleDigestFeed = ({
{extraHeader} +
- - {inFollowFeed && ( - - - + {actor ? ( + actor(userDigestProps) + ) : ( + )}
diff --git a/src/components/ArticleDigest/Feed/styles.css b/src/components/ArticleDigest/Feed/styles.css index b4fd7bc017..59904ed85b 100644 --- a/src/components/ArticleDigest/Feed/styles.css +++ b/src/components/ArticleDigest/Feed/styles.css @@ -4,13 +4,15 @@ header { & .left, & .right { @mixin inline-flex-center-all; + } - & .published-article { + & .left { + & :global(> * + *) { margin-left: var(--spacing-x-tight); - font-size: var(--font-size-sm); - color: var(--color-grey); } + } + & .right { & :global(> * + *) { margin-left: var(--spacing-tight); } diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx index 82cb07a125..38e8c02321 100644 --- a/src/components/Button/index.tsx +++ b/src/components/Button/index.tsx @@ -159,6 +159,7 @@ export const Button: React.FC = forwardRef( is, htmlHref, + htmlTarget, type = 'button', className, children, @@ -239,7 +240,7 @@ export const Button: React.FC = forwardRef( // anchor if (htmlHref) { return ( - +
{children} diff --git a/src/components/Buttons/Bookmark/Subscribe.tsx b/src/components/Buttons/Bookmark/Subscribe.tsx index 3afb1a21c8..2657666f27 100644 --- a/src/components/Buttons/Bookmark/Subscribe.tsx +++ b/src/components/Buttons/Bookmark/Subscribe.tsx @@ -49,6 +49,18 @@ const Subscribe = ({ articleId, size, disabled, inCard }: SubscribeProps) => { const push = data?.clientPreference.push const onClick = async () => { + if (viewer.isFrozen) { + window.dispatchEvent( + new CustomEvent(ADD_TOAST, { + detail: { + color: 'red', + content: , + }, + }) + ) + return + } + await subscribe() // skip diff --git a/src/components/Buttons/Bookmark/Unsubscribe.tsx b/src/components/Buttons/Bookmark/Unsubscribe.tsx index b14555992a..46078f270c 100644 --- a/src/components/Buttons/Bookmark/Unsubscribe.tsx +++ b/src/components/Buttons/Bookmark/Unsubscribe.tsx @@ -1,6 +1,16 @@ -import { Button, IconBookmarkActive, IconSize } from '~/components' +import { useContext } from 'react' + +import { + Button, + IconBookmarkActive, + IconSize, + Translate, + ViewerContext, +} from '~/components' import { useMutation } from '~/components/GQL' +import { ADD_TOAST } from '~/common/enums' + import TOGGLE_SUBSCRIBE_ARTICLE from '../../GQL/mutations/toggleSubscribeArticle' import { ToggleSubscribeArticle } from '~/components/GQL/mutations/__generated__/ToggleSubscribeArticle' @@ -18,6 +28,7 @@ const Unsubscribe = ({ disabled, inCard, }: UnsubscribeProps) => { + const viewer = useContext(ViewerContext) const [unsubscribe] = useMutation( TOGGLE_SUBSCRIBE_ARTICLE, { @@ -39,7 +50,21 @@ const Unsubscribe = ({ spacing={['xtight', 'xtight']} bgActiveColor={inCard ? 'grey-lighter-active' : 'grey-lighter'} aria-label="取消收藏" - onClick={unsubscribe} + onClick={async () => { + if (viewer.isFrozen) { + window.dispatchEvent( + new CustomEvent(ADD_TOAST, { + detail: { + color: 'red', + content: , + }, + }) + ) + return + } + + await unsubscribe() + }} disabled={disabled} > diff --git a/src/components/Buttons/Shuffle.tsx b/src/components/Buttons/Shuffle.tsx new file mode 100644 index 0000000000..223337c627 --- /dev/null +++ b/src/components/Buttons/Shuffle.tsx @@ -0,0 +1,31 @@ +import { + Button, + ButtonProps, + IconReload, + TextIcon, + TextIconProps, + Translate, +} from '~/components' + +type ShuffleButtonProps = ButtonProps & Pick + +export const ShuffleButton: React.FC = ({ + color, + ...props +}) => ( + +) diff --git a/src/components/Buttons/Write/index.tsx b/src/components/Buttons/Write/index.tsx index 8034eb3759..23171225f4 100644 --- a/src/components/Buttons/Write/index.tsx +++ b/src/components/Buttons/Write/index.tsx @@ -86,7 +86,7 @@ export const WriteButton = ({ allowed, isLarge, forbidden }: Props) => { new CustomEvent(ADD_TOAST, { detail: { color: 'red', - content: , + content: , }, }) ) diff --git a/src/components/Buttons/index.tsx b/src/components/Buttons/index.tsx index beb0654216..ce9670708b 100644 --- a/src/components/Buttons/index.tsx +++ b/src/components/Buttons/index.tsx @@ -8,3 +8,4 @@ export * from './Login' export * from './Write' export * from './ViewAll' export * from './ViewMore' +export * from './Shuffle' diff --git a/src/components/Comment/DropdownActions/CollapseComment/Dialog.tsx b/src/components/Comment/DropdownActions/CollapseComment/Dialog.tsx index 20decef235..d4d9596da8 100644 --- a/src/components/Comment/DropdownActions/CollapseComment/Dialog.tsx +++ b/src/components/Comment/DropdownActions/CollapseComment/Dialog.tsx @@ -78,7 +78,6 @@ const CollapseCommentDialog = ({ { onCollapse() close() diff --git a/src/components/Comment/DropdownActions/index.tsx b/src/components/Comment/DropdownActions/index.tsx index bb5c5d8b7c..7a6804a617 100644 --- a/src/components/Comment/DropdownActions/index.tsx +++ b/src/components/Comment/DropdownActions/index.tsx @@ -161,7 +161,7 @@ const DropdownActions = (props: DropdownActionsProps) => { const { comment } = props const viewer = useContext(ViewerContext) - const { isArchived, isBanned } = viewer + const { isArchived, isBanned, isFrozen } = viewer const isArticleAuthor = viewer.id === comment.article.author.id const isCommentAuthor = viewer.id === comment.author.id const isActive = comment.state === 'active' @@ -184,7 +184,7 @@ const DropdownActions = (props: DropdownActionsProps) => { new CustomEvent(ADD_TOAST, { detail: { color: 'red', - content: , + content: , }, }) ) @@ -212,9 +212,11 @@ const DropdownActions = (props: DropdownActionsProps) => { {...props} {...controls} openEditCommentDialog={ - isBanned ? forbid : openEditCommentDialog + isBanned || isFrozen ? forbid : openEditCommentDialog + } + openDeleteCommentDialog={ + isFrozen ? forbid : openDeleteCommentDialog } - openDeleteCommentDialog={openDeleteCommentDialog} openBlockUserDialog={openBlockUserDialog} openCollapseCommentDialog={openCollapseCommentDialog} /> diff --git a/src/components/Comment/FooterActions/index.tsx b/src/components/Comment/FooterActions/index.tsx index 149805a3f6..d4c51b1822 100644 --- a/src/components/Comment/FooterActions/index.tsx +++ b/src/components/Comment/FooterActions/index.tsx @@ -89,7 +89,7 @@ const BaseFooterActions = ({ }) ) } - const forbid = () => addToast('FORBIDDEN') + const forbid = () => addToast('FORBIDDEN_BY_STATE') let onClick @@ -97,7 +97,7 @@ const BaseFooterActions = ({ onClick = openLikeCoinDialog } else if (viewer.isOnboarding && article.author.id !== viewer.id) { onClick = () => addToast('failureCommentOnboarding') - } else if (viewer.isArchived) { + } else if (viewer.isArchived || viewer.isFrozen) { onClick = forbid } else if (article.author.isBlocking) { onClick = () => addToast('failureCommentBlocked') diff --git a/src/components/Context/Viewer/index.tsx b/src/components/Context/Viewer/index.tsx index 989f194ae9..bb13e59918 100644 --- a/src/components/Context/Viewer/index.tsx +++ b/src/components/Context/Viewer/index.tsx @@ -42,6 +42,7 @@ export type Viewer = ViewerUser & { isActive: boolean isArchived: boolean isBanned: boolean + isFrozen: boolean isOnboarding: boolean isInactive: boolean isCivicLiker: boolean @@ -58,8 +59,9 @@ export const processViewer = ( const isActive = state === 'active' const isBanned = state === 'banned' const isArchived = state === 'archived' + const isFrozen = state === 'frozen' const isOnboarding = state === 'onboarding' - const isInactive = isAuthed && (isBanned || isArchived) + const isInactive = isAuthed && (isBanned || isFrozen || isArchived) const isCivicLiker = viewer.liker.civicLiker const shouldSetupLikerID = isAuthed && !viewer.liker.likerId @@ -80,6 +82,7 @@ export const processViewer = ( isActive, isBanned, isArchived, + isFrozen, isOnboarding, isInactive, isCivicLiker, diff --git a/src/components/Dialog/Footer/Button.tsx b/src/components/Dialog/Footer/Button.tsx index d9cb7a1a62..948160a435 100644 --- a/src/components/Dialog/Footer/Button.tsx +++ b/src/components/Dialog/Footer/Button.tsx @@ -3,11 +3,12 @@ import { forwardRef } from 'react' import { Button, ButtonProps, IconSpinner, TextIcon } from '~/components' type DialogFooterButtonProps = { - textColor?: 'black' | 'white' - bgColor?: 'green' | 'grey-lighter' | 'red' + textColor?: 'black' | 'white' | 'grey' + bgColor?: 'green' | 'grey-lighter' | 'red' | 'white' + icon?: React.ReactNode loading?: boolean -} & ButtonProps +} & Omit const DialogFooterButton: React.FC = forwardRef( ( @@ -16,6 +17,8 @@ const DialogFooterButton: React.FC = forwardRef( textColor = 'white', bgColor = 'green', + icon, + disabled, children, ...restProps @@ -30,10 +33,11 @@ const DialogFooterButton: React.FC = forwardRef( {...restProps} > } + icon={icon || (loading && )} color={textColor} size="md" weight="md" + textPlacement="left" > {loading ? null : children} diff --git a/src/components/Dialog/Footer/styles.css b/src/components/Dialog/Footer/styles.css index 41ff01a920..d7fe8b9323 100644 --- a/src/components/Dialog/Footer/styles.css +++ b/src/components/Dialog/Footer/styles.css @@ -15,13 +15,4 @@ footer { & :global(> * + *) { margin-top: var(--spacing-base); } - - @media (--sm-up) { - flex-direction: row; - - & :global(> * + *) { - margin-top: 0; - margin-left: var(--spacing-base); - } - } } diff --git a/src/components/Dialog/Header/index.tsx b/src/components/Dialog/Header/index.tsx index da025ffffa..c06daffb88 100644 --- a/src/components/Dialog/Header/index.tsx +++ b/src/components/Dialog/Header/index.tsx @@ -55,6 +55,7 @@ const BaseHeader = ({ const Header: React.FC & { RightButton: typeof RightButton BackButton: typeof BackButton + CloseButton: typeof CloseButton } = (props) => { if (props.mode !== 'hidden') { return @@ -77,5 +78,6 @@ const Header: React.FC & { Header.RightButton = RightButton Header.BackButton = BackButton +Header.CloseButton = CloseButton export default Header diff --git a/src/components/Dialog/Message/styles.css b/src/components/Dialog/Message/styles.css index 893d52230d..d6972a2a25 100644 --- a/src/components/Dialog/Message/styles.css +++ b/src/components/Dialog/Message/styles.css @@ -16,6 +16,27 @@ & :global(h3 + p, p + p) { margin-top: var(--spacing-tight); } + + & :global(ul), + & :global(ol) { + padding-left: var(--spacing-loose); + font-size: var(--font-size-md-s); + line-height: 1.5rem; + color: var(--color-grey-dark); + } + + & :global(li) { + padding-left: var(--spacing-xx-tight); + margin: var(--spacing-x-tight) 0; + } + + & :global(ul) { + list-style-type: disc; + } + + & :global(ol) { + list-style-type: decimal; + } } .error { diff --git a/src/components/Dialogs/AddCreditDialog/index.tsx b/src/components/Dialogs/AddCreditDialog/index.tsx index beb1960bf3..614dc7029b 100644 --- a/src/components/Dialogs/AddCreditDialog/index.tsx +++ b/src/components/Dialogs/AddCreditDialog/index.tsx @@ -1,17 +1,10 @@ import { useContext, useEffect, useState } from 'react' -import { Dialog, PaymentForm, ViewerContext } from '~/components' +import { Dialog, PaymentForm, useStep, ViewerContext } from '~/components' -import { analytics, numRound } from '~/common/utils' +import { analytics } from '~/common/utils' -import { AddCredit_addCredit_transaction } from '~/components/Forms/PaymentForm/AddCredit/__generated__/AddCredit' - -type Step = - | 'setPaymentPassword' - | 'confirm' - | 'checkout' - | 'processing' - | 'complete' +type Step = 'setPaymentPassword' | 'addCredit' interface AddCreditDialogProps { children: ({ open }: { open: () => void }) => React.ReactNode @@ -20,55 +13,25 @@ interface AddCreditDialogProps { const BaseAddCreditDialog = ({ children }: AddCreditDialogProps) => { const viewer = useContext(ViewerContext) const [showDialog, setShowDialog] = useState(true) + const initialStep = viewer.status?.hasPaymentPassword - ? 'confirm' + ? 'addCredit' : 'setPaymentPassword' - const [step, setStep] = useState(initialStep) + const { currStep, forward } = useStep(initialStep) + const open = () => { - setStep(initialStep) - resetData() + forward(initialStep) setShowDialog(true) } - const close = () => setShowDialog(false) - - const [data, setData] = useState<{ - transaction: AddCredit_addCredit_transaction | undefined - client_secret: string - }>({ - transaction: undefined, - client_secret: '', - }) - - const resetData = () => - setData({ - transaction: undefined, - client_secret: '', - }) - - const onConfirm = ({ transaction, client_secret }: any) => { - setData({ ...data, transaction, client_secret }) - setStep('checkout') - analytics.trackEvent('click_button', { type: 'checkout' }) - } - - // set password if needed - const isSetPaymentPassword = step === 'setPaymentPassword' - - // confirm add credit amount - const isConfirm = step === 'confirm' - // stripe elements for credit card info - const isCheckout = step === 'checkout' - - // loader and error catching - const isProcessing = step === 'processing' + const close = () => setShowDialog(false) - // confirmation - const isComplete = step === 'complete' + const isSetPaymentPassword = currStep === 'setPaymentPassword' + const isAddCredit = currStep === 'addCredit' useEffect(() => { - analytics.trackEvent('view_add_credit_dialog', { step }) - }, [step]) + analytics.trackEvent('view_add_credit_dialog', { step: currStep }) + }, [currStep]) return ( <> @@ -76,50 +39,18 @@ const BaseAddCreditDialog = ({ children }: AddCreditDialogProps) => { setStep('confirm')} /> - ) : isProcessing ? ( - - ) : undefined - } /> {isSetPaymentPassword && ( - setStep('confirm')} /> - )} - {isConfirm && ( - - )} - {isCheckout && data.transaction && ( - setStep('processing')} - /> - )} - {isProcessing && data.transaction && ( - setStep('complete')} - /> - )} - {isComplete && data.transaction && ( - forward('addCredit')} /> )} + + {isAddCredit && } ) diff --git a/src/components/Dialogs/DonationDialog/index.tsx b/src/components/Dialogs/DonationDialog/index.tsx index 927b76b6b2..ffaa385d5c 100644 --- a/src/components/Dialogs/DonationDialog/index.tsx +++ b/src/components/Dialogs/DonationDialog/index.tsx @@ -1,28 +1,28 @@ import gql from 'graphql-tag' import { useContext, useEffect, useState } from 'react' -import { Dialog, PaymentForm, Translate, ViewerContext } from '~/components' +import { + Dialog, + PaymentForm, + Translate, + useStep, + ViewerContext, +} from '~/components' +import { UserDigest } from '~/components/UserDigest' import { PAYMENT_CURRENCY as CURRENCY } from '~/common/enums' -import { analytics, numRound } from '~/common/utils' +import { analytics } from '~/common/utils' -import { AddCredit_addCredit_transaction as AddCreditTx } from '~/components/Forms/PaymentForm/AddCredit/__generated__/AddCredit' import { PayTo_payTo_transaction as PayToTx } from '~/components/GQL/mutations/__generated__/PayTo' import { UserDonationRecipient } from './__generated__/UserDonationRecipient' type Step = + | 'setAmount' | 'addCredit' - | 'addCreditComplete' - | 'addCreditProcessing' - | 'checkout' | 'complete' | 'confirm' - | 'passwordInvalid' | 'processing' - | 'resetPasswordComplete' - | 'resetPasswordConfirm' - | 'resetPasswordRequest' - | 'setAmount' + | 'resetPassword' | 'setPaymentPassword' interface SetAmountCallbackValues { @@ -35,16 +35,6 @@ interface SetAmountOpenTabCallbackValues { transaction: PayToTx } -interface AddCreditData { - client_secret: string - transaction: AddCreditTx | undefined -} - -interface ResetPasswordData { - codeId: string - email: string -} - interface DonationDialogProps { children: ({ open }: { open: () => void }) => React.ReactNode completeCallback?: () => void @@ -57,13 +47,13 @@ const fragments = { recipient: gql` fragment UserDonationRecipient on User { id - avatar - displayName liker { likerId civicLiker } + ...UserDigestMiniUser } + ${UserDigest.Mini.fragments.user} `, } @@ -76,26 +66,16 @@ const BaseDonationDialog = ({ }: DonationDialogProps) => { const viewer = useContext(ViewerContext) - const baseAddCreditData = { transaction: undefined, client_secret: '' } - const baseResetPasswordData = { email: viewer.info.email, codeId: '' } - const [showDialog, setShowDialog] = useState(true) - const [step, setStep] = useState(defaultStep) + const { currStep, prevStep, forward, back } = useStep(defaultStep) const [windowRef, setWindowRef] = useState(undefined) const [amount, setAmount] = useState(0) const [currency, setCurrency] = useState(CURRENCY.HKD) const [payToTx, setPayToTx] = useState>() - const [addCreditData, setAddCreditData] = useState( - baseAddCreditData - ) - const [resetPasswordData, setResetPasswordData] = useState( - baseResetPasswordData - ) const open = () => { - setAddCreditData(baseAddCreditData) - setStep(defaultStep) + forward(defaultStep) setShowDialog(true) } @@ -108,7 +88,7 @@ const BaseDonationDialog = ({ setAmount(values.amount) setCurrency(values.currency) if (values.currency === CURRENCY.HKD) { - setStep( + forward( viewer.status?.hasPaymentPassword ? 'confirm' : 'setPaymentPassword' ) } @@ -117,96 +97,75 @@ const BaseDonationDialog = ({ const setAmountOpenTabCallback = (values: SetAmountOpenTabCallbackValues) => { setWindowRef(values.window) setPayToTx(values.transaction) - setStep('processing') - } - - const switchToLike = () => { - setAmount(160) - setCurrency(CURRENCY.LIKE) - setStep('setAmount') - } - - const switchToAddCredit = () => { - setAddCreditData(baseAddCreditData) - setStep('addCredit') - } - - const addCreditCallback = ({ transaction, client_secret }: any) => { - setAddCreditData({ ...addCreditData, transaction, client_secret }) - setStep('checkout') - } - - const resetPasswordRequestCallback = ({ email, codeId }: any) => { - setResetPasswordData({ ...resetPasswordData, email, codeId }) - setStep('resetPasswordConfirm') + forward('processing') } const ContinueDonationButton = ( - setStep('confirm')} - > - + forward('confirm')}> + ) - /** - * Add Credit - */ - // add credit when credit not enough - const isAddCredit = step === 'addCredit' - const isAddCreditComplete = step === 'addCreditComplete' - const isAddCreditProcessing = step === 'addCreditProcessing' - // stripe elements - const isCheckout = step === 'checkout' - // processing - const isProcessing = step === 'processing' - /** * Donation */ // complete dialog for donation - const isComplete = step === 'complete' + const isComplete = currStep === 'complete' // set donation amount - const isSetAmount = step === 'setAmount' + const isSetAmount = currStep === 'setAmount' // confirm donation amount - const isConfirm = step === 'confirm' + const isConfirm = currStep === 'confirm' + // processing + const isProcessing = currStep === 'processing' + + /** + * Add Credit + */ + const isAddCredit = currStep === 'addCredit' /** * Password */ - // wrong password - const isPasswordInvalid = step === 'passwordInvalid' - const isResetPasswordComplete = step === 'resetPasswordComplete' - const isResetPasswordConfirm = step === 'resetPasswordConfirm' - const isResetPasswordRequest = step === 'resetPasswordRequest' - const isSetPaymentPassword = step === 'setPaymentPassword' + const isResetPassword = currStep === 'resetPassword' + const isSetPaymentPassword = currStep === 'setPaymentPassword' const isHKD = currency === CURRENCY.HKD useEffect(() => { - analytics.trackEvent('view_donation_dialog', { step }) - }, [step]) + analytics.trackEvent('view_donation_dialog', { step: currStep }) + }, [currStep]) return ( <> {children({ open })} - + + ) : ( + + ) + } + rightButton={ + + } title={ - isAddCredit || isCheckout || isAddCreditProcessing + isAddCredit ? 'topUp' : isSetPaymentPassword ? 'paymentPassword' - : isResetPasswordComplete || - isResetPasswordConfirm || - isResetPasswordRequest + : isResetPassword ? 'resetPaymentPassword' + : isComplete + ? 'successDonation' : 'donation' } /> @@ -218,6 +177,9 @@ const BaseDonationDialog = ({ openTabCallback={setAmountOpenTabCallback} recipient={recipient} submitCallback={setAmountCallback} + switchToAddCredit={() => { + forward('addCredit') + }} targetId={targetId} /> )} @@ -227,17 +189,15 @@ const BaseDonationDialog = ({ amount={amount} currency={currency} recipient={recipient} - submitCallback={() => setStep(isHKD ? 'complete' : 'processing')} - switchToAddCredit={switchToAddCredit} - switchToLike={switchToLike} - switchToPasswordInvalid={() => setStep('passwordInvalid')} + submitCallback={() => forward(isHKD ? 'complete' : 'processing')} + switchToResetPassword={() => forward('resetPassword')} targetId={targetId} /> )} {isProcessing && ( setStep('complete')} + nextStep={() => forward('complete')} txId={payToTx?.id || ''} windowRef={windowRef} /> @@ -245,76 +205,24 @@ const BaseDonationDialog = ({ {isComplete && ( )} {isSetPaymentPassword && ( - setStep('confirm')} /> + forward('confirm')} /> )} - {/* below steps for add credit */} - {isAddCredit && ( - - )} - - {isCheckout && addCreditData.transaction && ( - setStep('addCreditProcessing')} - /> + )} - {isAddCreditProcessing && addCreditData.transaction && ( - setStep('addCreditComplete')} - /> - )} - - {isAddCreditComplete && addCreditData.transaction && ( - - )} - - {/* below steps for password management */} - - {isPasswordInvalid && ( - setStep('confirm')} - switchToResetPassword={() => setStep('resetPasswordRequest')} - /> - )} - - {isResetPasswordRequest && ( - - )} - - {isResetPasswordConfirm && ( - setStep('resetPasswordComplete')} - /> - )} - - {isResetPasswordComplete && ( - )} diff --git a/src/components/Dialogs/LikeCoinDialog/index.tsx b/src/components/Dialogs/LikeCoinDialog/index.tsx index 431caac7d7..fd7b160b28 100644 --- a/src/components/Dialogs/LikeCoinDialog/index.tsx +++ b/src/components/Dialogs/LikeCoinDialog/index.tsx @@ -1,6 +1,6 @@ import { useState } from 'react' -import { Dialog, SetupLikeCoin, useEventListener } from '~/components' +import { Dialog, SetupLikeCoin, useEventListener, useStep } from '~/components' import { OPEN_LIKE_COIN_DIALOG } from '~/common/enums' @@ -22,11 +22,11 @@ export const LikeCoinDialog: React.FC = ({ children, }) => { - const [step, setStep] = useState(defaultStep) - const nextStep = () => setStep('setup') + const { currStep, forward } = useStep(defaultStep) + const nextStep = () => forward('setup') const [showDialog, setShowDialog] = useState(defaultShowDialog) const open = () => { - setStep('term') + forward('term') setShowDialog(true) } const close = () => setShowDialog(false) @@ -42,11 +42,11 @@ export const LikeCoinDialog: React.FC = ({ {children && children({ open })} - {step === 'term' && ( + {currStep === 'term' && ( )} - {step === 'setup' && ( + {currStep === 'setup' && ( )} diff --git a/src/components/Dialogs/MigrationDialog/Success.tsx b/src/components/Dialogs/MigrationDialog/Success.tsx index f79eec10f4..946302b22e 100644 --- a/src/components/Dialogs/MigrationDialog/Success.tsx +++ b/src/components/Dialogs/MigrationDialog/Success.tsx @@ -33,6 +33,7 @@ const MigrationDialogSuccess = () => {

+ diff --git a/src/components/Dialogs/MigrationDialog/Upload.tsx b/src/components/Dialogs/MigrationDialog/Upload.tsx index b4d16905f5..8a591d6658 100644 --- a/src/components/Dialogs/MigrationDialog/Upload.tsx +++ b/src/components/Dialogs/MigrationDialog/Upload.tsx @@ -140,6 +140,7 @@ const MigrationDialogUpload = ({ nextStep }: MigrationDialogUploadProps) => {

+
diff --git a/src/components/Dialogs/NewsletterDialog/Content.tsx b/src/components/Dialogs/NewsletterDialog/Content.tsx index ff0d4ceeeb..ab49286133 100644 --- a/src/components/Dialogs/NewsletterDialog/Content.tsx +++ b/src/components/Dialogs/NewsletterDialog/Content.tsx @@ -1,9 +1,9 @@ import { useFormik } from 'formik' import fetch from 'isomorphic-unfetch' import _pickBy from 'lodash/pickBy' -import { useContext, useState } from 'react' +import { useContext } from 'react' -import { Dialog, Form, LanguageContext, Translate } from '~/components' +import { Dialog, Form, LanguageContext, Translate, useStep } from '~/components' import { translate } from '~/common/utils' @@ -33,10 +33,10 @@ const NewsletterDialogContent: React.FC = ({ closeDialog, }) => { const { lang } = useContext(LanguageContext) - const [step, setStep] = useState('subscribe') - const isSubscribe = step === 'subscribe' - const isComplete = step === 'complete' - const isRetry = step === 'retry' + const { currStep, forward } = useStep('subscribe') + const isSubscribe = currStep === 'subscribe' + const isComplete = currStep === 'complete' + const isRetry = currStep === 'retry' const formId = 'email-form' @@ -74,10 +74,10 @@ const NewsletterDialogContent: React.FC = ({ }) setSubmitting(false) - setStep('complete') + forward('complete') } catch (error) { setSubmitting(false) - setStep('retry') + forward('retry') } }, }) diff --git a/src/components/Dialogs/PayoutDialog/index.tsx b/src/components/Dialogs/PayoutDialog/index.tsx index 4034593fd0..3e378c60c1 100644 --- a/src/components/Dialogs/PayoutDialog/index.tsx +++ b/src/components/Dialogs/PayoutDialog/index.tsx @@ -1,6 +1,6 @@ -import { useContext, useState } from 'react' +import { useState } from 'react' -import { Dialog, PaymentForm, Translate, ViewerContext } from '~/components' +import { Dialog, PaymentForm, Translate, useStep } from '~/components' import { PAYMENT_CURRENCY as CURRENCY } from '~/common/enums' @@ -8,65 +8,39 @@ type Step = | 'complete' | 'connectStripeAccount' | 'confirm' - | 'passwordInvalid' | 'processing' - | 'resetPasswordComplete' - | 'resetPasswordConfirm' - | 'resetPasswordRequest' + | 'resetPassword' interface PayoutDialogProps { hasStripeAccount: boolean children: ({ open }: { open: () => void }) => React.ReactNode } -interface ResetPasswordData { - codeId: string - email: string -} - const BasePayoutDialog = ({ hasStripeAccount, children, }: PayoutDialogProps) => { const initialStep = hasStripeAccount ? 'confirm' : 'connectStripeAccount' - const viewer = useContext(ViewerContext) const [showDialog, setShowDialog] = useState(true) - const [step, setStep] = useState(initialStep) - - const [resetPasswordData, setResetPasswordData] = useState( - { email: viewer.info.email, codeId: '' } - ) + const { currStep, forward, prevStep, back } = useStep(initialStep) const open = () => { - setStep(initialStep) + forward(initialStep) setShowDialog(true) } const close = () => setShowDialog(false) - const resetPasswordRequestCallback = ({ email, codeId }: any) => { - setResetPasswordData({ ...resetPasswordData, email, codeId }) - setStep('resetPasswordConfirm') - } - const ContinuePayoutButton = ( - setStep('confirm')} - > + forward('confirm')}> ) - const isComplete = step === 'complete' - const isConnectStripeAccount = step === 'connectStripeAccount' - const isConfirm = step === 'confirm' - const isPasswordInvalid = step === 'passwordInvalid' - const isResetPasswordComplete = step === 'resetPasswordComplete' - const isResetPasswordConfirm = step === 'resetPasswordConfirm' - const isResetPasswordRequest = step === 'resetPasswordRequest' + const isComplete = currStep === 'complete' + const isConnectStripeAccount = currStep === 'connectStripeAccount' + const isConfirm = currStep === 'confirm' + const isResetPassword = currStep === 'resetPassword' return ( <> @@ -74,12 +48,16 @@ const BasePayoutDialog = ({ : + } + rightButton={ + + } title={ isConnectStripeAccount ? 'connectStripeAccount' - : isResetPasswordComplete || - isResetPasswordConfirm || - isResetPasswordRequest + : isResetPassword ? 'resetPaymentPassword' : isComplete ? 'paymentPayoutComplete' @@ -91,47 +69,24 @@ const BasePayoutDialog = ({ {isConnectStripeAccount && ( setStep('confirm')} + nextStep={() => forward('confirm')} /> )} {isConfirm && ( setStep('complete')} - switchToPasswordInvalid={() => setStep('passwordInvalid')} - /> - )} - - {isComplete && } - - {/* below steps for password management */} - - {isPasswordInvalid && ( - setStep('confirm')} - switchToResetPassword={() => setStep('resetPasswordRequest')} + submitCallback={() => forward('complete')} + switchToResetPassword={() => forward('resetPassword')} /> )} - {isResetPasswordRequest && ( - - )} - - {isResetPasswordConfirm && ( - setStep('resetPasswordComplete')} - /> - )} + {isComplete && } - {isResetPasswordComplete && ( - )} diff --git a/src/components/Dialogs/ResetPaymentPasswordDialog/index.tsx b/src/components/Dialogs/ResetPaymentPasswordDialog/index.tsx index 45a8d57d40..14af81b7ec 100644 --- a/src/components/Dialogs/ResetPaymentPasswordDialog/index.tsx +++ b/src/components/Dialogs/ResetPaymentPasswordDialog/index.tsx @@ -1,8 +1,6 @@ -import { useContext, useState } from 'react' +import { useState } from 'react' -import { Dialog, PaymentForm, ViewerContext } from '~/components' - -type Step = 'request' | 'confirm' | 'complete' +import { Dialog, PaymentForm } from '~/components' interface ResetPaymentPasswordProps { children: ({ open }: { open: () => void }) => React.ReactNode @@ -11,29 +9,9 @@ interface ResetPaymentPasswordProps { const BaseResetPaymentPasswordDialog: React.FC = ({ children, }) => { - const viewer = useContext(ViewerContext) - const [showDialog, setShowDialog] = useState(true) - const [step, setStep] = useState('request') - const open = () => { - setStep('request') - setShowDialog(true) - } - const close = () => setShowDialog(false) - - const [data, setData] = useState<{ email: string; codeId: string }>({ - email: viewer.info.email, - codeId: '', - }) - const requestCodeCallback = (params: any) => { - const { email, codeId } = params - setData({ ...data, email, codeId }) - setStep('confirm') - } - const isRequest = step === 'request' - const isConfirm = step === 'confirm' - const isComplete = step === 'complete' + const close = () => setShowDialog(false) return ( <> @@ -44,30 +22,9 @@ const BaseResetPaymentPasswordDialog: React.FC = ({ title="resetPaymentPassword" close={close} closeTextId="close" - leftButton={ - isConfirm ? ( - setStep('request')} /> - ) : undefined - } /> - {isRequest && ( - - )} - - {isConfirm && ( - setStep('complete')} - /> - )} - - {isComplete && ( - - )} + ) diff --git a/src/components/Dialogs/SearchSelectDialog/SearchSelectNode/Article/index.tsx b/src/components/Dialogs/SearchSelectDialog/SearchSelectNode/Article/index.tsx new file mode 100644 index 0000000000..a53f0c1cb4 --- /dev/null +++ b/src/components/Dialogs/SearchSelectDialog/SearchSelectNode/Article/index.tsx @@ -0,0 +1,58 @@ +import classNames from 'classnames' + +import { + ArticleDigestDropdown, + Card, + IconCheckedMedium, + IconCheckMedium, +} from '~/components' + +import styles from '../styles.css' + +import { ArticleDigestDropdownArticle } from '~/components/ArticleDigest/Dropdown/__generated__/ArticleDigestDropdownArticle' + +interface SearchSelectArticleProps { + article: ArticleDigestDropdownArticle + selected?: boolean + onClick: (article: ArticleDigestDropdownArticle) => void + inStagingArea?: boolean +} + +const SearchSelectArticle: React.FC = ({ + article, + selected, + onClick, + inStagingArea, +}) => { + const nodeClass = classNames({ + node: true, + selectable: inStagingArea, + }) + + return ( + onClick(article)}> +
+ + + + {inStagingArea && selected && ( + + )} + {inStagingArea && !selected && ( + + )} + + + +
+
+ ) +} + +export default SearchSelectArticle diff --git a/src/components/Dialogs/SearchSelectDialog/SearchSelectNode/Tag/index.tsx b/src/components/Dialogs/SearchSelectDialog/SearchSelectNode/Tag/index.tsx new file mode 100644 index 0000000000..3282c01e1d --- /dev/null +++ b/src/components/Dialogs/SearchSelectDialog/SearchSelectNode/Tag/index.tsx @@ -0,0 +1,47 @@ +import classNames from 'classnames' + +import { Card, IconCheckedMedium, IconCheckMedium, Tag } from '~/components' + +import styles from '../styles.css' + +import { DigestTag } from '~/components/Tag/__generated__/DigestTag' + +interface SearchSelectTagProps { + tag: DigestTag + selected?: boolean + onClick: (tag: DigestTag) => void + inStagingArea?: boolean +} + +const SearchSelectTag: React.FC = ({ + tag, + selected, + onClick, + inStagingArea, +}) => { + const nodeClass = classNames({ + node: true, + selectable: inStagingArea, + }) + + return ( + onClick(tag)}> +
+ + + + {inStagingArea && selected && ( + + )} + {inStagingArea && !selected && ( + + )} + + + +
+
+ ) +} + +export default SearchSelectTag diff --git a/src/components/Dialogs/SearchSelectDialog/SearchSelectNode/User/index.tsx b/src/components/Dialogs/SearchSelectDialog/SearchSelectNode/User/index.tsx new file mode 100644 index 0000000000..301563ebbe --- /dev/null +++ b/src/components/Dialogs/SearchSelectDialog/SearchSelectNode/User/index.tsx @@ -0,0 +1,59 @@ +import classNames from 'classnames' + +import { + Card, + IconCheckedMedium, + IconCheckMedium, + UserDigest, +} from '~/components' + +import styles from '../styles.css' + +import { UserDigestMiniUser } from '~/components/UserDigest/Mini/__generated__/UserDigestMiniUser' + +interface SearchSelectUserProps { + user: UserDigestMiniUser + selected?: boolean + onClick: (user: UserDigestMiniUser) => void + inStagingArea?: boolean +} + +const SearchSelectUser: React.FC = ({ + user, + selected, + onClick, + inStagingArea, +}) => { + const nodeClass = classNames({ + node: true, + selectable: inStagingArea, + }) + + return ( + onClick(user)}> +
+ + + + {inStagingArea && selected && ( + + )} + {inStagingArea && !selected && ( + + )} + + + +
+
+ ) +} + +export default SearchSelectUser diff --git a/src/components/Dialogs/SearchSelectDialog/SearchSelectNode/index.tsx b/src/components/Dialogs/SearchSelectDialog/SearchSelectNode/index.tsx new file mode 100644 index 0000000000..2ad8e56007 --- /dev/null +++ b/src/components/Dialogs/SearchSelectDialog/SearchSelectNode/index.tsx @@ -0,0 +1,26 @@ +import { SelectNode } from '../SearchingArea' +import Article from './Article' +import Tag from './Tag' +import User from './User' + +interface SearchSelectNodeProps { + node: SelectNode + selected?: boolean + onClick: (node: SelectNode) => void + inStagingArea?: boolean +} + +const SearchSelectNode: React.FC = ({ + node, + ...props +}) => { + return ( + <> + {node.__typename === 'Article' &&
} + {node.__typename === 'Tag' && } + {node.__typename === 'User' && } + + ) +} + +export default SearchSelectNode diff --git a/src/components/Dialogs/SearchSelectDialog/SearchSelectNode/styles.css b/src/components/Dialogs/SearchSelectDialog/SearchSelectNode/styles.css new file mode 100644 index 0000000000..7fbf4c66b6 --- /dev/null +++ b/src/components/Dialogs/SearchSelectDialog/SearchSelectNode/styles.css @@ -0,0 +1,20 @@ +:root { + --icon-size: 1.5rem; + --icon-spacing-right: var(--spacing-base); +} + +.node { + &.selectable { + padding-right: calc(var(--icon-spacing-right) + var(--icon-size)); + } +} + +.icon-select { + @mixin inline-flex-center-all; + + position: absolute; + top: 0; + right: 0; + height: 100%; + padding-right: var(--icon-spacing-right); +} diff --git a/src/components/Dialogs/SearchSelectDialog/SearchingArea/SearchInput/index.tsx b/src/components/Dialogs/SearchSelectDialog/SearchingArea/SearchInput/index.tsx new file mode 100644 index 0000000000..be4344f535 --- /dev/null +++ b/src/components/Dialogs/SearchSelectDialog/SearchingArea/SearchInput/index.tsx @@ -0,0 +1,95 @@ +import { useContext } from 'react' + +import { IconClear, IconSearch, LanguageContext } from '~/components' + +import { TEXT } from '~/common/enums' +import { translate } from '~/common/utils' + +import styles from './styles.css' + +export type SearchType = 'Article' | 'Tag' | 'User' + +interface SearchInputProps { + type: SearchType + value: string + onChange: (value: string) => void + onSubmit: (value: string) => void + onFocus: () => void +} + +const SearchInput: React.FC = ({ + type, + value, + onChange, + onSubmit, + onFocus, +}) => { + const { lang } = useContext(LanguageContext) + const textAriaLabel = translate({ id: 'search', lang }) + const textPlaceholder = { + Article: translate({ + zh_hant: '搜尋作品標題…', + zh_hans: '搜索作品标题…', + lang, + }), + Tag: translate({ + zh_hant: '搜尋標籤…', + zh_hans: '搜索标签…', + lang, + }), + User: translate({ + zh_hant: '搜尋作者…', + zh_hans: '搜索作者…', + lang, + }), + } + + return ( +
+
{ + onSubmit(value) + e.preventDefault() + }} + autoComplete="off" + > + { + onChange(e.target.value) + }} + onFocus={() => { + onFocus() + }} + /> + + + + {value && ( + + )} +
+ + +
+ ) +} + +export default SearchInput diff --git a/src/components/Dialogs/SearchSelectDialog/SearchingArea/SearchInput/styles.css b/src/components/Dialogs/SearchSelectDialog/SearchingArea/SearchInput/styles.css new file mode 100644 index 0000000000..a8934b0948 --- /dev/null +++ b/src/components/Dialogs/SearchSelectDialog/SearchingArea/SearchInput/styles.css @@ -0,0 +1,41 @@ +:root { + --search-input-height: 3rem; + --search-input-button-width: 2rem; +} + +form { + position: relative; + width: 100%; + height: var(--search-input-height); + margin-right: auto; + margin-left: auto; + font-size: var(--font-size-md-s); + line-height: var(--search-input-height); + + & :global(input[type='search']) { + height: 100%; + padding: 0 calc(var(--search-input-button-width) + var(--spacing-x-tight)); + font-size: var(--font-size-sm); + color: var(--color-black); + background-color: var(--color-grey-lighter); + + &::placeholder { + color: var(--color-grey); + opacity: 1; /* Firefox */ + } + } + + & button.search, + & button.clear { + position: absolute; + top: 0; + width: var(--search-input-button-width); + height: 100%; + } + & button.search { + left: var(--spacing-x-tight); + } + & button.clear { + right: var(--spacing-x-tight); + } +} diff --git a/src/components/Dialogs/SearchSelectDialog/SearchingArea/gql.ts b/src/components/Dialogs/SearchSelectDialog/SearchingArea/gql.ts new file mode 100644 index 0000000000..b683a74637 --- /dev/null +++ b/src/components/Dialogs/SearchSelectDialog/SearchingArea/gql.ts @@ -0,0 +1,47 @@ +import gql from 'graphql-tag' + +import { ArticleDigestDropdown, Tag, UserDigest } from '~/components' + +export const SELECT_SEARCH = gql` + query SelectSearch( + $key: String! + $type: SearchTypes! + $filter: SearchFilter + $after: String + $first: Int + ) { + search( + input: { + key: $key + type: $type + filter: $filter + after: $after + first: $first + } + ) { + pageInfo { + startCursor + endCursor + hasNextPage + } + edges { + cursor + node { + id + ... on User { + ...UserDigestMiniUser + } + ... on Article { + ...ArticleDigestDropdownArticle + } + ... on Tag { + ...DigestTag + } + } + } + } + } + ${UserDigest.Mini.fragments.user} + ${ArticleDigestDropdown.fragments.article} + ${Tag.fragments.tag} +` diff --git a/src/components/Dialogs/SearchSelectDialog/SearchingArea/index.tsx b/src/components/Dialogs/SearchSelectDialog/SearchingArea/index.tsx new file mode 100644 index 0000000000..bbee81d2f4 --- /dev/null +++ b/src/components/Dialogs/SearchSelectDialog/SearchingArea/index.tsx @@ -0,0 +1,171 @@ +import { useEffect, useState } from 'react' +import { useDebounce } from 'use-debounce' + +import { + EmptySearch, + InfiniteScroll, + Spinner, + usePublicLazyQuery, +} from '~/components' + +import { INPUT_DEBOUNCE } from '~/common/enums' +import { analytics, mergeConnections } from '~/common/utils' + +import SearchSelectNode from '../SearchSelectNode' +import styles from '../styles.css' +import { SELECT_SEARCH } from './gql' +import SearchInput, { SearchType as SearchInputType } from './SearchInput' + +import { + SelectSearch, + SelectSearch_search_edges_node, + SelectSearch_search_edges_node_Article, + SelectSearch_search_edges_node_Tag, + SelectSearch_search_edges_node_User, +} from './__generated__/SelectSearch' + +export type SearchType = SearchInputType +export interface SearchFilter { + authorId: string +} + +export type SelectNode = SelectSearch_search_edges_node +export type SelectArticle = SelectSearch_search_edges_node_Article +export type SelectTag = SelectSearch_search_edges_node_Tag +export type SelectUser = SelectSearch_search_edges_node_User + +interface SearchingAreaProps { + searchType: SearchType + searchFilter?: SearchFilter + inSearchingArea: boolean + toStagingArea: () => void + toSearchingArea: () => void + addNodeToStaging: (node: SelectNode) => void +} + +const SearchingArea: React.FC = ({ + searchType, + searchFilter, + + inSearchingArea, + toStagingArea, + toSearchingArea, + addNodeToStaging, +}) => { + // States of Searching + const [searching, setSearching] = useState(false) + const [searchingNodes, setSearchingNodes] = useState([]) + + // Data Fetching + const [searchKey, setSearchKey] = useState('') + const [debouncedSearchKey] = useDebounce(searchKey, INPUT_DEBOUNCE) + const [lazySearch, { data, loading, fetchMore }] = usePublicLazyQuery< + SelectSearch + >(SELECT_SEARCH) + + // pagination + const connectionPath = 'search' + const { edges, pageInfo } = data?.search || {} + + // load next page + const isArticle = searchType === 'Article' + const isTag = searchType === 'Tag' + // const isUser = searchType === 'User' + const loadMore = async () => { + analytics.trackEvent('load_more', { + type: isArticle ? 'search_article' : isTag ? 'search_tag' : 'search_user', + location: edges?.length || 0, + }) + + fetchMore({ + variables: { + after: pageInfo?.endCursor, + }, + updateQuery: (previousResult, { fetchMoreResult }) => + mergeConnections({ + oldData: previousResult, + newData: fetchMoreResult, + path: connectionPath, + }), + }) + } + + const nodes = edges?.map(({ node }) => node) || [] + const nodeIds = nodes.map((n) => n.id).join(',') + const search = (key: string) => { + lazySearch({ + variables: { key, type: searchType, filter: searchFilter, first: 10 }, + }) + } + + // handling changes from search input + const onSearchInputChange = (value: string) => { + setSearchKey(value) + + if (value) { + toSearchingArea() + } else { + toStagingArea() + setSearchingNodes([]) + } + } + const onSearchInputFocus = () => { + if (searchingNodes.length <= 0) { + return + } + toSearchingArea() + } + + // start searching + useEffect(() => { + search(debouncedSearchKey) + }, [debouncedSearchKey]) + + // show latest search results + useEffect(() => { + setSearching(loading) + setSearchingNodes(nodes) + }, [loading, nodeIds]) + + /** + * Render + */ + return ( + <> + + + {inSearchingArea && ( +
+ {searching && } + + {!searching && nodes.length <= 0 && } + + {!searching && nodes.length > 0 && ( + +
    + {searchingNodes.map((node) => ( +
  • + +
  • + ))} +
+
+ )} + + +
+ )} + + ) +} + +export default SearchingArea diff --git a/src/components/Dialogs/SearchSelectDialog/StagingArea/DraggableNodes.tsx b/src/components/Dialogs/SearchSelectDialog/StagingArea/DraggableNodes.tsx new file mode 100644 index 0000000000..8f62342e12 --- /dev/null +++ b/src/components/Dialogs/SearchSelectDialog/StagingArea/DraggableNodes.tsx @@ -0,0 +1,98 @@ +import { + DragDropContext, + Draggable, + Droppable, + DropResult, +} from 'react-beautiful-dnd' + +import { IconSort } from '~/components' + +import { SelectNode } from '../SearchingArea' +import SearchSelectNode from '../SearchSelectNode' +import areaStyles from '../styles.css' + +interface StagingNode { + node: SelectNode + selected: boolean +} + +interface DraggableNodesProps { + nodes: StagingNode[] + setNodes: (nodes: StagingNode[]) => void + toggleSelectNode: (node: SelectNode) => void +} + +const reorder = (list: any[], startIndex: number, endIndex: number) => { + const result = Array.from(list) + const [removed] = result.splice(startIndex, 1) + result.splice(endIndex, 0, removed) + return result +} + +const DraggableNodes: React.FC = ({ + nodes, + setNodes, + toggleSelectNode, +}) => { + const onDragEnd = (result: DropResult) => { + if (!result.destination) { + return + } + + const sourceIndex = result.source.index + const destinationIndex = result.destination.index + + if (sourceIndex === destinationIndex) { + return + } + + const newNodes = reorder( + nodes, + result.source.index, + result.destination.index + ) + setNodes(newNodes) + } + + return ( + + + {(dropProvided) => ( +
    + {nodes.map(({ node, selected }, index) => ( + + {(dragProvided) => ( +
  • + + + + + +
  • + )} +
    + ))} + + +
+ )} +
+
+ ) +} + +export default DraggableNodes diff --git a/src/components/Dialogs/SearchSelectDialog/StagingArea/index.tsx b/src/components/Dialogs/SearchSelectDialog/StagingArea/index.tsx new file mode 100644 index 0000000000..c27083443a --- /dev/null +++ b/src/components/Dialogs/SearchSelectDialog/StagingArea/index.tsx @@ -0,0 +1,93 @@ +import dynamic from 'next/dynamic' + +import { Spinner, Translate } from '~/components' + +import { TextId } from '~/common/enums' + +import { SelectNode } from '../SearchingArea' +import SearchSelectNode from '../SearchSelectNode' +import areaStyles from '../styles.css' +import styles from './styles.css' + +export interface StagingNode { + node: SelectNode + selected: boolean +} + +interface StagingAreaProps { + nodes: StagingNode[] + setNodes: (nodes: StagingNode[]) => void + + hint: TextId + inStagingArea: boolean + draggable?: boolean +} + +const DynamicDraggableNodes = dynamic(() => import('./DraggableNodes'), { + ssr: false, + loading: Spinner, +}) + +const StagingArea: React.FC = ({ + nodes, + setNodes, + + hint, + inStagingArea, + draggable, +}) => { + const toggleSelectNode = (node: SelectNode) => { + const newNodes = nodes.map(({ node: n, selected: s }) => { + if (n.id === node.id) { + return { node, selected: !s } + } + return { node: n, selected: s } + }) + setNodes(newNodes) + } + + if (!inStagingArea) { + return null + } + + return ( +
+ {/* empty hint */} + {nodes.length <= 0 && hint && ( +
+ +
+ )} + + {/* draggable */} + {nodes.length > 0 && draggable && ( + + )} + + {/* undraggable */} + {nodes.length > 0 && !draggable && ( +
    + {nodes.map(({ node, selected }) => ( +
  • + +
  • + ))} +
+ )} + + + +
+ ) +} + +export default StagingArea diff --git a/src/components/Dialogs/SearchSelectDialog/StagingArea/styles.css b/src/components/Dialogs/SearchSelectDialog/StagingArea/styles.css new file mode 100644 index 0000000000..ebf351d95e --- /dev/null +++ b/src/components/Dialogs/SearchSelectDialog/StagingArea/styles.css @@ -0,0 +1,5 @@ +.hint { + padding: 6.75rem var(--spacing-base); + color: var(--color-grey); + text-align: center; +} diff --git a/src/components/Dialogs/SearchSelectDialog/index.tsx b/src/components/Dialogs/SearchSelectDialog/index.tsx new file mode 100644 index 0000000000..48e61d14f6 --- /dev/null +++ b/src/components/Dialogs/SearchSelectDialog/index.tsx @@ -0,0 +1,147 @@ +import { useState } from 'react' + +import { Dialog, Translate } from '~/components' + +import { TextId } from '~/common/enums' + +import SearchingArea, { + SearchFilter, + SearchType, + SelectNode, +} from './SearchingArea' +import StagingArea, { StagingNode } from './StagingArea' + +/** + * is a dialog component for + * searching nodes (article, tag, and user), + * select and submit them to the component used it. + * + * It composed of three main components: + * + * - : typing keyword for searching nodes. + * + * - : showing the above search results, + * click node will add it to the staging area. + * + * - : managing staging nodes, selected nodes will be submitted. + * + */ +type Area = 'staging' | 'searching' + +export type SearchSelectNode = SelectNode + +interface SearchSelectDialogProps { + title: TextId | React.ReactElement + hint: TextId + + nodes?: SelectNode[] + onSave: (nodes: SelectNode[]) => Promise + saving?: boolean + + searchType: SearchType + searchFilter?: SearchFilter + + draggable?: boolean + + children: ({ open }: { open: () => void }) => React.ReactNode +} + +const BaseSearchSelectDialog = ({ + title, + hint, + + nodes, + onSave, + saving, + + searchType, + searchFilter, + + draggable, + + children, +}: SearchSelectDialogProps) => { + const initStagingNodes = + nodes?.map((node) => ({ node, selected: true })) || [] + + // dialog + const [showDialog, setShowDialog] = useState(true) + const open = () => { + setShowDialog(true) + setArea('staging') + setStagingNodes(initStagingNodes) + } + const close = () => setShowDialog(false) + + // area + const [area, setArea] = useState('staging') + const inStagingArea = area === 'staging' + const inSearchingArea = area === 'searching' + const toStagingArea = () => setArea('staging') + const toSearchingArea = () => setArea('searching') + + // data + const [stagingNodes, setStagingNodes] = useState( + initStagingNodes + ) + const addNodeToStaging = (node: SelectNode) => { + const isExists = stagingNodes.some(({ node: n }) => n.id === node.id) + + if (!isExists) { + setStagingNodes([...stagingNodes, { node, selected: true }]) + } + + toStagingArea() + } + + const onClickSave = async () => { + await onSave( + stagingNodes.filter(({ selected }) => !!selected).map(({ node }) => node) + ) + close() + } + + return ( + <> + {children({ open })} + + + } + loading={saving} + /> + } + /> + + + + + + + ) +} + +export const SearchSelectDialog = (props: SearchSelectDialogProps) => ( + }> + {({ open }) => <>{props.children({ open })}} + +) diff --git a/src/components/Dialogs/SearchSelectDialog/styles.css b/src/components/Dialogs/SearchSelectDialog/styles.css new file mode 100644 index 0000000000..55ca6edf7a --- /dev/null +++ b/src/components/Dialogs/SearchSelectDialog/styles.css @@ -0,0 +1,40 @@ +.area { + height: 100%; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + + @media (--sm-up) { + height: 37vh; + margin-bottom: var(--spacing-tight); + } +} + +.nodes { + height: 100%; + + & li { + position: relative; + + & .drag-handler { + @mixin flex-center-all; + @mixin all-transition; + + position: absolute; + top: 0; + left: 0; + z-index: 1; + height: 100%; + } + + &.Article { + & .drag-handler { + height: auto; + padding-top: var(--spacing-tight); + } + } + + & + li { + @mixin border-top-grey; + } + } +} diff --git a/src/components/Editor/Article/index.tsx b/src/components/Editor/Article/index.tsx index f85304132b..af7e46942c 100644 --- a/src/components/Editor/Article/index.tsx +++ b/src/components/Editor/Article/index.tsx @@ -61,10 +61,8 @@ const ArticleEditor: FC = ({ draft, update, upload }) => { mentionUsers={mentionUsers} mentionListComponent={MentionUserList} readOnly={readyOnly} - siteDomain="matters.news" theme="bubble" titleDefaultValue={title || ''} - uploadAssetDomain={process.env.NEXT_PUBLIC_ASSET_DOMAIN || ''} />
diff --git a/src/components/Editor/Sidebar/Collection/index.tsx b/src/components/Editor/Sidebar/Collection/index.tsx index 83cb50b220..9d90c8da60 100644 --- a/src/components/Editor/Sidebar/Collection/index.tsx +++ b/src/components/Editor/Sidebar/Collection/index.tsx @@ -32,10 +32,7 @@ const Collection = ({ articles, onEdit, disabled }: CollectionProps) => { defaultCollapsed={articles.length <= 0} >

- +

diff --git a/src/components/Editor/Sidebar/Tags/index.tsx b/src/components/Editor/Sidebar/Tags/index.tsx index 5741f61c21..4a44ba139f 100644 --- a/src/components/Editor/Sidebar/Tags/index.tsx +++ b/src/components/Editor/Sidebar/Tags/index.tsx @@ -25,10 +25,7 @@ const AddTags = ({ tags, onAddTag, onDeleteTag, disabled }: AddTagsProps) => { return ( } defaultCollapsed={!hasTags}>

- +

diff --git a/src/components/Empty/EmptyFolloweeDonatedArticles.tsx b/src/components/Empty/EmptyFolloweeDonatedArticles.tsx new file mode 100644 index 0000000000..3a1e817f64 --- /dev/null +++ b/src/components/Empty/EmptyFolloweeDonatedArticles.tsx @@ -0,0 +1,10 @@ +import { Empty, IconEmptyWarning, Translate } from '~/components' + +export const EmptyFolloweeDonatedArticles = () => ( + } + description={ + + } + /> +) diff --git a/src/components/Empty/EmptySearch.tsx b/src/components/Empty/EmptySearch.tsx new file mode 100644 index 0000000000..1601c4729a --- /dev/null +++ b/src/components/Empty/EmptySearch.tsx @@ -0,0 +1,12 @@ +import { Empty, IconNavSearch, Translate } from '~/components' + +export const EmptySearch = ({ + description, +}: { + description?: string | React.ReactNode +}) => ( + } + description={description || } + /> +) diff --git a/src/components/Empty/index.tsx b/src/components/Empty/index.tsx index f644b1fe3f..2ba0d35522 100644 --- a/src/components/Empty/index.tsx +++ b/src/components/Empty/index.tsx @@ -4,6 +4,7 @@ export * from './EmptyArticle' export * from './EmptyBookmark' export * from './EmptyComment' export * from './EmptyDraft' +export * from './EmptyFolloweeDonatedArticles' export * from './EmptyFollowingTag' export * from './EmptyHistory' export * from './EmptyNotice' @@ -12,3 +13,4 @@ export * from './EmptyTag' export * from './EmptyTagArticles' export * from './EmptyWarning' export * from './EmptyTransaction' +export * from './EmptySearch' diff --git a/src/components/Form/AmountInput/index.tsx b/src/components/Form/AmountInput/index.tsx index 7606695c9e..60f0f0e8dd 100644 --- a/src/components/Form/AmountInput/index.tsx +++ b/src/components/Form/AmountInput/index.tsx @@ -9,7 +9,7 @@ import styles from './styles.css' * * ```jsx * props... @@ -20,7 +20,7 @@ import styles from './styles.css' type AmountInputProps = { name: string - fixedPlaceholder: string | React.ReactNode + currency: string | React.ReactNode } & Omit & React.DetailedHTMLProps< React.InputHTMLAttributes, @@ -37,7 +37,7 @@ const AmountInput = forwardRef( hint, error, - fixedPlaceholder, + currency, ...inputProps }: AmountInputProps, @@ -55,10 +55,11 @@ const AmountInput = forwardRef( /> - {fixedPlaceholder} + {currency} , HTMLInputElement > type AmountRadioInputProps = { - currency: CURRENCY - name: string -} & Omit & + amounts: { [key in CURRENCY]: number[] } +} & BaseOptionProps & + Omit & React.DetailedHTMLProps< React.InputHTMLAttributes, HTMLInputElement > -const amountOptions = { - [CURRENCY.HKD]: [5, 10, 50, 80, 100, 200], - [CURRENCY.LIKE]: [160, 660, 1660], -} - const AmountOption: React.FC = ({ amount, currency, + balance, name, fieldMsgId, @@ -57,16 +58,21 @@ const AmountOption: React.FC = ({ }) => { const fieldId = `field-${name}-${amount}` - const classes = classNames({ + const isBalanceInsufficient = + typeof balance === 'number' ? balance < amount : false + + const amountClasses = classNames({ amount: true, [currency === CURRENCY.LIKE ? 'like' : 'hkd']: true, active: value === amount, - disabled, + disabled: disabled || isBalanceInsufficient, }) + return ( -
  • +
  • @@ -85,6 +92,8 @@ const AmountOption: React.FC = ({ const AmountRadioInput: React.FC = ({ currency, + balance, + amounts, name, hint, @@ -93,26 +102,22 @@ const AmountRadioInput: React.FC = ({ ...inputProps }) => { const fieldMsgId = `field-msg-${name}` - const options = amountOptions[currency] + + const options = amounts[currency] const baseInputProps = { ...inputProps, currency, + balance, fieldMsgId, name, - type: 'radio', } return (
      {options.map((option) => ( - + ))}
    diff --git a/src/components/Form/AmountRadioInput/styles.css b/src/components/Form/AmountRadioInput/styles.css index 81313fe048..ebac0a3820 100644 --- a/src/components/Form/AmountRadioInput/styles.css +++ b/src/components/Form/AmountRadioInput/styles.css @@ -24,6 +24,12 @@ padding-bottom: var(--spacing-base); text-align: center; } + + &.disabled { + pointer-events: none; + cursor: not-allowed; + opacity: 0.5; + } } .hkd { @@ -44,9 +50,4 @@ color: var(--color-white); background: var(--color-matters-green); } - - &.disabled { - cursor: not-allowed; - opacity: 0.5; - } } diff --git a/src/components/Form/CurrencyRadioInput/index.tsx b/src/components/Form/CurrencyRadioInput/index.tsx index 6a1c5d5fc7..d28f4497d0 100644 --- a/src/components/Form/CurrencyRadioInput/index.tsx +++ b/src/components/Form/CurrencyRadioInput/index.tsx @@ -62,6 +62,7 @@ const CurrencyOption: React.FC = ({ inactive, disabled, }) + return (