From 99b5d63eadfad578f697909a904383c25c8a9191 Mon Sep 17 00:00:00 2001 From: lauti7 Date: Thu, 30 Nov 2023 13:52:46 -0300 Subject: [PATCH 01/14] feat: notifications toggle --- package-lock.json | 95 ++++--- package.json | 1 + src/assets/bell-onboarding.png | Bin 0 -> 17732 bytes .../Icons/Notifications/BidAccepted.tsx | 28 +++ .../Icons/Notifications/BidReceived.tsx | 37 +++ .../Icons/Notifications/EmptyInbox.tsx | 27 ++ .../Icons/Notifications/ItemAirdropped.tsx | 28 +++ .../Icons/Notifications/ItemSold.tsx | 28 +++ .../Icons/Notifications/LandRented.tsx | 43 ++++ .../Icons/Notifications/ManaMainnet.tsx | 30 +++ .../Icons/Notifications/ManaPoly.tsx | 38 +++ .../Icons/Notifications/NewNotification.tsx | 11 + .../Icons/Notifications/NotificationBell.tsx | 12 + .../Notifications/NotificationBellActive.tsx | 13 + .../Icons/Notifications/RentPeriodEnding.tsx | 32 +++ .../Notifications/NotificationItem.css | 34 +++ .../Notifications/NotificationItem.tsx | 41 +++ .../Notifications/NotificationItemImage.css | 24 ++ .../Notifications/NotificationItemImage.tsx | 39 +++ .../ItemSoldNotification.tsx | 54 ++++ .../RoyaltiesEarnedNotification.tsx | 93 +++++++ .../Notifications/Notifications.css | 31 +++ .../Notifications/Notifications.stories.css | 0 .../Notifications/Notifications.stories.tsx | 232 +++++++++++++++++ .../Notifications/Notifications.tsx | 66 +++++ .../Notifications/NotificationsFeed.css | 125 +++++++++ .../Notifications/NotificationsFeed.tsx | 237 ++++++++++++++++++ src/components/Notifications/types.ts | 49 ++++ src/lib/time.ts | 6 + 29 files changed, 1426 insertions(+), 28 deletions(-) create mode 100644 src/assets/bell-onboarding.png create mode 100644 src/components/Icons/Notifications/BidAccepted.tsx create mode 100644 src/components/Icons/Notifications/BidReceived.tsx create mode 100644 src/components/Icons/Notifications/EmptyInbox.tsx create mode 100644 src/components/Icons/Notifications/ItemAirdropped.tsx create mode 100644 src/components/Icons/Notifications/ItemSold.tsx create mode 100644 src/components/Icons/Notifications/LandRented.tsx create mode 100644 src/components/Icons/Notifications/ManaMainnet.tsx create mode 100644 src/components/Icons/Notifications/ManaPoly.tsx create mode 100644 src/components/Icons/Notifications/NewNotification.tsx create mode 100644 src/components/Icons/Notifications/NotificationBell.tsx create mode 100644 src/components/Icons/Notifications/NotificationBellActive.tsx create mode 100644 src/components/Icons/Notifications/RentPeriodEnding.tsx create mode 100644 src/components/Notifications/NotificationItem.css create mode 100644 src/components/Notifications/NotificationItem.tsx create mode 100644 src/components/Notifications/NotificationItemImage.css create mode 100644 src/components/Notifications/NotificationItemImage.tsx create mode 100644 src/components/Notifications/NotificationTypes/ItemSoldNotification.tsx create mode 100644 src/components/Notifications/NotificationTypes/RoyaltiesEarnedNotification.tsx create mode 100644 src/components/Notifications/Notifications.css create mode 100644 src/components/Notifications/Notifications.stories.css create mode 100644 src/components/Notifications/Notifications.stories.tsx create mode 100644 src/components/Notifications/Notifications.tsx create mode 100644 src/components/Notifications/NotificationsFeed.css create mode 100644 src/components/Notifications/NotificationsFeed.tsx create mode 100644 src/components/Notifications/types.ts create mode 100644 src/lib/time.ts diff --git a/package-lock.json b/package-lock.json index ccefe979..036cab87 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@dcl/ui-env": "^1.4.0", "balloon-css": "^0.5.0", "classnames": "^2.3.2", + "dayjs": "^1.11.10", "deep-equal": "^2.0.5", "ethereum-blockies": "^0.1.1", "events": "^3.3.0", @@ -19331,6 +19332,11 @@ "node": "*" } }, + "node_modules/dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, "node_modules/dayzed": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/dayzed/-/dayzed-3.2.3.tgz", @@ -46641,7 +46647,8 @@ "ajv-errors": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-3.0.0.tgz", - "integrity": "sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ==" + "integrity": "sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ==", + "requires": {} }, "ajv-keywords": { "version": "5.1.0", @@ -48226,7 +48233,8 @@ "version": "1.6.22", "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-1.6.22.tgz", "integrity": "sha512-TDoPum4SHdfPiGSAaRBw7ECyI8VaHpK8GJugbJIJuqyh6kzw9ZLJZW3HGL3NNrJGxcAixUvqROm+YuQOo5eXtg==", - "dev": true + "dev": true, + "requires": {} }, "@mdx-js/util": { "version": "1.6.22", @@ -49257,7 +49265,8 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "dev": true, + "requires": {} }, "ansi-styles": { "version": "4.3.0", @@ -50135,7 +50144,8 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "dev": true, + "requires": {} }, "ansi-styles": { "version": "4.3.0", @@ -50897,7 +50907,8 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "dev": true, + "requires": {} }, "ansi-styles": { "version": "4.3.0", @@ -52248,7 +52259,8 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "dev": true, + "requires": {} }, "schema-utils": { "version": "1.0.0", @@ -52816,7 +52828,8 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "dev": true, + "requires": {} } } }, @@ -53251,7 +53264,8 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "dev": true, + "requires": {} }, "json5": { "version": "1.0.2", @@ -53413,7 +53427,8 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "dev": true, + "requires": {} }, "cosmiconfig": { "version": "6.0.0", @@ -53746,7 +53761,8 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "dev": true, + "requires": {} } } }, @@ -53776,7 +53792,8 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "dev": true, + "requires": {} }, "schema-utils": { "version": "2.7.1", @@ -53884,7 +53901,8 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true + "dev": true, + "requires": {} }, "eslint-scope": { "version": "5.1.1", @@ -53933,7 +53951,8 @@ "version": "8.12.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.1.tgz", "integrity": "sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew==", - "dev": true + "dev": true, + "requires": {} }, "yallist": { "version": "4.0.0", @@ -54213,7 +54232,8 @@ "version": "3.4.1", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==", - "dev": true + "dev": true, + "requires": {} }, "ansi-styles": { "version": "4.3.0", @@ -56010,7 +56030,8 @@ "version": "5.3.1", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", - "dev": true + "dev": true, + "requires": {} }, "acorn-walk": { "version": "7.2.0", @@ -56084,13 +56105,15 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", - "dev": true + "dev": true, + "requires": {} }, "ajv-keywords": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.0.tgz", "integrity": "sha512-aUjdRFISbuFOl0EIZc+9e4FfZp0bDZgAdOOf30bJmw8VM9v84SHyVyxDfbWxpGYbdZD/9XoKxfHVNmxPkhwyGw==", - "dev": true + "dev": true, + "requires": {} }, "alphanum-sort": { "version": "1.0.2", @@ -56576,7 +56599,8 @@ "version": "7.0.0-bridge.0", "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", - "dev": true + "dev": true, + "requires": {} }, "babel-jest": { "version": "28.1.3", @@ -56827,7 +56851,8 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "dev": true, + "requires": {} }, "emojis-list": { "version": "3.0.0", @@ -59559,6 +59584,11 @@ "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", "dev": true }, + "dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, "dayzed": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/dayzed/-/dayzed-3.2.3.tgz", @@ -60921,7 +60951,8 @@ "version": "8.3.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", - "dev": true + "dev": true, + "requires": {} }, "eslint-scope": { "version": "4.0.3", @@ -61663,7 +61694,8 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "dev": true, + "requires": {} } } } @@ -65933,7 +65965,8 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true + "dev": true, + "requires": {} }, "jest-regex-util": { "version": "26.0.0", @@ -67634,7 +67667,8 @@ "version": "8.13.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", - "dev": true + "dev": true, + "requires": {} } } }, @@ -74939,7 +74973,8 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "dev": true, + "requires": {} } } } @@ -74999,7 +75034,8 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/react-docgen-typescript/-/react-docgen-typescript-2.2.2.tgz", "integrity": "sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==", - "dev": true + "dev": true, + "requires": {} }, "react-dom": { "version": "17.0.2", @@ -75175,7 +75211,8 @@ "react-virtualized-auto-sizer": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.11.tgz", - "integrity": "sha512-v2YlC4KCnjEF7V5KtxrHCy50vPhbL73BS1qNOXFYaaE1XGeRqZHrGP+F4BE0QDv2pP+f1t9twL1n5pRp5GPMkQ==" + "integrity": "sha512-v2YlC4KCnjEF7V5KtxrHCy50vPhbL73BS1qNOXFYaaE1XGeRqZHrGP+F4BE0QDv2pP+f1t9twL1n5pRp5GPMkQ==", + "requires": {} }, "read-cache": { "version": "1.0.0", @@ -78915,7 +78952,8 @@ "version": "3.4.1", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==", - "dev": true + "dev": true, + "requires": {} }, "fast-deep-equal": { "version": "3.1.1", @@ -79141,7 +79179,8 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/webpack-filter-warnings-plugin/-/webpack-filter-warnings-plugin-1.2.1.tgz", "integrity": "sha512-Ez6ytc9IseDMLPo0qCuNNYzgtUl8NovOqjIq4uAU8LTD4uoa1w1KpZyyzFtLTEMZpkkOkLfL9eN+KGYdk1Qtwg==", - "dev": true + "dev": true, + "requires": {} }, "webpack-hot-middleware": { "version": "2.25.3", diff --git a/package.json b/package.json index 504f71dd..16227948 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,7 @@ "@dcl/ui-env": "^1.4.0", "balloon-css": "^0.5.0", "classnames": "^2.3.2", + "dayjs": "^1.11.10", "deep-equal": "^2.0.5", "ethereum-blockies": "^0.1.1", "events": "^3.3.0", diff --git a/src/assets/bell-onboarding.png b/src/assets/bell-onboarding.png new file mode 100644 index 0000000000000000000000000000000000000000..3188250c61bc404f7cb59c1a798c1fc6fe8c25c5 GIT binary patch literal 17732 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 => { + return ( + + + + + + + + + + + ) +} + +export default BidAccepted diff --git a/src/components/Icons/Notifications/BidReceived.tsx b/src/components/Icons/Notifications/BidReceived.tsx new file mode 100644 index 00000000..076420bc --- /dev/null +++ b/src/components/Icons/Notifications/BidReceived.tsx @@ -0,0 +1,37 @@ +import React from 'react' + +const BidReceived = (props: React.SVGAttributes) => { + return ( + + + + + + + + + + + + + + ) +} + +export default BidReceived diff --git a/src/components/Icons/Notifications/EmptyInbox.tsx b/src/components/Icons/Notifications/EmptyInbox.tsx new file mode 100644 index 00000000..9fec6b05 --- /dev/null +++ b/src/components/Icons/Notifications/EmptyInbox.tsx @@ -0,0 +1,27 @@ +import React from 'react' + +const EmptyInbox = (props: React.SVGAttributes) => { + return ( + + + + + + + + ) +} + +export default EmptyInbox diff --git a/src/components/Icons/Notifications/ItemAirdropped.tsx b/src/components/Icons/Notifications/ItemAirdropped.tsx new file mode 100644 index 00000000..e2d60048 --- /dev/null +++ b/src/components/Icons/Notifications/ItemAirdropped.tsx @@ -0,0 +1,28 @@ +import React from 'react' + +const ItemAirdropped = (props: React.SVGAttributes) => { + return ( + + + + + + + + + + + ) +} + +export default ItemAirdropped diff --git a/src/components/Icons/Notifications/ItemSold.tsx b/src/components/Icons/Notifications/ItemSold.tsx new file mode 100644 index 00000000..d25e13d0 --- /dev/null +++ b/src/components/Icons/Notifications/ItemSold.tsx @@ -0,0 +1,28 @@ +import React from 'react' + +const ItemSold = (props: React.SVGAttributes) => { + return ( + + + + + + + + + + + ) +} + +export default ItemSold diff --git a/src/components/Icons/Notifications/LandRented.tsx b/src/components/Icons/Notifications/LandRented.tsx new file mode 100644 index 00000000..bffc0a66 --- /dev/null +++ b/src/components/Icons/Notifications/LandRented.tsx @@ -0,0 +1,43 @@ +import React from 'react' +const LandRented = (props: React.SVGAttributes) => { + return ( + + + + + + + + + + + + + + + ) +} + +export default LandRented diff --git a/src/components/Icons/Notifications/ManaMainnet.tsx b/src/components/Icons/Notifications/ManaMainnet.tsx new file mode 100644 index 00000000..83670543 --- /dev/null +++ b/src/components/Icons/Notifications/ManaMainnet.tsx @@ -0,0 +1,30 @@ +import React from 'react' + +const ManaMainnet = (props: React.SVGAttributes) => { + return ( + + + + + + + + + + + ) +} + +export default ManaMainnet diff --git a/src/components/Icons/Notifications/ManaPoly.tsx b/src/components/Icons/Notifications/ManaPoly.tsx new file mode 100644 index 00000000..c8a002a1 --- /dev/null +++ b/src/components/Icons/Notifications/ManaPoly.tsx @@ -0,0 +1,38 @@ +import React from 'react' + +const ManaPoly = (props: React.SVGAttributes) => { + return ( + + + + + + + + + + + + + ) +} + +export default ManaPoly diff --git a/src/components/Icons/Notifications/NewNotification.tsx b/src/components/Icons/Notifications/NewNotification.tsx new file mode 100644 index 00000000..090b6525 --- /dev/null +++ b/src/components/Icons/Notifications/NewNotification.tsx @@ -0,0 +1,11 @@ +import React from 'react' + +const NewNotification = (props: React.SVGAttributes) => { + return ( + + + + ) +} + +export default NewNotification diff --git a/src/components/Icons/Notifications/NotificationBell.tsx b/src/components/Icons/Notifications/NotificationBell.tsx new file mode 100644 index 00000000..d54aa722 --- /dev/null +++ b/src/components/Icons/Notifications/NotificationBell.tsx @@ -0,0 +1,12 @@ +import React from 'react' + +const NotificationBell = () => ( + + + +) + +export default NotificationBell diff --git a/src/components/Icons/Notifications/NotificationBellActive.tsx b/src/components/Icons/Notifications/NotificationBellActive.tsx new file mode 100644 index 00000000..e90afd0a --- /dev/null +++ b/src/components/Icons/Notifications/NotificationBellActive.tsx @@ -0,0 +1,13 @@ +import React from 'react' + +const NotificationBellActive = () => ( + + + + +) + +export default NotificationBellActive diff --git a/src/components/Icons/Notifications/RentPeriodEnding.tsx b/src/components/Icons/Notifications/RentPeriodEnding.tsx new file mode 100644 index 00000000..718ab029 --- /dev/null +++ b/src/components/Icons/Notifications/RentPeriodEnding.tsx @@ -0,0 +1,32 @@ +import React from 'react' + +const RentPeriodEnding = (props: React.SVGAttributes) => { + return ( + + + + + + + + + + + + ) +} + +export default RentPeriodEnding diff --git a/src/components/Notifications/NotificationItem.css b/src/components/Notifications/NotificationItem.css new file mode 100644 index 00000000..f3428319 --- /dev/null +++ b/src/components/Notifications/NotificationItem.css @@ -0,0 +1,34 @@ +.dcl.notification-item { + display: flex; + flex-direction: row; + align-items: center; + padding: 16px; + justify-content: space-between; +} + +.dcl.notification-item__image { + margin-left: 16px; +} + +.dcl.notification-item__content { + display: flex; + flex-direction: column; + flex-grow: 1; + margin-left: 16px; +} + +.dcl.notification-item__content__title { + font-weight: 600; + font-size: 14px; + margin-bottom: 0; +} + +.dcl.notification-item__content__description { + color: #a09ba8; + margin: 4px 0; +} + +.dcl.notification-item__content__timestamp { + font-size: 12px; + font-weight: 600; +} diff --git a/src/components/Notifications/NotificationItem.tsx b/src/components/Notifications/NotificationItem.tsx new file mode 100644 index 00000000..869690c3 --- /dev/null +++ b/src/components/Notifications/NotificationItem.tsx @@ -0,0 +1,41 @@ +import React from 'react' + +import NotificationItemImage, { + NotificationItemImageProps +} from './NotificationItemImage' +import Time from '../../lib/time' + +import './NotificationItem.css' +import NewNotification from '../Icons/Notifications/NewNotification' + +interface NotificationItemProps { + image: NotificationItemImageProps + timestamp: number + isNew: boolean +} + +export default function NotificationItem({ + image, + timestamp, + isNew, + children +}: React.PropsWithChildren) { + return ( +
+
+ +
+
+ {children} +

+ {Time(timestamp).fromNow()} +

+
+ {isNew && ( + + + + )} +
+ ) +} diff --git a/src/components/Notifications/NotificationItemImage.css b/src/components/Notifications/NotificationItemImage.css new file mode 100644 index 00000000..7a57cfd6 --- /dev/null +++ b/src/components/Notifications/NotificationItemImage.css @@ -0,0 +1,24 @@ +.dcl.notification-image-container { + height: 48px; + width: 48px; +} + +.dcl.notification-image { + border-radius: 100%; + background-color: rgba(var(--bluish-steel-raw), 0.4); + height: 48px; + width: 48px; + display: flex; + align-items: center; + justify-content: center; +} + +.dcl.notification-image img { + width: 80%; +} + +.dcl.notification-icon { + position: relative; + top: -20px; + left: -8px; +} diff --git a/src/components/Notifications/NotificationItemImage.tsx b/src/components/Notifications/NotificationItemImage.tsx new file mode 100644 index 00000000..9f42b14a --- /dev/null +++ b/src/components/Notifications/NotificationItemImage.tsx @@ -0,0 +1,39 @@ +import React, { ReactNode } from 'react' + +import { Rarity } from '@dcl/schemas' + +import './NotificationItemImage.css' + +export interface NotificationItemImageProps { + rarity?: Rarity + backgroundColor?: string + imageLink: string + icon?: ReactNode +} + +export default function NotificationItemImage({ + rarity, + imageLink, + backgroundColor, + icon +}: NotificationItemImageProps) { + const bgColor = rarity + ? Rarity.getGradient(rarity).join() + : backgroundColor + ? backgroundColor + : null + + return ( +
+
+ Notification Image +
+ {icon && {icon}} +
+ ) +} diff --git a/src/components/Notifications/NotificationTypes/ItemSoldNotification.tsx b/src/components/Notifications/NotificationTypes/ItemSoldNotification.tsx new file mode 100644 index 00000000..1e94f067 --- /dev/null +++ b/src/components/Notifications/NotificationTypes/ItemSoldNotification.tsx @@ -0,0 +1,54 @@ +import React from 'react' + +import { ItemSoldNotification, NotificationLocale } from '../types' +import NotificationItem from '../NotificationItem' +import ItemSold from '../../Icons/Notifications/ItemSold' +import { Rarity } from '@dcl/schemas' + +interface ItemSoldNotificationProps { + notification: ItemSoldNotification + locale: NotificationLocale +} + +const i18N = { + en: { description: 'You just sold ', title: 'Item Sold' }, + es: { description: 'Vendiste ', title: 'Item vendido' }, + zh: { description: '你剛剛賣了 ', title: '已售商品' } +} + +const ItemSoldNotification = ({ + notification, + locale +}: ItemSoldNotificationProps) => { + return ( + + }} + timestamp={notification.timestamp} + isNew={!notification.read} + > +

