diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 18262b9f50..fbf3344b10 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -101,6 +101,7 @@ jobs: region: ap-southeast-1 deployment_package: deploy.zip use_existing_version_if_available: true + wait_for_deployment: false # === `master` branch === - name: Upload Assets (production) diff --git a/bin/buildFragmentTypes.js b/bin/buildFragmentTypes.js index 68d3f29228..7893f9f5c3 100644 --- a/bin/buildFragmentTypes.js +++ b/bin/buildFragmentTypes.js @@ -3,11 +3,14 @@ const fs = require('fs') const dotenv = require('dotenv') // load environment variables from .env -const dotEnvResult = dotenv.config() +const dotEnvResult = dotenv.config({ + path: '.env.dev' +}) if (dotEnvResult.error) { console.error(dotEnvResult.error) } + fetch(process.env.NEXT_PUBLIC_API_URL, { method: 'POST', headers: { diff --git a/package-lock.json b/package-lock.json index 1062868123..96e4d99d0d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "matters-web", - "version": "3.8.0", + "version": "3.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -4152,59 +4152,59 @@ } }, "@sentry/browser": { - "version": "5.19.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-5.19.0.tgz", - "integrity": "sha512-Cz8PnzC5NGfpHIGCmHLgA6iDEBVELwo4Il/iFweXjs4+VL4biw62lI32Q4iLCCpmX0t5UvrWBOnju2v8zuFIjA==", + "version": "5.19.1", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-5.19.1.tgz", + "integrity": "sha512-Aon5Nc2n8sIXKg6Xbr4RM3/Xs7vFpXksL56z3yIuGrmpCM8ToQ25/tQv8h+anYi72x5bn1npzaXB/NwU1Qwfhg==", "requires": { - "@sentry/core": "5.19.0", - "@sentry/types": "5.19.0", - "@sentry/utils": "5.19.0", + "@sentry/core": "5.19.1", + "@sentry/types": "5.19.1", + "@sentry/utils": "5.19.1", "tslib": "^1.9.3" } }, "@sentry/core": { - "version": "5.19.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.19.0.tgz", - "integrity": "sha512-ry1Zms6jrVQPEwmfywItyUhOgabPrykd8stR1x/u2t1YiISWlR813fE5nzdwgW5mxEitXz/ikTwvrfK3egsDWQ==", - "requires": { - "@sentry/hub": "5.19.0", - "@sentry/minimal": "5.19.0", - "@sentry/types": "5.19.0", - "@sentry/utils": "5.19.0", + "version": "5.19.1", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.19.1.tgz", + "integrity": "sha512-BGGxjeT95Og/hloBhQXAVcndVXPmIU6drtF3oKRT12cBpiG965xEDEUwiJVvyb5MAvojdVEZBK2LURUFY/d7Zw==", + "requires": { + "@sentry/hub": "5.19.1", + "@sentry/minimal": "5.19.1", + "@sentry/types": "5.19.1", + "@sentry/utils": "5.19.1", "tslib": "^1.9.3" } }, "@sentry/hub": { - "version": "5.19.0", - "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.19.0.tgz", - "integrity": "sha512-UFaQLa1XAa02ZcxUmN9GdDpGs3vHK1LpOyYooimX8ttWa4KAkMuP+O5uXH1TJabry6o/cRwYisg2k6PBSy8gxw==", + "version": "5.19.1", + "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.19.1.tgz", + "integrity": "sha512-XjfbNGWVeDsP38alm5Cm08YPIw5Hu6HbPkw7a3y1piViTrg4HdtsE+ZJqq0YcURo2RTpg6Ks6coCS/zJxIPygQ==", "requires": { - "@sentry/types": "5.19.0", - "@sentry/utils": "5.19.0", + "@sentry/types": "5.19.1", + "@sentry/utils": "5.19.1", "tslib": "^1.9.3" } }, "@sentry/minimal": { - "version": "5.19.0", - "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.19.0.tgz", - "integrity": "sha512-3FHgirwOuOMF4VlwHboYObPT9c0S9b9y5FW0DGgNt8sJPezS00VaJti/38ZwOHQJ4fJ6ubt/Y3Kz0eBTVxMCCQ==", + "version": "5.19.1", + "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.19.1.tgz", + "integrity": "sha512-pgNfsaCroEsC8gv+NqmPTIkj4wyK6ZgYLV12IT4k2oJLkGyg45TSAKabyB7oEP5jsj8sRzm8tDomu8M4HpaCHg==", "requires": { - "@sentry/hub": "5.19.0", - "@sentry/types": "5.19.0", + "@sentry/hub": "5.19.1", + "@sentry/types": "5.19.1", "tslib": "^1.9.3" } }, "@sentry/types": { - "version": "5.19.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.19.0.tgz", - "integrity": "sha512-NlHLS9mwCEimKUA5vClAwVhXFLsqSF3VJEXU+K61fF6NZx8zfJi/HTrIBtoM4iNSAt9o4XLQatC1liIpBC06tg==" + "version": "5.19.1", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.19.1.tgz", + "integrity": "sha512-M5MhTLnjqYFwxMwcFPBpBgYQqI9hCvtVuj/A+NvcBHpe7VWOXdn/Sys+zD6C76DWGFYQdw3OWCsZimP24dL8mA==" }, "@sentry/utils": { - "version": "5.19.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.19.0.tgz", - "integrity": "sha512-EU/T9aJrI8isexRnyDx5InNw/HjSQ0nKI7YWdiwfFrJusqQ/uisnCGK7vw6gGHDgiAHMXW3TJ38NHpNBin6y7Q==", + "version": "5.19.1", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.19.1.tgz", + "integrity": "sha512-neUiNBnZSHjWTZWy2QV02EHTx1C2L3DBPzRXlh0ca5xrI7LMBLmhkHlhebn1E5ky3PW1teqZTgmh0jZoL99TEA==", "requires": { - "@sentry/types": "5.19.0", + "@sentry/types": "5.19.1", "tslib": "^1.9.3" } }, @@ -4232,9 +4232,9 @@ } }, "@stripe/stripe-js": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-1.7.0.tgz", - "integrity": "sha512-/OreTnc8qWBsNrkpNTOxn67oRlqa+PZXukpiyZTcPo9/DRYyxtBZKvplFbH0C/qC/w0TpN8cueRl3h/dNYO4eg==" + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-1.8.0.tgz", + "integrity": "sha512-VsIohJw70LIdB4b621RN6pTyw49ZMy+QjB81fl+Psu5X5ppiq/c+rG0F+XKHSVpp4JZRNDkQnsOGeuHow0aluA==" }, "@stylelint/postcss-css-in-js": { "version": "0.37.1", @@ -5624,21 +5624,22 @@ "dev": true }, "@testing-library/dom": { - "version": "7.18.1", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.18.1.tgz", - "integrity": "sha512-tGq4KAFjaI7j375sMM1RRVleWA0viJWs/w69B+nyDkqYLNkhdTHdV6mGkspJlkn3PUfyBDi3rERDv4PA/LrpVA==", + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.20.0.tgz", + "integrity": "sha512-TywaC+qDGm/Ro34kRYkFQPdT+pxSF4UjZGLIqcGfFQH5IGR43Y7sGLPnkieIW/GNsu337oxNsLUAgpI0JWhXHw==", "dev": true, "requires": { "@babel/runtime": "^7.10.3", + "@types/aria-query": "^4.2.0", "aria-query": "^4.2.2", "dom-accessibility-api": "^0.4.5", "pretty-format": "^25.5.0" }, "dependencies": { "@babel/runtime": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.3.tgz", - "integrity": "sha512-RzGO0RLSdokm9Ipe/YD+7ww8X2Ro79qiXZF3HU9ljrM+qnJmH1Vqth+hbiQZy761LnMJTMitHDuKVYTk3k4dLw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.4.tgz", + "integrity": "sha512-UpTN5yUJr9b4EX2CnGNWIvER7Ab83ibv0pcvvHc4UOdrBI5jb8bj+32cCwPX6xu0mt2daFNjYhoi+X7beH0RSw==", "dev": true, "requires": { "regenerator-runtime": "^0.13.4" @@ -5647,23 +5648,30 @@ } }, "@testing-library/react": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-10.4.3.tgz", - "integrity": "sha512-A/ydYXcwAcfY7vkPrfUkUTf9HQLL3/GtixTefcu3OyGQtAYQ7XBQj1S9FWbLEhfWa0BLwFwTBFS3Ao1O0tbMJg==", + "version": "10.4.4", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-10.4.4.tgz", + "integrity": "sha512-SKDQ2jBdg9UQQYQragkvXOzNp4hnCdOvXyZ52rg+OXiiumVxkAutdvvRzBF4PrbvMQ27Z6gx0GVo2YQ1Mcip8g==", "dev": true, "requires": { "@babel/runtime": "^7.10.3", - "@testing-library/dom": "^7.17.1" + "@testing-library/dom": "^7.17.1", + "semver": "^7.3.2" }, "dependencies": { "@babel/runtime": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.3.tgz", - "integrity": "sha512-RzGO0RLSdokm9Ipe/YD+7ww8X2Ro79qiXZF3HU9ljrM+qnJmH1Vqth+hbiQZy761LnMJTMitHDuKVYTk3k4dLw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.4.tgz", + "integrity": "sha512-UpTN5yUJr9b4EX2CnGNWIvER7Ab83ibv0pcvvHc4UOdrBI5jb8bj+32cCwPX6xu0mt2daFNjYhoi+X7beH0RSw==", "dev": true, "requires": { "regenerator-runtime": "^0.13.4" } + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true } } }, @@ -5685,6 +5693,12 @@ } } }, + "@types/aria-query": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.0.tgz", + "integrity": "sha512-iIgQNzCm0v7QMhhe4Jjn9uRh+I6GoPmt03CbEtwx3ao8/EfoQcmgtqH4vQ5Db/lxiIGaWDv6nwvunuh0RyX0+A==", + "dev": true + }, "@types/autosize": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/@types/autosize/-/autosize-3.0.7.tgz", @@ -7098,18 +7112,18 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.3.tgz", - "integrity": "sha512-RzGO0RLSdokm9Ipe/YD+7ww8X2Ro79qiXZF3HU9ljrM+qnJmH1Vqth+hbiQZy761LnMJTMitHDuKVYTk3k4dLw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.4.tgz", + "integrity": "sha512-UpTN5yUJr9b4EX2CnGNWIvER7Ab83ibv0pcvvHc4UOdrBI5jb8bj+32cCwPX6xu0mt2daFNjYhoi+X7beH0RSw==", "dev": true, "requires": { "regenerator-runtime": "^0.13.4" } }, "@babel/runtime-corejs3": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.10.3.tgz", - "integrity": "sha512-HA7RPj5xvJxQl429r5Cxr2trJwOfPjKiqhCXcdQPSqO2G0RHPZpXu4fkYmBaTKCp2c/jRaMK9GB/lN+7zvvFPw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.10.4.tgz", + "integrity": "sha512-BFlgP2SoLO9HJX9WBwN67gHWMBhDX/eDz64Jajd6mR/UAUzqrNMm99d4qHnVaKscAElZoFiPv+JpR/Siud5lXw==", "dev": true, "requires": { "core-js-pure": "^3.0.0", @@ -12880,9 +12894,9 @@ "dev": true }, "graphql": { - "version": "14.6.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-14.6.0.tgz", - "integrity": "sha512-VKzfvHEKybTKjQVpTFrA5yUq2S9ihcZvfJAtsDBBCuV6wauPu1xl/f9ehgVf0FcEJJs4vz6ysb/ZMkGigQZseg==", + "version": "14.7.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-14.7.0.tgz", + "integrity": "sha512-l0xWZpoPKpppFzMfvVyFmp9vLN7w/ZZJPefUicMCepfJeQ8sMcztloGYY9DfjVPo6tIUDzU5Hw3MUbIjj9AVVA==", "requires": { "iterall": "^1.2.2" } @@ -18711,9 +18725,9 @@ "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" }, "next-with-apollo": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/next-with-apollo/-/next-with-apollo-5.0.1.tgz", - "integrity": "sha512-pLNUWYNwY36u/L0Sco9PqML+gbCtyzE85ZysEGOzKfxdWbmazo5cUTGYDqwbHWS3xE7Mh79A26k1WJsIshJurQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/next-with-apollo/-/next-with-apollo-5.1.0.tgz", + "integrity": "sha512-LaBv4BKkyc2SUhkTbPODjCr17mk6mB7yC7yGnbfZ11+PawJ+YQZHsqLmh7z5exNba+HuBCZ/Y1I44qm9zNTZRw==", "requires": { "isomorphic-unfetch": "^3.0.0" } @@ -25931,9 +25945,9 @@ "integrity": "sha512-DPBPh1i2adCZoIArRlTuKRy7yue7QogtEnfv0AKrWsY+GA+4EKe37zhRDouNnyWMoNQFYZZRF+2dLHsWE4YvJA==" }, "use-debounce": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-3.4.2.tgz", - "integrity": "sha512-rW44wZaFPh3XiwUzGBA0JRuklpbfKO/szU/5CYD2Q/erLmCem63lJ650p3a+NJE6S+g0rulKtBOfa/3rw/GN+Q==" + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-3.4.3.tgz", + "integrity": "sha512-nxy+opOxDccWfhMl36J5BSCTpvcj89iaQk2OZWLAtBJQj7ISCtx1gh+rFbdjGfMl6vtCZf6gke/kYvrkVfHMoA==" }, "use-memo-one": { "version": "1.1.1", diff --git a/package.json b/package.json index 92cc484132..003afd6a83 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matters-web", - "version": "3.9.0", + "version": "3.10.0", "description": "codebase of Matters' website", "sideEffects": false, "author": "", @@ -39,9 +39,9 @@ "@reach/alert": "^0.10.5", "@reach/dialog": "^0.10.5", "@reach/visually-hidden": "^0.10.4", - "@sentry/browser": "^5.19.0", + "@sentry/browser": "^5.19.1", "@stripe/react-stripe-js": "^1.1.2", - "@stripe/stripe-js": "^1.7.0", + "@stripe/stripe-js": "^1.8.0", "@tippyjs/react": "^4.0.4", "apollo-cache-inmemory": "^1.6.6", "apollo-cache-persist": "^0.1.1", @@ -59,14 +59,14 @@ "fingerprintjs2": "^2.1.0", "firebase": "^7.15.5", "formik": "^2.1.4", - "graphql": "^14.6.0", + "graphql": "^14.7.0", "graphql-tag": "^2.10.3", "helmet": "^3.23.3", "isomorphic-unfetch": "^3.0.0", "jump.js": "^1.0.2", "lodash": "^4.17.15", "next": "^9.4.4", - "next-with-apollo": "^5.0.1", + "next-with-apollo": "^5.1.0", "nprogress": "^0.2.0", "number-precision": "^1.5.0", "path-to-regexp": "^6.1.0", @@ -83,13 +83,13 @@ "react-virtualized": "^9.21.2", "react-waypoint": "^9.0.3", "subscriptions-transport-ws": "^0.9.16", - "use-debounce": "^3.4.2", + "use-debounce": "^3.4.3", "validator": "^13.1.1" }, "devDependencies": { "@babel/plugin-proposal-optional-chaining": "^7.10.4", "@svgr/webpack": "^5.4.0", - "@testing-library/react": "^10.4.3", + "@testing-library/react": "^10.4.4", "@types/autosize": "^3.0.7", "@types/classnames": "^2.2.10", "@types/express": "^4.17.4", diff --git a/src/common/enums/fileTypes.ts b/src/common/enums/fileTypes.ts index 4dbbadf38e..db108dc75a 100644 --- a/src/common/enums/fileTypes.ts +++ b/src/common/enums/fileTypes.ts @@ -2,7 +2,6 @@ export const ACCEPTED_UPLOAD_IMAGE_TYPES: string[] = [ 'image/gif', 'image/png', 'image/jpeg', - 'image/svg+xml', 'image/webp', ] diff --git a/src/common/gql/fragmentTypes.json b/src/common/gql/fragmentTypes.json index fa7b25c222..b715c5daa1 100644 --- a/src/common/gql/fragmentTypes.json +++ b/src/common/gql/fragmentTypes.json @@ -17,8 +17,8 @@ "name": "Connection", "possibleTypes": [ { "name": "ArticleConnection" }, - { "name": "ResponseConnection" }, { "name": "CommentConnection" }, + { "name": "ResponseConnection" }, { "name": "TagConnection" }, { "name": "UserConnection" }, { "name": "DraftConnection" }, diff --git a/src/common/utils/analytics.ts b/src/common/utils/analytics.ts index 4b7b44f29e..3c4061c03a 100644 --- a/src/common/utils/analytics.ts +++ b/src/common/utils/analytics.ts @@ -26,7 +26,11 @@ type EventArgs = | ['share', ShareProp] | ['purchase', PurchaseProp] -type ClickFeedProp = ArticleFeedProp | UserFeedProp | TagFeedProp +type ClickFeedProp = + | ArticleFeedProp + | CommentFeedProp + | UserFeedProp + | TagFeedProp interface ClickButtonProp { type: @@ -42,7 +46,7 @@ interface ClickButtonProp { } interface LoadMoreProp { - type: ArticleFeedType | UserFeedType | TagFeedType + type: ArticleFeedType | CommentFeedType | UserFeedType | TagFeedType location: number } @@ -76,7 +80,13 @@ interface ArticleFeedProp { | 'small_cover' | 'no_cover' | 'title' - | 'comment' // comment in follow feed + location: number +} + +interface CommentFeedProp { + type: CommentFeedType + contentType: 'comment' + styleType: 'card' location: number } @@ -102,7 +112,7 @@ type ArticleFeedType = | 'all_topics' | 'authors' // author feed on home page | 'collection' - | 'follow' + | 'follow-article' | 'hottest' | 'icymi' | 'newest' @@ -118,6 +128,8 @@ type ArticleFeedType = | 'user_article' | 'wallet' +type CommentFeedType = 'follow-comment' + type UserFeedType = | 'all_authors' | 'all_icymi' @@ -143,6 +155,7 @@ type UserFeedType = type TagFeedType = | 'all_tags' // all tags page + | 'follow-tag' | 'search' | 'search_tag' | 'tags' // tag feed on home page diff --git a/src/common/utils/resolvers/clientPreference.ts b/src/common/utils/resolvers/clientPreference.ts index bb2ec68f59..62060d04b7 100644 --- a/src/common/utils/resolvers/clientPreference.ts +++ b/src/common/utils/resolvers/clientPreference.ts @@ -11,6 +11,7 @@ export default (_: any) => { __typename: 'Push', }, routeHistory: [], + followFeedType: 'article', __typename: 'ClientPreference', } } diff --git a/src/common/utils/types/index.ts b/src/common/utils/types/index.ts index 121987a285..60d9260b86 100644 --- a/src/common/utils/types/index.ts +++ b/src/common/utils/types/index.ts @@ -28,6 +28,7 @@ export default gql` wall: Boolean! push: Push! routeHistory: [URL!] + followFeedType: FollowFeedType } type ClientInfo { @@ -50,6 +51,11 @@ export default gql` newest } + enum FollowFeedType { + article + comment + } + enum ViewMode { default comfortable diff --git a/src/common/utils/url.ts b/src/common/utils/url.ts index 6674f8e085..2e3ce88078 100644 --- a/src/common/utils/url.ts +++ b/src/common/utils/url.ts @@ -34,16 +34,16 @@ interface ToSizedImageURLProps { const PROCESSED_PREFIX = 'processed' -export const changeExt = ({ url, ext }: { url: string; ext?: 'webp' }) => { - const list = url.split('.') +export const changeExt = ({ key, ext }: { key: string; ext?: 'webp' }) => { + const list = key.split('.') const hasExt = list.length > 1 const newExt = ext || list.slice(-1)[0] || '' if (hasExt) { - return url.replace(/\.[^.]+$/, `.${newExt}`) + return key.replace(/\.[^.]+$/, `.${newExt}`) } - return `${url}.${ext || ''}` + return `${key}${ext ? '.' + ext : ''}` } export const toSizedImageURL = ({ url, size, ext }: ToSizedImageURLProps) => { @@ -53,8 +53,9 @@ export const toSizedImageURL = ({ url, size, ext }: ToSizedImageURLProps) => { return url } - const extedUrl = changeExt({ url, ext }) + const key = url.replace(assetDomain, ``) + const extedUrl = changeExt({ key, ext }) const prefix = size ? '/' + PROCESSED_PREFIX + '/' + size : '' - return extedUrl.replace(assetDomain, `${assetDomain}${prefix}`) + return assetDomain + prefix + extedUrl } diff --git a/src/components/GQL/queries/clientPreference.ts b/src/components/GQL/queries/clientPreference.ts index 96ba7dc929..ba501b3c91 100644 --- a/src/components/GQL/queries/clientPreference.ts +++ b/src/components/GQL/queries/clientPreference.ts @@ -13,6 +13,7 @@ export default gql` supported } routeHistory + followFeedType } } ` diff --git a/src/components/Img/index.tsx b/src/components/Img/index.tsx index 56e6a01330..b404a19918 100644 --- a/src/components/Img/index.tsx +++ b/src/components/Img/index.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import React, { useState } from 'react' import { toSizedImageURL, ToSizedImageURLSize } from '~/common/utils' @@ -13,7 +13,7 @@ interface ImgProps { disabled?: boolean } -export const Img = ({ url, size, smUpSize, disabled }: ImgProps) => { +const BaseImg = ({ url, size, smUpSize, disabled }: ImgProps) => { const [error, setError] = useState(false) // Fallback to the raw `url` if manually disable or responsive image is failed to load @@ -54,3 +54,17 @@ export const Img = ({ url, size, smUpSize, disabled }: ImgProps) => { ) } + +/** + * Memoizing + */ +type MemoizedImg = React.MemoExoticComponent> + +export const Img = React.memo(BaseImg, (prevProps, props) => { + return ( + prevProps.url === props.url && + prevProps.size === props.size && + prevProps.smUpSize === props.smUpSize && + prevProps.disabled === props.disabled + ) +}) as MemoizedImg diff --git a/src/components/UserProfile/Cover/index.tsx b/src/components/UserProfile/Cover/index.tsx index 4990913743..d8cffaf0dd 100644 --- a/src/components/UserProfile/Cover/index.tsx +++ b/src/components/UserProfile/Cover/index.tsx @@ -11,7 +11,7 @@ interface ProfileCoverProps { export const ProfileCover = ({ cover, inEditor }: ProfileCoverProps) => { const url = cover || IMAGE_COVER - const isFallback = !!cover + const isFallback = !cover return (
diff --git a/src/views/Follow/FollowFeed/FeedType/index.tsx b/src/views/Follow/FollowFeed/FeedType/index.tsx new file mode 100644 index 0000000000..cdefea6164 --- /dev/null +++ b/src/views/Follow/FollowFeed/FeedType/index.tsx @@ -0,0 +1,27 @@ +import { Tabs, Translate } from '~/components' + +export type FollowFeedType = 'article' | 'comment' + +interface FeedTypeProps { + type: FollowFeedType + setFeedType: (type: FollowFeedType) => void +} + +const FeedType: React.FC = ({ type, setFeedType }) => { + const isArticle = type === 'article' + const isComment = type === 'comment' + + return ( + + setFeedType('article')} selected={isArticle}> + + + + setFeedType('comment')} selected={isComment}> + + + + ) +} + +export default FeedType diff --git a/src/views/Follow/FollowFeed/index.tsx b/src/views/Follow/FollowFeed/index.tsx index 5c95e210ce..7de40bc854 100644 --- a/src/views/Follow/FollowFeed/index.tsx +++ b/src/views/Follow/FollowFeed/index.tsx @@ -10,48 +10,143 @@ import { Spinner, } from '~/components' import { QueryError } from '~/components/GQL' +import CLIENT_PREFERENCE from '~/components/GQL/queries/clientPreference' import { analytics, mergeConnections } from '~/common/utils' +import FeedType, { FollowFeedType } from './FeedType' import FollowComment from './FollowComment' -import { FollowFeed as FollowFeedType } from './__generated__/FollowFeed' - -const FOLLOW_FEED = gql` - query FollowFeed($after: String) { - viewer { - id - recommendation { - followeeWorks(input: { first: 10, after: $after }) { - pageInfo { - startCursor - endCursor - hasNextPage - } - edges { - cursor - node { - __typename - ... on Article { - ...ArticleDigestFeedArticle +import { ClientPreference } from '~/components/GQL/queries/__generated__/ClientPreference' +import { FollowArticleFeed } from './__generated__/FollowArticleFeed' +import { FollowCommentFeed } from './__generated__/FollowCommentFeed' + +const queries = { + article: gql` + query FollowArticleFeed($after: String) { + viewer { + id + recommendation { + followeeArticles(input: { first: 10, after: $after }) { + pageInfo { + startCursor + endCursor + hasNextPage + } + edges { + cursor + node { + __typename + ... on Article { + ...ArticleDigestFeedArticle + } } - ... on Comment { - ...FollowComment + } + } + } + } + } + ${ArticleDigestFeed.fragments.article} + `, + comment: gql` + query FollowCommentFeed($after: String) { + viewer { + id + recommendation { + followeeComments(input: { first: 10, after: $after }) { + pageInfo { + startCursor + endCursor + hasNextPage + } + edges { + cursor + node { + __typename + ... on Comment { + ...FollowComment + } } } } } } } + ${FollowComment.fragments.comment} + `, +} + +const ArticlesFeed = () => { + const { data, loading, error, fetchMore, refetch } = useQuery< + FollowArticleFeed + >(queries.article) + + if (loading) { + return } - ${ArticleDigestFeed.fragments.article} - ${FollowComment.fragments.comment} -` -const FollowFeed = () => { - const { data, loading, error, fetchMore, refetch } = useQuery( - FOLLOW_FEED + if (error) { + return + } + + const connectionPath = 'viewer.recommendation.followeeArticles' + const { edges, pageInfo } = + data?.viewer?.recommendation.followeeArticles || {} + + if (!edges || edges.length <= 0 || !pageInfo) { + return + } + + const loadMore = () => { + analytics.trackEvent('load_more', { + type: 'follow', + location: edges.length, + }) + return fetchMore({ + variables: { + after: pageInfo.endCursor, + }, + updateQuery: (previousResult, { fetchMoreResult }) => + mergeConnections({ + oldData: previousResult, + newData: fetchMoreResult, + path: connectionPath, + }), + }) + } + + return ( + + + {edges.map(({ node, cursor }, i) => ( + + + analytics.trackEvent('click_feed', { + type: 'follow-article', + contentType: 'article', + styleType: 'no_cover', + location: i, + }) + } + inFollowFeed + /> + + ))} + + ) +} + +const CommentsFeed = () => { + const { data, loading, error, fetchMore, refetch } = useQuery< + FollowCommentFeed + >(queries.comment) if (loading) { return @@ -61,8 +156,9 @@ const FollowFeed = () => { return } - const connectionPath = 'viewer.recommendation.followeeWorks' - const { edges, pageInfo } = data?.viewer?.recommendation.followeeWorks || {} + const connectionPath = 'viewer.recommendation.followeeComments' + const { edges, pageInfo } = + data?.viewer?.recommendation.followeeComments || {} if (!edges || edges.length <= 0 || !pageInfo) { return @@ -95,33 +191,17 @@ const FollowFeed = () => { {edges.map(({ node, cursor }, i) => ( - {node.__typename === 'Article' && ( - - analytics.trackEvent('click_feed', { - type: 'follow', - contentType: 'article', - styleType: 'no_cover', - location: i, - }) - } - inFollowFeed - /> - )} - {node.__typename === 'Comment' && ( - - analytics.trackEvent('click_feed', { - type: 'follow', - contentType: 'article', - styleType: 'comment', - location: i, - }) - } - /> - )} + + analytics.trackEvent('click_feed', { + type: 'follow-comment', + contentType: 'comment', + styleType: 'card', + location: i, + }) + } + /> ))} @@ -129,9 +209,35 @@ const FollowFeed = () => { ) } -export default () => ( - <> - - - -) +const FollowFeed = () => { + const { data, client } = useQuery(CLIENT_PREFERENCE, { + variables: { id: 'local' }, + }) + const { followFeedType } = data?.clientPreference || { + followFeedType: 'article', + } + const setFeedType = (type: FollowFeedType) => { + if (client) { + client.writeData({ + id: 'ClientPreference:local', + data: { followFeedType: type }, + }) + } + } + + return ( + <> + +
+ +
+ {followFeedType === 'article' && } + {followFeedType === 'comment' && } + + ) +} + +export default FollowFeed diff --git a/src/views/Me/Transactions/index.tsx b/src/views/Me/Transactions/index.tsx index 9bef1ab6e9..d00ac6000f 100644 --- a/src/views/Me/Transactions/index.tsx +++ b/src/views/Me/Transactions/index.tsx @@ -20,13 +20,7 @@ const ME_TRANSACTIONS = gql` viewer { id wallet { - transactions( - input: { - first: 20 - after: $after - states: [canceled, failed, succeeded] - } - ) { + transactions(input: { first: 20, after: $after, states: [succeeded] }) { pageInfo { startCursor endCursor