+ {i18N[locale].title} +

+

+ {i18N[locale].description}{' '} + + + {notification.metadata.nftName} + + +

+
+ ) +} + +export default ItemSoldNotification diff --git a/src/components/Notifications/NotificationTypes/RoyaltiesEarnedNotification.tsx b/src/components/Notifications/NotificationTypes/RoyaltiesEarnedNotification.tsx new file mode 100644 index 00000000..ce7369db --- /dev/null +++ b/src/components/Notifications/NotificationTypes/RoyaltiesEarnedNotification.tsx @@ -0,0 +1,93 @@ +import React from 'react' + +import { NotificationLocale, RoyalitesEarnedNotification } from '../types' +import NotificationItem from '../NotificationItem' +import ManaMainnet from '../../Icons/Notifications/ManaMainnet' +import ManaPolygon from '../../Icons/Notifications/ManaPoly' +import { Rarity } from '@dcl/schemas' + +interface RoyaltiesEarnedNotificationProps { + notification: RoyalitesEarnedNotification + locale: NotificationLocale +} + +const i18N = { + en: { + description_1: `You earned `, + description_2: `for selling `, + title: 'Royalties Earned' + }, + es: { + description_1: `Ganaste `, + description_2: `por vender `, + title: 'Regalias ganadas' + }, + zh: { + description_1: `您通过出售 `, + description_2: `赚取了 `, + title: '所得版税' + } +} + +const RoyaltiesEarnedNotification = ({ + notification, + locale +}: RoyaltiesEarnedNotificationProps) => { + return ( + + ) : ( + + ) + }} + timestamp={notification.timestamp} + isNew={!notification.read} + > +

+ {i18N[locale].title} +

+ { + locale == "zh" ? + ( +

+ {i18N[locale].description_1} + + + {notification.metadata.nftName} + + + {' '}{i18N[locale].description_2}{Number(notification.metadata.royaltiesCut)} +

+ ) : +

+ {i18N[locale].description_1}{Number(notification.metadata.royaltiesCut)} + {' '}{i18N[locale].description_2} + + + {notification.metadata.nftName} + + +

+ } +
+ ) +} + +export default RoyaltiesEarnedNotification diff --git a/src/components/Notifications/Notifications.css b/src/components/Notifications/Notifications.css new file mode 100644 index 00000000..9e1d4006 --- /dev/null +++ b/src/components/Notifications/Notifications.css @@ -0,0 +1,31 @@ +.dcl.notifications { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; +} + +.dcl.notifications-bell { + display: flex; + background: none; + border: none; + padding: 0; + margin: 0; + transition: filter 0.2s ease; + cursor: pointer; + position: relative; +} + +.dcl.notifications-counter { + width: 15px; + height: 15px; + border-radius: 50%; + font-size: 11px; + background-color: var(--primary); + position: relative; + top: -21px; + left: 19px; + display: flex; + justify-content: center; + align-items: center; +} diff --git a/src/components/Notifications/Notifications.stories.css b/src/components/Notifications/Notifications.stories.css new file mode 100644 index 00000000..e69de29b diff --git a/src/components/Notifications/Notifications.stories.tsx b/src/components/Notifications/Notifications.stories.tsx new file mode 100644 index 00000000..2600cac9 --- /dev/null +++ b/src/components/Notifications/Notifications.stories.tsx @@ -0,0 +1,232 @@ +import * as React from 'react' +import { storiesOf } from '@storybook/react' +import Notifications from './Notifications' +import NotificationItemImage from './NotificationItemImage' +import { NFTCategory, Rarity } from '@dcl/schemas' +import BidReceived from '../Icons/Notifications/BidReceived' + +storiesOf('Notifications Toggle', module) + .add('Without new notifications', () => { + return ( +
+ console.log(newTab)} + onClickToggle={() => console.log('Toggle button')} + onBegin={() => console.log('Begin')} + /> +
+ ) + }) + .add('With new notificatitons', () => { + return ( +
+ console.log(newTab)} + onClickToggle={() => console.log('Toggle button')} + onBegin={() => console.log('Begin')} + /> +
+ ) + }) + .add('Onboarding', () => { + return ( +
+ console.log(newTab)} + locale="en" + onClickToggle={() => console.log('Toggle button')} + onBegin={() => console.log('Begin')} + /> +
+ ) + }) + .add('Open not loading', () => { + return ( +
+ console.log(newTab)} + onClickToggle={() => console.log('Toggle button')} + onBegin={() => console.log('Begin')} + /> +
+ ) + }) + .add('Open not loading but empty', () => { + return ( +
+ console.log(newTab)} + onClickToggle={() => console.log('Toggle button')} + onBegin={() => console.log('Begin')} + /> +
+ ) + }) + .add('Open loading', () => { + return ( +
+ console.log(newTab)} + onClickToggle={() => console.log('Toggle button')} + onBegin={() => console.log('Begin')} + /> +
+ ) + }) + .add('NotificationItemImage', () => { + return ( +
+ } + /> +
+ ) + }) diff --git a/src/components/Notifications/Notifications.tsx b/src/components/Notifications/Notifications.tsx new file mode 100644 index 00000000..94d92c52 --- /dev/null +++ b/src/components/Notifications/Notifications.tsx @@ -0,0 +1,66 @@ +import React from 'react' + +import NotificationsFeed from './NotificationsFeed' +import { ActiveTab, DCLNotification, NotificationLocale } from './types' + +import NotificationBell from '../Icons/Notifications/NotificationBell' +import NotificationBellActive from '../Icons/Notifications/NotificationBellActive' + +import './Notifications.css' + +export interface NotificationsProps { + isOpen: boolean + userNotifications: DCLNotification[] + isLoading: boolean + locale: NotificationLocale + isOnboarding: boolean + activeTab: ActiveTab + onClickToggle: (e: React.MouseEvent) => void + onChangeTab: ( + e: React.MouseEvent, + newActiveTab: ActiveTab + ) => void + onBegin: (e: React.MouseEvent) => void +} + +export default function Notifications({ + isOpen, + userNotifications, + isLoading, + locale, + isOnboarding, + activeTab, + onClickToggle, + onChangeTab, + onBegin +}: NotificationsProps) { + const unseenNotifications = userNotifications.filter( + (notification) => !notification.read + ).length + + return ( +
+
+ + {!isOpen && unseenNotifications > 0 && ( +
+ {unseenNotifications > 9 ? '9+' : unseenNotifications} +
+ )} +
+ {isOpen && ( + + )} +
+ ) +} diff --git a/src/components/Notifications/NotificationsFeed.css b/src/components/Notifications/NotificationsFeed.css new file mode 100644 index 00000000..9d3edd37 --- /dev/null +++ b/src/components/Notifications/NotificationsFeed.css @@ -0,0 +1,125 @@ +.dcl.notifications-feed { + width: 384px; + min-height: 200px; + max-height: 385px; + height: 100%; + overflow-y: scroll; + pointer-events: visible; + opacity: 1; + display: flex; + flex-direction: column; + position: relative; + top: 5px; + z-index: 100; + border-radius: 8px; + border: 1px solid hsla(260, 6%, 46%, 0.24); + background: var(--background); + box-shadow: 0px 1px 24px 0px hsla(260, 6%, 46%, 0.24); + transition: opacity 0.3s ease; + overscroll-behavior-y: contain; +} + +.dcl.notifications-feed::before { + position: absolute; + content: ''; + width: 28px; + height: 28px; + background: var(--background); + transform: rotate(-45deg); + top: -0.30714286em; + left: 46.5%; + box-shadow: -1px -1px 0 0 var(--background); +} + +.dcl.notifications-feed__content > .dcl.tabs { + margin-bottom: 0; + height: auto; +} + +.dcl.notifications-feed__content > .dcl.tabs > .ui.container > .dcl.tab { + margin-right: 0; + letter-spacing: 0; + font-size: 14px; + padding: 8px 16px; +} + +@media (max-width: 768px) { + .dcl.notifications-feed__content > .dcl.tabs, + .dcl.tabs.fullscreen { + margin-top: 0; + } +} + +.dcl.notifications-feed__header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 8px 16px; +} + +.dcl.notifications-feed__title { + margin: 0; + font-size: 18px; + font-weight: 600; +} + +.dcl.notifications-feed__header > .ui.basic.button.notifications-feed__markit { + color: #a09ba8 !important; + font-size: 12px; + padding: 8px 12px; +} + +.dcl.notifications-feed__list-container { + display: flex; + flex-direction: column; + height: 100%; + justify-content: space-between; +} + +.dcl.notifications-feed__list { + display: flex; + flex-direction: column; +} + +.dcl.notifications-feed__content { + height: 100%; +} + +.dcl.notifications-feed__emptyview { + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + gap: 16px; + padding: 40px 80px; + align-items: center; + text-align: center; +} + +.dcl.notifications-feed__emptyview__title { + font-size: 18px; + font-weight: 600; + margin-bottom: 0; + width: 345px; +} + +.dcl.notifications-feed__emptyview__description { + font-size: 16px; + color: #a09ba8; +} + +.dcl.notifications-feed__onboarding { + display: flex; + flex-direction: column; + gap: 16px; + align-items: center; + text-align: center; + padding: 30px; +} + +.dcl.notifications-feed__onboarding-bell { + background-image: url(../../assets/bell-onboarding.png); + border-radius: 50%; + width: 120px; + height: 120px; +} diff --git a/src/components/Notifications/NotificationsFeed.tsx b/src/components/Notifications/NotificationsFeed.tsx new file mode 100644 index 00000000..680d3d0d --- /dev/null +++ b/src/components/Notifications/NotificationsFeed.tsx @@ -0,0 +1,237 @@ +import React, { useMemo } from 'react' + +import { Loader } from '../Loader/Loader' +import { ActiveTab, DCLNotification, NotificationLocale } from './types' + +import './NotificationsFeed.css' +import ItemSoldNotification from './NotificationTypes/ItemSoldNotification' +import EmptyInbox from '../Icons/Notifications/EmptyInbox' +import { Tabs } from '../Tabs/Tabs' +import { Button } from '../Button/Button' +import Time from '../../lib/time' +import RoyaltiesEarnedNotification from './NotificationTypes/RoyaltiesEarnedNotification' + +interface NotificationsFeedProps { + userNotifications: DCLNotification[] + isLoading: boolean + locale: NotificationLocale + isOnboarding: boolean + activeTab: ActiveTab + onChangeTab: ( + e: React.MouseEvent, + newActiveTab: ActiveTab + ) => void + onBegin: (e: React.MouseEvent) => void +} + +const i18N = { + en: { + onboarding: { + title: 'Introducing Decentraland Inbox System', + description: + 'Never miss anything anymore! Now you will get notified every time something relevant happens to your account.', + button: "Let's begin" + }, + feed: { + title: 'Notifications', + tabs: { + newest: 'Newest', + read: 'Read' + }, + empty: { + title: "You're all caught up!", + description: + "We'll let you know if there are new notifications for you." + } + } + }, + es: { + onboarding: { + title: 'Presentacion de Decentraland Inbox', + description: + '¡No te pierdas nada nunca más! Ahora recibirás una notificación cada vez que ocurra algo relevante en tu cuenta.', + button: 'Continuar' + }, + feed: { + title: 'Notificaciones', + tabs: { + newest: 'Mas reciente', + read: 'Leidas' + }, + empty: { + title: '¡Ya estas al día!', + description: 'Te avisaremos si hay nuevas notificaciones para ti.' + } + } + }, + zh: { + onboarding: { + title: '介绍 Decentraland 收件箱系统 ', + description: + '不再错过任何信息!现在,每当您的账户发生相关事件,您都会收到通知。', + button: '讓我們開始' + }, + feed: { + title: '通知', + tabs: { + newest: '最新', + read: '阅读' + }, + empty: { + title: '你们都赶上了!', + description: '如果有新的通知,我们会及时通知您' + } + } + } +} + +const NotificationHandler = ({ locale, notification }: { notification: DCLNotification, locale: NotificationLocale }) => { + switch (notification.type) { + case 'item_sold': + return ( + + ) + case 'royalties_earned': + return ( + + ) + default: + return null + } +} + +export default function NotificationsFeed({ + userNotifications, + isLoading, + locale, + isOnboarding, + activeTab, + onChangeTab, + onBegin +}: NotificationsFeedProps) { + const unreadNotifications = useMemo( + () => userNotifications.filter((notification) => !notification.read), + [userNotifications] + ) + const previousNotifications = useMemo( + () => + userNotifications.filter((notification) => { + if (!notification.read) return false + + const diff = Time(notification.timestamp).diff(new Date(), 'hour') + if (diff >= -24 && diff < 0) { + return true + } + }), + [userNotifications] + ) + const readNotifications = useMemo( + () => userNotifications.filter((notification) => notification.read), + [userNotifications] + ) + + if (isOnboarding) { + return ( +
+
+
+
+

+ {i18N[locale].onboarding.title} +

+

+ {i18N[locale].onboarding.description} +

+
+ +
+
+
+
+ ) + } + + return ( +
+
+

+ {i18N[locale].feed.title} +

+
+ {!isLoading && ( +
+ {userNotifications.length > 0 && ( + + onChangeTab(e, 'newest')} + > + {i18N[locale].feed.tabs.newest} + + onChangeTab(e, 'read')} + > + {i18N[locale].feed.tabs.read} + + + )} + {userNotifications.length > 0 && ( +
+
+ {activeTab == 'newest' ? ( + <> +
+ {unreadNotifications.map((notification) => )} +
+ { + previousNotifications.length > 0 && ( +
+

+ Previous +

+ {previousNotifications.map((notification) => )} +
+ ) + } + + ) : ( + <> + {readNotifications.map((notification) => )} + + )} +
+
+ )} + {!userNotifications.length && ( +
+ +

+ {i18N[locale].feed.empty.title} +

+

+ {i18N[locale].feed.empty.description} +

+
+ )} +
+ )} + {isLoading && } +
+ ) +} diff --git a/src/components/Notifications/types.ts b/src/components/Notifications/types.ts new file mode 100644 index 00000000..e0b504e1 --- /dev/null +++ b/src/components/Notifications/types.ts @@ -0,0 +1,49 @@ +import { NFTCategory, Rarity } from '@dcl/schemas' + +export type ActiveTab = 'newest' | 'read' + +export type NotificationLocale = 'en' | 'es' | 'zh' + +type RawDecentralandNotification = { + type: T + address: string + timestamp: number + read: boolean + created_at: string + updated_at: string + metadata: M +} + +export type DecentralandNotificationType = 'item_sold' | 'royalties_earned' + +type CommonNFTMetadata = { + link: string + image: string + rarity: Rarity + nftName: string + network: 'ethereum' | 'polygon' + category: NFTCategory +} + +type ItemSoldMetadata = CommonNFTMetadata & { + seller: string +} + +type RoyalitesEarnedMetadata = CommonNFTMetadata & { + royaltiesCollector: string + royaltiesCut: string +} + +export type MetadataTypes = ItemSoldMetadata | RoyalitesEarnedMetadata + +export type ItemSoldNotification = RawDecentralandNotification< + 'item_sold', + ItemSoldMetadata +> + +export type RoyalitesEarnedNotification = RawDecentralandNotification< + 'royalties_earned', + RoyalitesEarnedMetadata +> + +export type DCLNotification = ItemSoldNotification | RoyalitesEarnedNotification diff --git a/src/lib/time.ts b/src/lib/time.ts new file mode 100644 index 00000000..6e2b55e5 --- /dev/null +++ b/src/lib/time.ts @@ -0,0 +1,6 @@ +import Time from 'dayjs' +import relativeTime from 'dayjs/plugin/relativeTime' + +Time.extend(relativeTime) + +export default Time From f450ba85cf25109f715acc7f59b189fd75cd3105 Mon Sep 17 00:00:00 2001 From: lauti7 Date: Thu, 30 Nov 2023 13:54:37 -0300 Subject: [PATCH 02/14] fix: prettier --- .../RoyaltiesEarnedNotification.tsx | 50 +++++++-------- .../Notifications/Notifications.stories.tsx | 63 ++++++++++--------- .../Notifications/NotificationsFeed.tsx | 58 ++++++++++------- 3 files changed, 94 insertions(+), 77 deletions(-) diff --git a/src/components/Notifications/NotificationTypes/RoyaltiesEarnedNotification.tsx b/src/components/Notifications/NotificationTypes/RoyaltiesEarnedNotification.tsx index ce7369db..d774c70f 100644 --- a/src/components/Notifications/NotificationTypes/RoyaltiesEarnedNotification.tsx +++ b/src/components/Notifications/NotificationTypes/RoyaltiesEarnedNotification.tsx @@ -13,17 +13,17 @@ interface RoyaltiesEarnedNotificationProps { const i18N = { en: { - description_1: `You earned `, + description_1: `You earned `, description_2: `for selling `, title: 'Royalties Earned' }, es: { - description_1: `Ganaste `, + description_1: `Ganaste `, description_2: `por vender `, title: 'Regalias ganadas' }, zh: { - description_1: `您通过出售 `, + description_1: `您通过出售 `, description_2: `赚取了 `, title: '所得版税' } @@ -51,28 +51,28 @@ const RoyaltiesEarnedNotification = ({

{i18N[locale].title}

- { - locale == "zh" ? - ( -

- {i18N[locale].description_1} - - - {notification.metadata.nftName} - - - {' '}{i18N[locale].description_2}{Number(notification.metadata.royaltiesCut)} -

- ) : + {locale == 'zh' ? (

- {i18N[locale].description_1}{Number(notification.metadata.royaltiesCut)} - {' '}{i18N[locale].description_2} + {i18N[locale].description_1} + + + {notification.metadata.nftName} + + {' '} + {i18N[locale].description_2} + {Number(notification.metadata.royaltiesCut)} +

+ ) : ( +

+ {i18N[locale].description_1} + {Number(notification.metadata.royaltiesCut)}{' '} + {i18N[locale].description_2}

- } + )} ) } diff --git a/src/components/Notifications/Notifications.stories.tsx b/src/components/Notifications/Notifications.stories.tsx index 2600cac9..dec60523 100644 --- a/src/components/Notifications/Notifications.stories.tsx +++ b/src/components/Notifications/Notifications.stories.tsx @@ -43,11 +43,11 @@ storiesOf('Notifications Toggle', module) rarity: 'epic' as Rarity, seller: '0x8bc619e7f9ca9949b8440245fd9d8c4c002edf02', nftName: 'Green Atari Tee', - network: "ethereum", - category: "wearable" as NFTCategory + network: 'ethereum', + category: 'wearable' as NFTCategory }, - created_at: "2023-11-29T12:51:00.600Z", - updated_at: "2023-11-29T12:51:00.600Z" + created_at: '2023-11-29T12:51:00.600Z', + updated_at: '2023-11-29T12:51:00.600Z' } ]} locale="en" @@ -79,11 +79,11 @@ storiesOf('Notifications Toggle', module) rarity: 'epic' as Rarity, seller: '0x8bc619e7f9ca9949b8440245fd9d8c4c002edf02', nftName: 'Green Atari Tee', - network: "ethereum", - category: "wearable" as NFTCategory + network: 'ethereum', + category: 'wearable' as NFTCategory }, - created_at: "2023-11-29T12:51:00.600Z", - updated_at: "2023-11-29T12:51:00.600Z" + created_at: '2023-11-29T12:51:00.600Z', + updated_at: '2023-11-29T12:51:00.600Z' } ]} activeTab="newest" @@ -106,21 +106,22 @@ storiesOf('Notifications Toggle', module) userNotifications={[ { read: false, - type: "royalties_earned", + type: 'royalties_earned', address: '0xA', timestamp: 1701198655 * 1000, metadata: { - link: "https://market.decentraland.zone/contracts/0xb726634ed82ac04e6bca66b3b97cc41a2c10ec31/tokens/9", - image: 'https://peer.decentraland.org/lambdas/collections/contents/urn:decentraland:ethereum:collections-v1:binance_us_collection:binance_us_upper_body/thumbnail', - rarity: "common" as Rarity, - network: "polygon", - nftName: "NJacket", - category: "wearable" as NFTCategory, - royaltiesCut: "0.3", - royaltiesCollector: "0x2a39d4f68133491f0442496f601cde2a945b6d31" + link: 'https://market.decentraland.zone/contracts/0xb726634ed82ac04e6bca66b3b97cc41a2c10ec31/tokens/9', + image: + 'https://peer.decentraland.org/lambdas/collections/contents/urn:decentraland:ethereum:collections-v1:binance_us_collection:binance_us_upper_body/thumbnail', + rarity: 'common' as Rarity, + network: 'polygon', + nftName: 'NJacket', + category: 'wearable' as NFTCategory, + royaltiesCut: '0.3', + royaltiesCollector: '0x2a39d4f68133491f0442496f601cde2a945b6d31' }, - created_at: "2023-11-29T12:51:00.600Z", - updated_at: "2023-11-29T12:51:00.600Z" + created_at: '2023-11-29T12:51:00.600Z', + updated_at: '2023-11-29T12:51:00.600Z' }, { read: true, @@ -134,11 +135,11 @@ storiesOf('Notifications Toggle', module) rarity: 'uncommon' as Rarity, seller: '0x6b347a82fcac4e6a38d1fc40e3631bd8f9495e9f', nftName: 'Binance US Hoodie', - network: "ethereum", - category: "wearable" as NFTCategory + network: 'ethereum', + category: 'wearable' as NFTCategory }, - created_at: "2023-11-29T12:51:00.600Z", - updated_at: "2023-11-29T12:51:00.600Z" + created_at: '2023-11-29T12:51:00.600Z', + updated_at: '2023-11-29T12:51:00.600Z' }, { read: true, @@ -152,11 +153,11 @@ storiesOf('Notifications Toggle', module) rarity: 'uncommon' as Rarity, seller: '0x6b347a82fcac4e6a38d1fc40e3631bd8f9495e9f', nftName: 'Binance US Hoodie', - network: "ethereum", - category: "wearable" as NFTCategory + network: 'ethereum', + category: 'wearable' as NFTCategory }, - created_at: "2023-11-29T12:51:00.600Z", - updated_at: "2023-11-29T12:51:00.600Z" + created_at: '2023-11-29T12:51:00.600Z', + updated_at: '2023-11-29T12:51:00.600Z' }, { read: true, @@ -170,11 +171,11 @@ storiesOf('Notifications Toggle', module) rarity: 'uncommon' as Rarity, seller: '0x6b347a82fcac4e6a38d1fc40e3631bd8f9495e9f', nftName: 'Binance US Hoodie', - network: "ethereum", - category: "wearable" as NFTCategory + network: 'ethereum', + category: 'wearable' as NFTCategory }, - created_at: "2023-11-29T12:51:00.600Z", - updated_at: "2023-11-29T12:51:00.600Z" + created_at: '2023-11-29T12:51:00.600Z', + updated_at: '2023-11-29T12:51:00.600Z' } ]} activeTab="newest" diff --git a/src/components/Notifications/NotificationsFeed.tsx b/src/components/Notifications/NotificationsFeed.tsx index 680d3d0d..92cb7ac7 100644 --- a/src/components/Notifications/NotificationsFeed.tsx +++ b/src/components/Notifications/NotificationsFeed.tsx @@ -85,22 +85,25 @@ const i18N = { } } -const NotificationHandler = ({ locale, notification }: { notification: DCLNotification, locale: NotificationLocale }) => { +const NotificationHandler = ({ + locale, + notification +}: { + notification: DCLNotification + locale: NotificationLocale +}) => { switch (notification.type) { case 'item_sold': return ( - - ) - case 'royalties_earned': - return ( - - ) + + ) + case 'royalties_earned': + return ( + + ) default: return null } @@ -190,11 +193,15 @@ export default function NotificationsFeed({ {activeTab == 'newest' ? ( <>
- {unreadNotifications.map((notification) => )} + {unreadNotifications.map((notification) => ( + + ))}
- { - previousNotifications.length > 0 && ( -
+ {previousNotifications.length > 0 && ( +

Previous

- {previousNotifications.map((notification) => )} + {previousNotifications.map((notification) => ( + + ))}
- ) - } + )} ) : ( <> - {readNotifications.map((notification) => )} + {readNotifications.map((notification) => ( + + ))} )}
From f79cd830cad36dcf3d838d8776925174d5907580 Mon Sep 17 00:00:00 2001 From: lauti7 Date: Fri, 1 Dec 2023 11:30:56 -0300 Subject: [PATCH 03/14] fix: missing mana --- .../NotificationTypes/RoyaltiesEarnedNotification.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Notifications/NotificationTypes/RoyaltiesEarnedNotification.tsx b/src/components/Notifications/NotificationTypes/RoyaltiesEarnedNotification.tsx index d774c70f..cc1d940c 100644 --- a/src/components/Notifications/NotificationTypes/RoyaltiesEarnedNotification.tsx +++ b/src/components/Notifications/NotificationTypes/RoyaltiesEarnedNotification.tsx @@ -66,12 +66,12 @@ const RoyaltiesEarnedNotification = ({
{' '} {i18N[locale].description_2} - {Number(notification.metadata.royaltiesCut)} + {Number(notification.metadata.royaltiesCut)} MANA

) : (

{i18N[locale].description_1} - {Number(notification.metadata.royaltiesCut)}{' '} + {Number(notification.metadata.royaltiesCut)} MANA {' '} {i18N[locale].description_2} Date: Fri, 1 Dec 2023 12:57:02 -0300 Subject: [PATCH 04/14] fix: bell went up --- src/components/Notifications/Notifications.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/components/Notifications/Notifications.css b/src/components/Notifications/Notifications.css index 9e1d4006..8314d452 100644 --- a/src/components/Notifications/Notifications.css +++ b/src/components/Notifications/Notifications.css @@ -5,6 +5,11 @@ flex-direction: column; } +.dcl.notifications > div { + width: 32px; + height: 32px; +} + .dcl.notifications-bell { display: flex; background: none; From ed4e6b8fea28045b84a49f63c64366290393a161 Mon Sep 17 00:00:00 2001 From: lauti7 Date: Fri, 1 Dec 2023 14:17:24 -0300 Subject: [PATCH 05/14] fix: just first child --- src/components/Notifications/Notifications.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Notifications/Notifications.css b/src/components/Notifications/Notifications.css index 8314d452..1a9c550b 100644 --- a/src/components/Notifications/Notifications.css +++ b/src/components/Notifications/Notifications.css @@ -5,7 +5,7 @@ flex-direction: column; } -.dcl.notifications > div { +.dcl.notifications > div:first-child { width: 32px; height: 32px; } From 8af3e7f242cfe7eb1a43b18d1dc5d0e30f5d019c Mon Sep 17 00:00:00 2001 From: lauti7 Date: Mon, 4 Dec 2023 16:49:09 -0300 Subject: [PATCH 06/14] fix: styles --- .../Notifications/Notifications.css | 1 + .../Notifications/NotificationsFeed.css | 29 ++-- .../Notifications/NotificationsFeed.tsx | 153 +++++++++++------- src/components/Tabs/Tabs.tsx | 5 +- 4 files changed, 108 insertions(+), 80 deletions(-) diff --git a/src/components/Notifications/Notifications.css b/src/components/Notifications/Notifications.css index 1a9c550b..301550c4 100644 --- a/src/components/Notifications/Notifications.css +++ b/src/components/Notifications/Notifications.css @@ -3,6 +3,7 @@ justify-content: center; align-items: center; flex-direction: column; + position: relative; } .dcl.notifications > div:first-child { diff --git a/src/components/Notifications/NotificationsFeed.css b/src/components/Notifications/NotificationsFeed.css index 9d3edd37..faab0b7b 100644 --- a/src/components/Notifications/NotificationsFeed.css +++ b/src/components/Notifications/NotificationsFeed.css @@ -1,22 +1,16 @@ .dcl.notifications-feed { width: 384px; - min-height: 200px; - max-height: 385px; - height: 100%; - overflow-y: scroll; pointer-events: visible; - opacity: 1; display: flex; flex-direction: column; - position: relative; - top: 5px; + position: absolute; + top: 40px; z-index: 100; border-radius: 8px; border: 1px solid hsla(260, 6%, 46%, 0.24); background: var(--background); box-shadow: 0px 1px 24px 0px hsla(260, 6%, 46%, 0.24); transition: opacity 0.3s ease; - overscroll-behavior-y: contain; } .dcl.notifications-feed::before { @@ -26,8 +20,8 @@ height: 28px; background: var(--background); transform: rotate(-45deg); - top: -0.30714286em; - left: 46.5%; + top: -2px; + left: calc(50% - 14px); box-shadow: -1px -1px 0 0 var(--background); } @@ -43,6 +37,12 @@ padding: 8px 16px; } +.dcl.notifications-feed__content + > .dcl.tabs.notifications-feed__tabs + > .ui.container { + justify-content: flex-start !important; +} + @media (max-width: 768px) { .dcl.notifications-feed__content > .dcl.tabs, .dcl.tabs.fullscreen { @@ -63,17 +63,14 @@ font-weight: 600; } -.dcl.notifications-feed__header > .ui.basic.button.notifications-feed__markit { - color: #a09ba8 !important; - font-size: 12px; - padding: 8px 12px; -} - .dcl.notifications-feed__list-container { display: flex; flex-direction: column; height: 100%; justify-content: space-between; + max-height: 290px; + overflow-y: scroll; + overscroll-behavior-y: contain; } .dcl.notifications-feed__list { diff --git a/src/components/Notifications/NotificationsFeed.tsx b/src/components/Notifications/NotificationsFeed.tsx index 92cb7ac7..64f02a72 100644 --- a/src/components/Notifications/NotificationsFeed.tsx +++ b/src/components/Notifications/NotificationsFeed.tsx @@ -5,11 +5,13 @@ import { ActiveTab, DCLNotification, NotificationLocale } from './types' import './NotificationsFeed.css' import ItemSoldNotification from './NotificationTypes/ItemSoldNotification' +import RoyaltiesEarnedNotification from './NotificationTypes/RoyaltiesEarnedNotification' +import BidAcceptedNotification from './NotificationTypes/BidAcceptedNotification' import EmptyInbox from '../Icons/Notifications/EmptyInbox' import { Tabs } from '../Tabs/Tabs' import { Button } from '../Button/Button' import Time from '../../lib/time' -import RoyaltiesEarnedNotification from './NotificationTypes/RoyaltiesEarnedNotification' +import BidReceivedNotification from './NotificationTypes/BidReceivedNotification' interface NotificationsFeedProps { userNotifications: DCLNotification[] @@ -104,6 +106,20 @@ const NotificationHandler = ({ locale={locale} /> ) + case 'bid_accepted': + return ( + + ) + case 'bid_received': + return ( + + ) default: return null } @@ -122,6 +138,7 @@ export default function NotificationsFeed({ () => userNotifications.filter((notification) => !notification.read), [userNotifications] ) + const previousNotifications = useMemo( () => userNotifications.filter((notification) => { @@ -134,8 +151,14 @@ export default function NotificationsFeed({ }), [userNotifications] ) + const readNotifications = useMemo( - () => userNotifications.filter((notification) => notification.read), + () => + userNotifications.filter( + (notification) => + notification.read && + !previousNotifications.find(({ id }) => id === notification.id) + ), [userNotifications] ) @@ -171,83 +194,89 @@ export default function NotificationsFeed({ {!isLoading && (

- {userNotifications.length > 0 && ( - - onChangeTab(e, 'newest')} - > - {i18N[locale].feed.tabs.newest} - + + onChangeTab(e, 'newest')} + > + {i18N[locale].feed.tabs.newest} + + {readNotifications.length > 0 && ( onChangeTab(e, 'read')} > {i18N[locale].feed.tabs.read} - - )} - {userNotifications.length > 0 && ( -
-
- {activeTab == 'newest' ? ( - <> -
- {unreadNotifications.map((notification) => ( - - ))} -
- {previousNotifications.length > 0 && ( + )} + +
+
+ {activeTab == 'newest' ? ( + <> + {!unreadNotifications.length && + !previousNotifications.length ? ( + + ) : ( + <>
-

- Previous -

- {previousNotifications.map((notification) => ( + {unreadNotifications.map((notification) => ( ))}
- )} - - ) : ( - <> - {readNotifications.map((notification) => ( - - ))} - - )} -
-
- )} - {!userNotifications.length && ( -
- -

- {i18N[locale].feed.empty.title} -

-

- {i18N[locale].feed.empty.description} -

+ {previousNotifications.length > 0 && ( +
+

+ Previous +

+ {previousNotifications.map((notification) => ( + + ))} +
+ )} + + )} + + ) : ( + <> + {readNotifications.map((notification) => ( + + ))} + + )}
- )} +
)} {isLoading && }
) } + +const NoNotifications = ({ locale }: { locale: NotificationLocale }) => ( +
+ +

+ {i18N[locale].feed.empty.title} +

+

+ {i18N[locale].feed.empty.description} +

+
+) diff --git a/src/components/Tabs/Tabs.tsx b/src/components/Tabs/Tabs.tsx index 8fa8437f..a5440d49 100644 --- a/src/components/Tabs/Tabs.tsx +++ b/src/components/Tabs/Tabs.tsx @@ -6,6 +6,7 @@ import './Tabs.css' export type TabsProps = { isFullscreen?: boolean onClick?: (e: React.MouseEvent) => void + className: string } export class Tabs extends React.PureComponent { @@ -36,10 +37,10 @@ export class Tabs extends React.PureComponent { ) render(): JSX.Element { - const { children, isFullscreen, onClick } = this.props + const { children, isFullscreen, onClick, className } = this.props return (
From dd8de6e960fbbde6a691eb41cf16ad67b74c0173 Mon Sep 17 00:00:00 2001 From: lauti7 Date: Mon, 4 Dec 2023 16:50:27 -0300 Subject: [PATCH 07/14] feat: new notification types --- .../BidAcceptedNotification.tsx | 68 +++++++++++++++++++ .../BidReceivedNotification.tsx | 68 +++++++++++++++++++ .../RoyaltiesEarnedNotification.tsx | 4 +- src/components/Notifications/types.ts | 17 ++++- 4 files changed, 153 insertions(+), 4 deletions(-) create mode 100644 src/components/Notifications/NotificationTypes/BidAcceptedNotification.tsx create mode 100644 src/components/Notifications/NotificationTypes/BidReceivedNotification.tsx diff --git a/src/components/Notifications/NotificationTypes/BidAcceptedNotification.tsx b/src/components/Notifications/NotificationTypes/BidAcceptedNotification.tsx new file mode 100644 index 00000000..17a71fb4 --- /dev/null +++ b/src/components/Notifications/NotificationTypes/BidAcceptedNotification.tsx @@ -0,0 +1,68 @@ +import React from 'react' + +import { BidAcceptedNotification, NotificationLocale } from '../types' +import NotificationItem from '../NotificationItem' +import BidAccepted from '../../Icons/Notifications/BidAccepted' +import { Rarity } from '@dcl/schemas' + +interface BidAcceptedNotificationProps { + notification: BidAcceptedNotification + locale: NotificationLocale +} + +const i18N = { + en: { + description: ( + mana: React.ReactNode, + nftName: React.ReactNode + ): React.ReactNode => + `Your bid of ${mana} MANA was accepted for ${nftName}`, + title: 'Bid Accepted' + }, + es: { description: ( + mana: React.ReactNode, + nftName: React.ReactNode + ): React.ReactNode => + `Tu oferta de ${mana} MANA fue aceptada para ${nftName}`, title: 'Oferta aceptada' }, + zh: { description: ( + mana: React.ReactNode, + nftName: React.ReactNode + ): React.ReactNode => `您的出价 ${mana} MANA 已被接受 ${nftName}` , title: '接受投标' } +} + +const BidAcceptedNotification = ({ + notification, + locale +}: BidAcceptedNotificationProps) => { + return ( + + }} + timestamp={notification.timestamp} + isNew={!notification.read} + > +

+ {i18N[locale].title} +

+

+ {i18N[locale].description}{' '} + + + {notification.metadata.nftName} + + +

+ + ) +} + +export default BidAcceptedNotification diff --git a/src/components/Notifications/NotificationTypes/BidReceivedNotification.tsx b/src/components/Notifications/NotificationTypes/BidReceivedNotification.tsx new file mode 100644 index 00000000..ed8a9f65 --- /dev/null +++ b/src/components/Notifications/NotificationTypes/BidReceivedNotification.tsx @@ -0,0 +1,68 @@ +import React from 'react' + +import { BidReceivedNotification, NotificationLocale } from '../types' +import NotificationItem from '../NotificationItem' +import BidAccepted from '../../Icons/Notifications/BidAccepted' +import { Rarity } from '@dcl/schemas' + +interface BidReceivedNotificationProps { + notification: BidReceivedNotification + locale: NotificationLocale +} + +const i18N = { + en: { + description: ( + mana: React.ReactNode, + nftName: React.ReactNode + ): React.ReactNode => + `Your received a bid of ${mana} MANA was accepted for ${nftName}`, + title: 'Bid Received' + }, + es: { description: ( + mana: React.ReactNode, + nftName: React.ReactNode + ): React.ReactNode => + `Recibiste una oferta de ${mana} MANA para ${nftName}`, title: 'Oferta aceptada' }, + zh: { description: ( + mana: React.ReactNode, + nftName: React.ReactNode + ): React.ReactNode => `您为 ${nftName} 出价 ${mana} MANA 已被接受` , title: '收到的投标' } +} + +const BidReceivedNotification = ({ + notification, + locale +}: BidReceivedNotificationProps) => { + return ( + + }} + timestamp={notification.timestamp} + isNew={!notification.read} + > +

+ {i18N[locale].title} +

+

+ {i18N[locale].description}{' '} + + + {notification.metadata.nftName} + + +

+
+ ) +} + +export default BidReceivedNotification diff --git a/src/components/Notifications/NotificationTypes/RoyaltiesEarnedNotification.tsx b/src/components/Notifications/NotificationTypes/RoyaltiesEarnedNotification.tsx index cc1d940c..18a67569 100644 --- a/src/components/Notifications/NotificationTypes/RoyaltiesEarnedNotification.tsx +++ b/src/components/Notifications/NotificationTypes/RoyaltiesEarnedNotification.tsx @@ -66,12 +66,12 @@ const RoyaltiesEarnedNotification = ({ {' '} {i18N[locale].description_2} - {Number(notification.metadata.royaltiesCut)} MANA + {Number(notification.metadata.royaltiesCut)} MANA

) : (

{i18N[locale].description_1} - {Number(notification.metadata.royaltiesCut)} MANA {' '} + {Number(notification.metadata.royaltiesCut)} MANA{' '} {i18N[locale].description_2} = { + id: string type: T address: string timestamp: number @@ -14,7 +15,7 @@ type RawDecentralandNotification = { metadata: M } -export type DecentralandNotificationType = 'item_sold' | 'royalties_earned' +export type DecentralandNotificationType = 'item_sold' | 'royalties_earned' | 'bid_accepted' | 'bid_received' type CommonNFTMetadata = { link: string @@ -34,6 +35,14 @@ type RoyalitesEarnedMetadata = CommonNFTMetadata & { royaltiesCut: string } +type BidAcceptedMetadata = CommonNFTMetadata & { + +} + +type BidReceivedMetadata = CommonNFTMetadata & { + +} + export type MetadataTypes = ItemSoldMetadata | RoyalitesEarnedMetadata export type ItemSoldNotification = RawDecentralandNotification< @@ -46,4 +55,8 @@ export type RoyalitesEarnedNotification = RawDecentralandNotification< RoyalitesEarnedMetadata > -export type DCLNotification = ItemSoldNotification | RoyalitesEarnedNotification +export type BidAcceptedNotification = RawDecentralandNotification<'bid_accepted', BidAcceptedMetadata> + +export type BidReceivedNotification = RawDecentralandNotification<'bid_received', BidReceivedMetadata> + +export type DCLNotification = ItemSoldNotification | RoyalitesEarnedNotification | BidAcceptedNotification | BidReceivedNotification From 9bd082e4bbec21f23f404a4f9d7597c2d2e5e3a9 Mon Sep 17 00:00:00 2001 From: lauti7 Date: Mon, 4 Dec 2023 16:51:23 -0300 Subject: [PATCH 08/14] chore: storybook --- .../Notifications/Notifications.stories.tsx | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/components/Notifications/Notifications.stories.tsx b/src/components/Notifications/Notifications.stories.tsx index dec60523..8ac81749 100644 --- a/src/components/Notifications/Notifications.stories.tsx +++ b/src/components/Notifications/Notifications.stories.tsx @@ -4,6 +4,7 @@ import Notifications from './Notifications' import NotificationItemImage from './NotificationItemImage' import { NFTCategory, Rarity } from '@dcl/schemas' import BidReceived from '../Icons/Notifications/BidReceived' +import { ActiveTab } from './types' storiesOf('Notifications Toggle', module) .add('Without new notifications', () => { @@ -32,6 +33,7 @@ storiesOf('Notifications Toggle', module) isOnboarding={false} userNotifications={[ { + id: 'A', read: false, type: 'item_sold', address: '0xA', @@ -68,6 +70,7 @@ storiesOf('Notifications Toggle', module) isOnboarding userNotifications={[ { + id: 'A', read: false, type: 'item_sold', address: '0xA', @@ -96,6 +99,7 @@ storiesOf('Notifications Toggle', module) ) }) .add('Open not loading', () => { + const [tab, setTab] = React.useState('newest') return (

console.log(newTab)} + activeTab={tab as ActiveTab} + onChangeTab={(e, newTab) => setTab(newTab)} onClickToggle={() => console.log('Toggle button')} onBegin={() => console.log('Begin')} /> From d6e35faf729472ddc8a16492fe9f1e9d401448ea Mon Sep 17 00:00:00 2001 From: lauti7 Date: Mon, 4 Dec 2023 17:13:20 -0300 Subject: [PATCH 09/14] fix: new tpypes --- .../BidAcceptedNotification.tsx | 51 +++++++++++-------- .../BidReceivedNotification.tsx | 51 +++++++++++-------- .../RoyaltiesEarnedNotification.tsx | 5 +- .../Notifications/NotificationsFeed.tsx | 10 +--- src/components/Notifications/types.ts | 26 +++++++--- src/components/Notifications/utils.ts | 10 ++++ 6 files changed, 95 insertions(+), 58 deletions(-) create mode 100644 src/components/Notifications/utils.ts diff --git a/src/components/Notifications/NotificationTypes/BidAcceptedNotification.tsx b/src/components/Notifications/NotificationTypes/BidAcceptedNotification.tsx index 17a71fb4..e3a1b2fb 100644 --- a/src/components/Notifications/NotificationTypes/BidAcceptedNotification.tsx +++ b/src/components/Notifications/NotificationTypes/BidAcceptedNotification.tsx @@ -4,6 +4,7 @@ import { BidAcceptedNotification, NotificationLocale } from '../types' import NotificationItem from '../NotificationItem' import BidAccepted from '../../Icons/Notifications/BidAccepted' import { Rarity } from '@dcl/schemas' +import { formatMana } from '../utils' interface BidAcceptedNotificationProps { notification: BidAcceptedNotification @@ -19,15 +20,21 @@ const i18N = { `Your bid of ${mana} MANA was accepted for ${nftName}`, title: 'Bid Accepted' }, - es: { description: ( - mana: React.ReactNode, - nftName: React.ReactNode - ): React.ReactNode => - `Tu oferta de ${mana} MANA fue aceptada para ${nftName}`, title: 'Oferta aceptada' }, - zh: { description: ( - mana: React.ReactNode, - nftName: React.ReactNode - ): React.ReactNode => `您的出价 ${mana} MANA 已被接受 ${nftName}` , title: '接受投标' } + es: { + description: ( + mana: React.ReactNode, + nftName: React.ReactNode + ): React.ReactNode => + `Tu oferta de ${mana} MANA fue aceptada para ${nftName}`, + title: 'Oferta aceptada' + }, + zh: { + description: ( + mana: React.ReactNode, + nftName: React.ReactNode + ): React.ReactNode => `您的出价 ${mana} MANA 已被接受 ${nftName}`, + title: '接受投标' + } } const BidAcceptedNotification = ({ @@ -48,18 +55,20 @@ const BidAcceptedNotification = ({ {i18N[locale].title}

- {i18N[locale].description}{' '} - - - {notification.metadata.nftName} - - + {i18N[locale].description( + formatMana(notification.metadata.price), + + + {notification.metadata.nftName} + + + )}

) diff --git a/src/components/Notifications/NotificationTypes/BidReceivedNotification.tsx b/src/components/Notifications/NotificationTypes/BidReceivedNotification.tsx index ed8a9f65..6376b696 100644 --- a/src/components/Notifications/NotificationTypes/BidReceivedNotification.tsx +++ b/src/components/Notifications/NotificationTypes/BidReceivedNotification.tsx @@ -4,6 +4,7 @@ import { BidReceivedNotification, NotificationLocale } from '../types' import NotificationItem from '../NotificationItem' import BidAccepted from '../../Icons/Notifications/BidAccepted' import { Rarity } from '@dcl/schemas' +import { formatMana } from '../utils' interface BidReceivedNotificationProps { notification: BidReceivedNotification @@ -19,15 +20,21 @@ const i18N = { `Your received a bid of ${mana} MANA was accepted for ${nftName}`, title: 'Bid Received' }, - es: { description: ( - mana: React.ReactNode, - nftName: React.ReactNode - ): React.ReactNode => - `Recibiste una oferta de ${mana} MANA para ${nftName}`, title: 'Oferta aceptada' }, - zh: { description: ( - mana: React.ReactNode, - nftName: React.ReactNode - ): React.ReactNode => `您为 ${nftName} 出价 ${mana} MANA 已被接受` , title: '收到的投标' } + es: { + description: ( + mana: React.ReactNode, + nftName: React.ReactNode + ): React.ReactNode => + `Recibiste una oferta de ${mana} MANA para ${nftName}`, + title: 'Oferta aceptada' + }, + zh: { + description: ( + mana: React.ReactNode, + nftName: React.ReactNode + ): React.ReactNode => `您为 ${nftName} 出价 ${mana} MANA 已被接受`, + title: '收到的投标' + } } const BidReceivedNotification = ({ @@ -48,18 +55,20 @@ const BidReceivedNotification = ({ {i18N[locale].title}

- {i18N[locale].description}{' '} - - - {notification.metadata.nftName} - - + {i18N[locale].description( + formatMana(notification.metadata.price), + + + {notification.metadata.nftName} + + + )}

) diff --git a/src/components/Notifications/NotificationTypes/RoyaltiesEarnedNotification.tsx b/src/components/Notifications/NotificationTypes/RoyaltiesEarnedNotification.tsx index 18a67569..f4b8b071 100644 --- a/src/components/Notifications/NotificationTypes/RoyaltiesEarnedNotification.tsx +++ b/src/components/Notifications/NotificationTypes/RoyaltiesEarnedNotification.tsx @@ -5,6 +5,7 @@ import NotificationItem from '../NotificationItem' import ManaMainnet from '../../Icons/Notifications/ManaMainnet' import ManaPolygon from '../../Icons/Notifications/ManaPoly' import { Rarity } from '@dcl/schemas' +import { formatMana } from '../utils' interface RoyaltiesEarnedNotificationProps { notification: RoyalitesEarnedNotification @@ -66,12 +67,12 @@ const RoyaltiesEarnedNotification = ({ {' '} {i18N[locale].description_2} - {Number(notification.metadata.royaltiesCut)} MANA + {formatMana(notification.metadata.royaltiesCut)} MANA

) : (

{i18N[locale].description_1} - {Number(notification.metadata.royaltiesCut)} MANA{' '} + {formatMana(notification.metadata.royaltiesCut)} MANA{' '} {i18N[locale].description_2} + ) case 'bid_received': return ( - + ) default: return null diff --git a/src/components/Notifications/types.ts b/src/components/Notifications/types.ts index 9aad3dd7..3fa1ec40 100644 --- a/src/components/Notifications/types.ts +++ b/src/components/Notifications/types.ts @@ -15,7 +15,11 @@ type RawDecentralandNotification = { metadata: M } -export type DecentralandNotificationType = 'item_sold' | 'royalties_earned' | 'bid_accepted' | 'bid_received' +export type DecentralandNotificationType = + | 'item_sold' + | 'royalties_earned' + | 'bid_accepted' + | 'bid_received' type CommonNFTMetadata = { link: string @@ -36,11 +40,11 @@ type RoyalitesEarnedMetadata = CommonNFTMetadata & { } type BidAcceptedMetadata = CommonNFTMetadata & { - + price: string } type BidReceivedMetadata = CommonNFTMetadata & { - + price: string } export type MetadataTypes = ItemSoldMetadata | RoyalitesEarnedMetadata @@ -55,8 +59,18 @@ export type RoyalitesEarnedNotification = RawDecentralandNotification< RoyalitesEarnedMetadata > -export type BidAcceptedNotification = RawDecentralandNotification<'bid_accepted', BidAcceptedMetadata> +export type BidAcceptedNotification = RawDecentralandNotification< + 'bid_accepted', + BidAcceptedMetadata +> -export type BidReceivedNotification = RawDecentralandNotification<'bid_received', BidReceivedMetadata> +export type BidReceivedNotification = RawDecentralandNotification< + 'bid_received', + BidReceivedMetadata +> -export type DCLNotification = ItemSoldNotification | RoyalitesEarnedNotification | BidAcceptedNotification | BidReceivedNotification +export type DCLNotification = + | ItemSoldNotification + | RoyalitesEarnedNotification + | BidAcceptedNotification + | BidReceivedNotification diff --git a/src/components/Notifications/utils.ts b/src/components/Notifications/utils.ts new file mode 100644 index 00000000..78c55e00 --- /dev/null +++ b/src/components/Notifications/utils.ts @@ -0,0 +1,10 @@ +export const MAXIMUM_FRACTION_DIGITS = 2 + +export function formatMana( + mana: string, + maximumFractionDigits = MAXIMUM_FRACTION_DIGITS +): string { + return (Number(mana || '0') / 1e18) + .toFixed(maximumFractionDigits) + .toLocaleString() +} From 55a813a77e94f6a4c40cc320024943a169db3481 Mon Sep 17 00:00:00 2001 From: lauti7 Date: Tue, 5 Dec 2023 12:08:31 -0300 Subject: [PATCH 10/14] fix: return reactnode --- .../NotificationTypes/BidAcceptedNotification.tsx | 6 +++--- .../NotificationTypes/BidReceivedNotification.tsx | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/Notifications/NotificationTypes/BidAcceptedNotification.tsx b/src/components/Notifications/NotificationTypes/BidAcceptedNotification.tsx index e3a1b2fb..b186ab44 100644 --- a/src/components/Notifications/NotificationTypes/BidAcceptedNotification.tsx +++ b/src/components/Notifications/NotificationTypes/BidAcceptedNotification.tsx @@ -17,7 +17,7 @@ const i18N = { mana: React.ReactNode, nftName: React.ReactNode ): React.ReactNode => - `Your bid of ${mana} MANA was accepted for ${nftName}`, + <>Your bid of {mana} MANA was accepted for {nftName}, title: 'Bid Accepted' }, es: { @@ -25,14 +25,14 @@ const i18N = { mana: React.ReactNode, nftName: React.ReactNode ): React.ReactNode => - `Tu oferta de ${mana} MANA fue aceptada para ${nftName}`, + <>Tu oferta de {mana} MANA fue aceptada para {nftName}, title: 'Oferta aceptada' }, zh: { description: ( mana: React.ReactNode, nftName: React.ReactNode - ): React.ReactNode => `您的出价 ${mana} MANA 已被接受 ${nftName}`, + ): React.ReactNode => <>您的出价 {mana} MANA 已被接受 {nftName}, title: '接受投标' } } diff --git a/src/components/Notifications/NotificationTypes/BidReceivedNotification.tsx b/src/components/Notifications/NotificationTypes/BidReceivedNotification.tsx index 6376b696..8bcdbf8a 100644 --- a/src/components/Notifications/NotificationTypes/BidReceivedNotification.tsx +++ b/src/components/Notifications/NotificationTypes/BidReceivedNotification.tsx @@ -17,7 +17,7 @@ const i18N = { mana: React.ReactNode, nftName: React.ReactNode ): React.ReactNode => - `Your received a bid of ${mana} MANA was accepted for ${nftName}`, + <>Your received a bid of {mana} MANA was accepted for {nftName}, title: 'Bid Received' }, es: { @@ -25,14 +25,14 @@ const i18N = { mana: React.ReactNode, nftName: React.ReactNode ): React.ReactNode => - `Recibiste una oferta de ${mana} MANA para ${nftName}`, + <>Recibiste una oferta de {mana} MANA para {nftName}, title: 'Oferta aceptada' }, zh: { description: ( mana: React.ReactNode, nftName: React.ReactNode - ): React.ReactNode => `您为 ${nftName} 出价 ${mana} MANA 已被接受`, + ): React.ReactNode => <>您为 {nftName} 出价 {mana} MANA 已被接受, title: '收到的投标' } } From 416e4371a468d1078be3685cd0b51c8967e4dde2 Mon Sep 17 00:00:00 2001 From: lauti7 Date: Tue, 5 Dec 2023 12:39:05 -0300 Subject: [PATCH 11/14] fix: apply suggestions --- .../Notifications/NotificationItem.css | 6 ++-- .../Notifications/NotificationItem.tsx | 8 ++--- .../Notifications/NotificationItemImage.css | 2 +- .../Notifications/NotificationItemImage.tsx | 8 ++--- .../BidAcceptedNotification.tsx | 6 ++-- .../BidReceivedNotification.tsx | 6 ++-- .../ItemSoldNotification.tsx | 6 ++-- .../RoyaltiesEarnedNotification.tsx | 8 ++--- .../Notifications/Notifications.stories.tsx | 26 ++++++++-------- .../Notifications/Notifications.tsx | 18 +++++------ .../Notifications/NotificationsFeed.css | 4 +-- .../Notifications/NotificationsFeed.tsx | 30 ++++++++++--------- 12 files changed, 63 insertions(+), 65 deletions(-) diff --git a/src/components/Notifications/NotificationItem.css b/src/components/Notifications/NotificationItem.css index f3428319..1516d6bc 100644 --- a/src/components/Notifications/NotificationItem.css +++ b/src/components/Notifications/NotificationItem.css @@ -17,18 +17,18 @@ margin-left: 16px; } -.dcl.notification-item__content__title { +.dcl.notification-item__content-title { font-weight: 600; font-size: 14px; margin-bottom: 0; } -.dcl.notification-item__content__description { +.dcl.notification-item__content-description { color: #a09ba8; margin: 4px 0; } -.dcl.notification-item__content__timestamp { +.dcl.notification-item__content-timestamp { font-size: 12px; font-weight: 600; } diff --git a/src/components/Notifications/NotificationItem.tsx b/src/components/Notifications/NotificationItem.tsx index 869690c3..03399f03 100644 --- a/src/components/Notifications/NotificationItem.tsx +++ b/src/components/Notifications/NotificationItem.tsx @@ -27,15 +27,11 @@ export default function NotificationItem({

{children} -

+

{Time(timestamp).fromNow()}

- {isNew && ( - - - - )} + {isNew && ()}
) } diff --git a/src/components/Notifications/NotificationItemImage.css b/src/components/Notifications/NotificationItemImage.css index 7a57cfd6..f5b8080c 100644 --- a/src/components/Notifications/NotificationItemImage.css +++ b/src/components/Notifications/NotificationItemImage.css @@ -1,4 +1,4 @@ -.dcl.notification-image-container { +.dcl.notification-image__container { height: 48px; width: 48px; } diff --git a/src/components/Notifications/NotificationItemImage.tsx b/src/components/Notifications/NotificationItemImage.tsx index 9f42b14a..95ac2bfd 100644 --- a/src/components/Notifications/NotificationItemImage.tsx +++ b/src/components/Notifications/NotificationItemImage.tsx @@ -7,13 +7,13 @@ import './NotificationItemImage.css' export interface NotificationItemImageProps { rarity?: Rarity backgroundColor?: string - imageLink: string + url: string icon?: ReactNode } export default function NotificationItemImage({ rarity, - imageLink, + url, backgroundColor, icon }: NotificationItemImageProps) { @@ -24,14 +24,14 @@ export default function NotificationItemImage({ : null return ( -
+
- Notification Image + Notification Image
{icon && {icon}}
diff --git a/src/components/Notifications/NotificationTypes/BidAcceptedNotification.tsx b/src/components/Notifications/NotificationTypes/BidAcceptedNotification.tsx index b186ab44..6123b05f 100644 --- a/src/components/Notifications/NotificationTypes/BidAcceptedNotification.tsx +++ b/src/components/Notifications/NotificationTypes/BidAcceptedNotification.tsx @@ -44,17 +44,17 @@ const BidAcceptedNotification = ({ return ( }} timestamp={notification.timestamp} isNew={!notification.read} > -

+

{i18N[locale].title}

-

+

{i18N[locale].description( formatMana(notification.metadata.price), diff --git a/src/components/Notifications/NotificationTypes/BidReceivedNotification.tsx b/src/components/Notifications/NotificationTypes/BidReceivedNotification.tsx index 8bcdbf8a..429a1828 100644 --- a/src/components/Notifications/NotificationTypes/BidReceivedNotification.tsx +++ b/src/components/Notifications/NotificationTypes/BidReceivedNotification.tsx @@ -44,17 +44,17 @@ const BidReceivedNotification = ({ return ( }} timestamp={notification.timestamp} isNew={!notification.read} > -

+

{i18N[locale].title}

-

+

{i18N[locale].description( formatMana(notification.metadata.price), diff --git a/src/components/Notifications/NotificationTypes/ItemSoldNotification.tsx b/src/components/Notifications/NotificationTypes/ItemSoldNotification.tsx index 1e94f067..d2dd0df6 100644 --- a/src/components/Notifications/NotificationTypes/ItemSoldNotification.tsx +++ b/src/components/Notifications/NotificationTypes/ItemSoldNotification.tsx @@ -23,17 +23,17 @@ const ItemSoldNotification = ({ return ( }} timestamp={notification.timestamp} isNew={!notification.read} > -

+

{i18N[locale].title}

-

+

{i18N[locale].description}{' '} -

+

{i18N[locale].title}

{locale == 'zh' ? ( -

+

{i18N[locale].description_1} ) : ( -

+

{i18N[locale].description_1} {formatMana(notification.metadata.royaltiesCut)} MANA{' '} {i18N[locale].description_2} diff --git a/src/components/Notifications/Notifications.stories.tsx b/src/components/Notifications/Notifications.stories.tsx index 8ac81749..3f0aeebd 100644 --- a/src/components/Notifications/Notifications.stories.tsx +++ b/src/components/Notifications/Notifications.stories.tsx @@ -14,11 +14,11 @@ storiesOf('Notifications Toggle', module) isOpen={false} isLoading={false} isOnboarding={false} - userNotifications={[]} + items={[]} locale="en" activeTab="newest" onChangeTab={(e, newTab) => console.log(newTab)} - onClickToggle={() => console.log('Toggle button')} + onClick={() => console.log('Toggle button')} onBegin={() => console.log('Begin')} />

@@ -31,7 +31,7 @@ storiesOf('Notifications Toggle', module) isOpen={false} isLoading={false} isOnboarding={false} - userNotifications={[ + items={[ { id: 'A', read: false, @@ -55,7 +55,7 @@ storiesOf('Notifications Toggle', module) locale="en" activeTab="newest" onChangeTab={(e, newTab) => console.log(newTab)} - onClickToggle={() => console.log('Toggle button')} + onClick={() => console.log('Toggle button')} onBegin={() => console.log('Begin')} /> @@ -68,7 +68,7 @@ storiesOf('Notifications Toggle', module) isOpen isLoading={false} isOnboarding - userNotifications={[ + items={[ { id: 'A', read: false, @@ -92,7 +92,7 @@ storiesOf('Notifications Toggle', module) activeTab="newest" onChangeTab={(e, newTab) => console.log(newTab)} locale="en" - onClickToggle={() => console.log('Toggle button')} + onClick={() => console.log('Toggle button')} onBegin={() => console.log('Begin')} /> @@ -107,7 +107,7 @@ storiesOf('Notifications Toggle', module) isLoading={false} isOnboarding={false} locale="en" - userNotifications={[ + items={[ { id: 'B', read: false, @@ -190,7 +190,7 @@ storiesOf('Notifications Toggle', module) ]} activeTab={tab as ActiveTab} onChangeTab={(e, newTab) => setTab(newTab)} - onClickToggle={() => console.log('Toggle button')} + onClick={() => console.log('Toggle button')} onBegin={() => console.log('Begin')} /> @@ -204,10 +204,10 @@ storiesOf('Notifications Toggle', module) isLoading={false} isOnboarding={false} locale="en" - userNotifications={[]} + items={[]} activeTab="newest" onChangeTab={(e, newTab) => console.log(newTab)} - onClickToggle={() => console.log('Toggle button')} + onClick={() => console.log('Toggle button')} onBegin={() => console.log('Begin')} /> @@ -221,10 +221,10 @@ storiesOf('Notifications Toggle', module) isLoading={true} isOnboarding={false} locale="en" - userNotifications={[]} + items={[]} activeTab="newest" onChangeTab={(e, newTab) => console.log(newTab)} - onClickToggle={() => console.log('Toggle button')} + onClick={() => console.log('Toggle button')} onBegin={() => console.log('Begin')} /> @@ -235,7 +235,7 @@ storiesOf('Notifications Toggle', module)
} />
diff --git a/src/components/Notifications/Notifications.tsx b/src/components/Notifications/Notifications.tsx index 94d92c52..198b18fb 100644 --- a/src/components/Notifications/Notifications.tsx +++ b/src/components/Notifications/Notifications.tsx @@ -10,12 +10,12 @@ import './Notifications.css' export interface NotificationsProps { isOpen: boolean - userNotifications: DCLNotification[] + items: DCLNotification[] isLoading: boolean locale: NotificationLocale isOnboarding: boolean activeTab: ActiveTab - onClickToggle: (e: React.MouseEvent) => void + onClick: (e: React.MouseEvent) => void onChangeTab: ( e: React.MouseEvent, newActiveTab: ActiveTab @@ -25,34 +25,34 @@ export interface NotificationsProps { export default function Notifications({ isOpen, - userNotifications, + items, isLoading, locale, isOnboarding, activeTab, - onClickToggle, + onClick, onChangeTab, onBegin }: NotificationsProps) { - const unseenNotifications = userNotifications.filter( + const newNotificationsCount = items.filter( (notification) => !notification.read ).length return (
- - {!isOpen && unseenNotifications > 0 && ( + {!isOpen && newNotificationsCount > 0 && (
- {unseenNotifications > 9 ? '9+' : unseenNotifications} + {newNotificationsCount > 9 ? '9+' : newNotificationsCount}
)}
{isOpen && ( userNotifications.filter((notification) => !notification.read), - [userNotifications] + () => items.filter((notification) => !notification.read), + [items] ) const previousNotifications = useMemo( () => - userNotifications.filter((notification) => { + items.filter((notification) => { if (!notification.read) return false const diff = Time(notification.timestamp).diff(new Date(), 'hour') @@ -143,17 +145,17 @@ export default function NotificationsFeed({ return true } }), - [userNotifications] + [items] ) const readNotifications = useMemo( () => - userNotifications.filter( + items.filter( (notification) => notification.read && !previousNotifications.find(({ id }) => id === notification.id) ), - [userNotifications] + [items] ) if (isOnboarding) { @@ -162,10 +164,10 @@ export default function NotificationsFeed({
-

+

{i18N[locale].onboarding.title}

-

+

{i18N[locale].onboarding.description}

@@ -266,10 +268,10 @@ export default function NotificationsFeed({ const NoNotifications = ({ locale }: { locale: NotificationLocale }) => (
-

+

{i18N[locale].feed.empty.title}

-

+

{i18N[locale].feed.empty.description}

From 00ee6e3427e35719de3b93086411750ed8be9302 Mon Sep 17 00:00:00 2001 From: lauti7 Date: Tue, 5 Dec 2023 12:39:29 -0300 Subject: [PATCH 12/14] fix: prettier --- .../Notifications/NotificationItem.tsx | 2 +- .../BidAcceptedNotification.tsx | 20 ++++++++++++++----- .../BidReceivedNotification.tsx | 20 ++++++++++++++----- .../Notifications/NotificationsFeed.tsx | 1 - 4 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/components/Notifications/NotificationItem.tsx b/src/components/Notifications/NotificationItem.tsx index 03399f03..60bfd8f9 100644 --- a/src/components/Notifications/NotificationItem.tsx +++ b/src/components/Notifications/NotificationItem.tsx @@ -31,7 +31,7 @@ export default function NotificationItem({ {Time(timestamp).fromNow()}

- {isNew && ()} + {isNew && }
) } diff --git a/src/components/Notifications/NotificationTypes/BidAcceptedNotification.tsx b/src/components/Notifications/NotificationTypes/BidAcceptedNotification.tsx index 6123b05f..72794c09 100644 --- a/src/components/Notifications/NotificationTypes/BidAcceptedNotification.tsx +++ b/src/components/Notifications/NotificationTypes/BidAcceptedNotification.tsx @@ -16,23 +16,33 @@ const i18N = { description: ( mana: React.ReactNode, nftName: React.ReactNode - ): React.ReactNode => - <>Your bid of {mana} MANA was accepted for {nftName}, + ): React.ReactNode => ( + <> + Your bid of {mana} MANA was accepted for {nftName} + + ), title: 'Bid Accepted' }, es: { description: ( mana: React.ReactNode, nftName: React.ReactNode - ): React.ReactNode => - <>Tu oferta de {mana} MANA fue aceptada para {nftName}, + ): React.ReactNode => ( + <> + Tu oferta de {mana} MANA fue aceptada para {nftName} + + ), title: 'Oferta aceptada' }, zh: { description: ( mana: React.ReactNode, nftName: React.ReactNode - ): React.ReactNode => <>您的出价 {mana} MANA 已被接受 {nftName}, + ): React.ReactNode => ( + <> + 您的出价 {mana} MANA 已被接受 {nftName} + + ), title: '接受投标' } } diff --git a/src/components/Notifications/NotificationTypes/BidReceivedNotification.tsx b/src/components/Notifications/NotificationTypes/BidReceivedNotification.tsx index 429a1828..f9ccc71e 100644 --- a/src/components/Notifications/NotificationTypes/BidReceivedNotification.tsx +++ b/src/components/Notifications/NotificationTypes/BidReceivedNotification.tsx @@ -16,23 +16,33 @@ const i18N = { description: ( mana: React.ReactNode, nftName: React.ReactNode - ): React.ReactNode => - <>Your received a bid of {mana} MANA was accepted for {nftName}, + ): React.ReactNode => ( + <> + Your received a bid of {mana} MANA was accepted for {nftName} + + ), title: 'Bid Received' }, es: { description: ( mana: React.ReactNode, nftName: React.ReactNode - ): React.ReactNode => - <>Recibiste una oferta de {mana} MANA para {nftName}, + ): React.ReactNode => ( + <> + Recibiste una oferta de {mana} MANA para {nftName} + + ), title: 'Oferta aceptada' }, zh: { description: ( mana: React.ReactNode, nftName: React.ReactNode - ): React.ReactNode => <>您为 {nftName} 出价 {mana} MANA 已被接受, + ): React.ReactNode => ( + <> + 您为 {nftName} 出价 {mana} MANA 已被接受 + + ), title: '收到的投标' } } diff --git a/src/components/Notifications/NotificationsFeed.tsx b/src/components/Notifications/NotificationsFeed.tsx index b3b4a154..424985d6 100644 --- a/src/components/Notifications/NotificationsFeed.tsx +++ b/src/components/Notifications/NotificationsFeed.tsx @@ -14,7 +14,6 @@ import Time from '../../lib/time' import './NotificationsFeed.css' - interface NotificationsFeedProps { items: DCLNotification[] isLoading: boolean From 4f031b2863010e89a7a8c46c786ae4c51bdabca0 Mon Sep 17 00:00:00 2001 From: lauti7 Date: Tue, 5 Dec 2023 12:41:05 -0300 Subject: [PATCH 13/14] fix: use memo --- src/components/Notifications/Notifications.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/Notifications/Notifications.tsx b/src/components/Notifications/Notifications.tsx index 198b18fb..6a74c5d1 100644 --- a/src/components/Notifications/Notifications.tsx +++ b/src/components/Notifications/Notifications.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useMemo } from 'react' import NotificationsFeed from './NotificationsFeed' import { ActiveTab, DCLNotification, NotificationLocale } from './types' @@ -34,9 +34,9 @@ export default function Notifications({ onChangeTab, onBegin }: NotificationsProps) { - const newNotificationsCount = items.filter( - (notification) => !notification.read - ).length + const newNotificationsCount = useMemo(() => { + return items.filter((notification) => !notification.read).length + }, [items]) return (
From b490933e36b74482f0a61fe87475d78176f8cb89 Mon Sep 17 00:00:00 2001 From: lauti7 Date: Tue, 5 Dec 2023 12:47:20 -0300 Subject: [PATCH 14/14] fix: loading status --- src/components/Notifications/NotificationsFeed.css | 4 ++++ src/components/Notifications/NotificationsFeed.tsx | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/Notifications/NotificationsFeed.css b/src/components/Notifications/NotificationsFeed.css index 63040c47..1ca55aef 100644 --- a/src/components/Notifications/NotificationsFeed.css +++ b/src/components/Notifications/NotificationsFeed.css @@ -25,6 +25,10 @@ box-shadow: -1px -1px 0 0 var(--background); } +.dcl.notifications-feed__loader { + height: 290px; +} + .dcl.notifications-feed__content > .dcl.tabs { margin-bottom: 0; height: auto; diff --git a/src/components/Notifications/NotificationsFeed.tsx b/src/components/Notifications/NotificationsFeed.tsx index 424985d6..1f628410 100644 --- a/src/components/Notifications/NotificationsFeed.tsx +++ b/src/components/Notifications/NotificationsFeed.tsx @@ -259,7 +259,11 @@ export default function NotificationsFeed({
)} - {isLoading && } + {isLoading && ( +
+ +
+ )}
) }