From d24c4f6f73cd94dd40782f4a9a3f022449e80ef0 Mon Sep 17 00:00:00 2001 From: Bonn Date: Tue, 9 Apr 2024 11:45:43 +0700 Subject: [PATCH 001/300] chore: ASC-00000 - css module typescript config (#233) --- .vscode/settings.json | 3 + package.json | 1 + pnpm-lock.yaml | 271 ++++++++++++++++++++++++++++++++++++++++++ src/styles.d.ts | 4 - tsconfig.json | 9 +- 5 files changed, 282 insertions(+), 6 deletions(-) create mode 100644 .vscode/settings.json delete mode 100644 src/styles.d.ts diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..25fa6215f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "typescript.tsdk": "node_modules/typescript/lib" +} diff --git a/package.json b/package.json index a799c44d9..347b6c8ca 100644 --- a/package.json +++ b/package.json @@ -90,6 +90,7 @@ "ts-jest": "^29.1.1", "tsup": "^7.3.0", "typescript": "^4.9.5", + "typescript-plugin-css-modules": "^5.1.0", "vite": "^4.5.1", "vite-tsconfig-paths": "^4.2.3" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8df1e9296..e141b03a3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -253,6 +253,9 @@ devDependencies: typescript: specifier: ^4.9.5 version: 4.9.5 + typescript-plugin-css-modules: + specifier: ^5.1.0 + version: 5.1.0(typescript@4.9.5) vite: specifier: ^4.5.1 version: 4.5.1 @@ -267,6 +270,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /@adobe/css-tools@4.3.3: + resolution: {integrity: sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==} + dev: true + /@amityco/ts-sdk@6.20.1-615c591.0: resolution: {integrity: sha512-6HThI+wBUTKeHFbwbycrt3dk9WN9gnJiqJwqNRXiZD82svRQ7m9pdLKeahNLpKMEtZec8LybCEwOkbBcP5PFUw==} engines: {node: '>=12', npm: '>=6'} @@ -4310,6 +4317,18 @@ packages: resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} dev: true + /@types/postcss-modules-local-by-default@4.0.2: + resolution: {integrity: sha512-CtYCcD+L+trB3reJPny+bKWKMzPfxEyQpKIwit7kErnOexf5/faaGpkFy4I5AwbV4hp1sk7/aTg0tt0B67VkLQ==} + dependencies: + postcss: 8.4.38 + dev: true + + /@types/postcss-modules-scope@3.0.4: + resolution: {integrity: sha512-//ygSisVq9kVI0sqx3UPLzWIMCmtSVrzdljtuaAEJtGoGnpjBikZ2sXO5MpH9SnWX9HRfXxHifDAXcQjupWnIQ==} + dependencies: + postcss: 8.4.38 + dev: true + /@types/pretty-hrtime@1.0.3: resolution: {integrity: sha512-nj39q0wAIdhwn7DGUyT9irmsKK1tV0bd5WFEhgpqNTMFZ8cE+jieuTphCW0tfdm47S2zVT5mr09B28b1chmQMA==} dev: true @@ -5955,6 +5974,12 @@ packages: engines: {node: '>= 0.6'} dev: true + /copy-anything@2.0.6: + resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==} + dependencies: + is-what: 3.14.1 + dev: true + /copy-to-clipboard@3.3.3: resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==} dependencies: @@ -6031,6 +6056,12 @@ packages: source-map: 0.6.1 dev: false + /cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + dev: true + /csstype@3.1.2: resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} dev: true @@ -6272,6 +6303,11 @@ packages: engines: {node: '>=12'} dev: true + /dotenv@16.4.5: + resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} + engines: {node: '>=12'} + dev: true + /dotgitignore@2.1.0: resolution: {integrity: sha512-sCm11ak2oY6DglEPpCB8TixLjWAxd3kJTs6UIcSasNYxXdFPV+YKlye92c8H4kKFqV5qYMIh7d+cYecEg0dIkA==} engines: {node: '>=6'} @@ -6396,6 +6432,15 @@ packages: hasBin: true dev: true + /errno@0.1.8: + resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==} + hasBin: true + requiresBuild: true + dependencies: + prr: 1.0.1 + dev: true + optional: true + /error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: @@ -7698,6 +7743,24 @@ packages: safer-buffer: 2.1.2 dev: true + /iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + requiresBuild: true + dependencies: + safer-buffer: 2.1.2 + dev: true + optional: true + + /icss-utils@5.1.0(postcss@8.4.38): + resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + dependencies: + postcss: 8.4.38 + dev: true + /ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} dev: true @@ -7707,6 +7770,18 @@ packages: engines: {node: '>= 4'} dev: true + /image-size@0.5.5: + resolution: {integrity: sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==} + engines: {node: '>=0.10.0'} + hasBin: true + requiresBuild: true + dev: true + optional: true + + /immutable@4.3.5: + resolution: {integrity: sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==} + dev: true + /import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} @@ -8029,6 +8104,10 @@ packages: call-bind: 1.0.5 dev: true + /is-what@3.14.1: + resolution: {integrity: sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==} + dev: true + /is-wsl@2.2.0: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} @@ -8710,6 +8789,24 @@ packages: dotenv-expand: 10.0.0 dev: true + /less@4.2.0: + resolution: {integrity: sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==} + engines: {node: '>=6'} + hasBin: true + dependencies: + copy-anything: 2.0.6 + parse-node-version: 1.0.1 + tslib: 2.6.2 + optionalDependencies: + errno: 0.1.8 + graceful-fs: 4.2.11 + image-size: 0.5.5 + make-dir: 2.1.0 + mime: 1.6.0 + needle: 3.3.1 + source-map: 0.6.1 + dev: true + /leven@2.1.0: resolution: {integrity: sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==} engines: {node: '>=0.10.0'} @@ -8859,6 +8956,10 @@ packages: p-locate: 5.0.0 dev: true + /lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + dev: true + /lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} dev: true @@ -9288,6 +9389,17 @@ packages: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true + /needle@3.3.1: + resolution: {integrity: sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==} + engines: {node: '>= 4.4.x'} + hasBin: true + requiresBuild: true + dependencies: + iconv-lite: 0.6.3 + sax: 1.3.0 + dev: true + optional: true + /negotiator@0.6.3: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} @@ -9625,6 +9737,11 @@ packages: lines-and-columns: 1.2.4 dev: true + /parse-node-version@1.0.1: + resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==} + engines: {node: '>= 0.10'} + dev: true + /parseqs@0.0.5: resolution: {integrity: sha512-B3Nrjw2aL7aI4TDujOzfA4NsEc4u1lVcIRE0xesutH8kjeWF70uk+W5cBlIQx04zUH9NTBvuN36Y9xLRPK6Jjw==} dependencies: @@ -9739,6 +9856,7 @@ packages: /pify@4.0.1: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} + requiresBuild: true dev: true /pirates@4.0.6: @@ -9773,6 +9891,23 @@ packages: dependencies: '@babel/runtime': 7.23.7 + /postcss-load-config@3.1.4(postcss@8.4.38): + resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} + engines: {node: '>= 10'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + dependencies: + lilconfig: 2.1.0 + postcss: 8.4.38 + yaml: 1.10.2 + dev: true + /postcss-load-config@4.0.2(postcss@8.4.33): resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} engines: {node: '>= 14'} @@ -9790,6 +9925,45 @@ packages: yaml: 2.3.4 dev: true + /postcss-modules-extract-imports@3.1.0(postcss@8.4.38): + resolution: {integrity: sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + dependencies: + postcss: 8.4.38 + dev: true + + /postcss-modules-local-by-default@4.0.5(postcss@8.4.38): + resolution: {integrity: sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + dependencies: + icss-utils: 5.1.0(postcss@8.4.38) + postcss: 8.4.38 + postcss-selector-parser: 6.0.16 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-modules-scope@3.2.0(postcss@8.4.38): + resolution: {integrity: sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + dependencies: + postcss: 8.4.38 + postcss-selector-parser: 6.0.16 + dev: true + + /postcss-selector-parser@6.0.16: + resolution: {integrity: sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==} + engines: {node: '>=4'} + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + dev: true + /postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} dev: true @@ -9812,6 +9986,15 @@ packages: source-map-js: 1.0.2 dev: true + /postcss@8.4.38: + resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.7 + picocolors: 1.0.0 + source-map-js: 1.2.0 + dev: true + /prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -9884,6 +10067,12 @@ packages: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} dev: true + /prr@1.0.1: + resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} + requiresBuild: true + dev: true + optional: true + /pump@2.0.1: resolution: {integrity: sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==} dependencies: @@ -10479,6 +10668,10 @@ packages: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} + /reserved-words@0.1.2: + resolution: {integrity: sha512-0S5SrIUJ9LfpbVl4Yzij6VipUdafHrOTzvmfazSw/jeZrZtQK303OPZW+obtkaw7jQlTQppy0UvZWm9872PbRw==} + dev: true + /resize-observer-polyfill@1.5.1: resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} dev: false @@ -10637,6 +10830,20 @@ packages: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} dev: true + /sass@1.74.1: + resolution: {integrity: sha512-w0Z9p/rWZWelb88ISOLyvqTWGmtmu2QJICqDBGyNnfG4OUnPX9BBjjYIXUpXCMOOg5MQWNpqzt876la1fsTvUA==} + engines: {node: '>=14.0.0'} + hasBin: true + dependencies: + chokidar: 3.5.3 + immutable: 4.3.5 + source-map-js: 1.0.2 + dev: true + + /sax@1.3.0: + resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==} + dev: true + /scheduler@0.23.0: resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} dependencies: @@ -10842,6 +11049,11 @@ packages: engines: {node: '>=0.10.0'} dev: true + /source-map-js@1.2.0: + resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} + engines: {node: '>=0.10.0'} + dev: true + /source-map-support@0.5.13: resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} dependencies: @@ -10865,6 +11077,11 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + /source-map@0.7.4: + resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} + engines: {node: '>= 8'} + dev: true + /source-map@0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} @@ -11144,6 +11361,19 @@ packages: /stylis@4.3.1: resolution: {integrity: sha512-EQepAV+wMsIaGVGX1RECzgrcqRRU/0sYOHkeLsZ3fzHaHXZy4DaOOX0vOlGQdlsjkh3mFHAIlVimpwAs4dslyQ==} + /stylus@0.62.0: + resolution: {integrity: sha512-v3YCf31atbwJQIMtPNX8hcQ+okD4NQaTuKGUWfII8eaqn+3otrbttGL1zSMZAAtiPsBztQnujVBugg/cXFUpyg==} + hasBin: true + dependencies: + '@adobe/css-tools': 4.3.3 + debug: 4.3.4 + glob: 7.2.3 + sax: 1.3.0 + source-map: 0.7.4 + transitivePeerDependencies: + - supports-color + dev: true + /substyle@9.4.1(react@18.2.0): resolution: {integrity: sha512-VOngeq/W1/UkxiGzeqVvDbGDPM8XgUyJVWjrqeh+GgKqspEPiLYndK+XRcsKUHM5Muz/++1ctJ1QCF/OqRiKWA==} peerDependencies: @@ -11505,6 +11735,15 @@ packages: strip-bom: 3.0.0 dev: true + /tsconfig-paths@4.2.0: + resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} + engines: {node: '>=6'} + dependencies: + json5: 2.2.3 + minimist: 1.2.8 + strip-bom: 3.0.0 + dev: true + /tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} dev: true @@ -11666,6 +11905,33 @@ packages: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} dev: true + /typescript-plugin-css-modules@5.1.0(typescript@4.9.5): + resolution: {integrity: sha512-6h+sLBa4l+XYSTn/31vZHd/1c3SvAbLpobY6FxDiUOHJQG1eD9Gh3eCs12+Eqc+TCOAdxcO+zAPvUq0jBfdciw==} + peerDependencies: + typescript: '>=4.0.0' + dependencies: + '@types/postcss-modules-local-by-default': 4.0.2 + '@types/postcss-modules-scope': 3.0.4 + dotenv: 16.4.5 + icss-utils: 5.1.0(postcss@8.4.38) + less: 4.2.0 + lodash.camelcase: 4.3.0 + postcss: 8.4.38 + postcss-load-config: 3.1.4(postcss@8.4.38) + postcss-modules-extract-imports: 3.1.0(postcss@8.4.38) + postcss-modules-local-by-default: 4.0.5(postcss@8.4.38) + postcss-modules-scope: 3.2.0(postcss@8.4.38) + reserved-words: 0.1.2 + sass: 1.74.1 + source-map-js: 1.0.2 + stylus: 0.62.0 + tsconfig-paths: 4.2.0 + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + - ts-node + dev: true + /typescript@4.9.5: resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} engines: {node: '>=4.2.0'} @@ -12203,6 +12469,11 @@ packages: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} dev: true + /yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + dev: true + /yaml@2.3.1: resolution: {integrity: sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==} engines: {node: '>= 14'} diff --git a/src/styles.d.ts b/src/styles.d.ts deleted file mode 100644 index 3d673e2eb..000000000 --- a/src/styles.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module '*.module.css' { - const classes: { [key: string]: string }; - export default classes; -} diff --git a/tsconfig.json b/tsconfig.json index 1f99a563b..28a37ad95 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -102,8 +102,13 @@ /* Completeness */ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ "skipLibCheck": true /* Skip type checking all .d.ts files. */, - "allowJs": true + "allowJs": true, + "plugins": [ + { + "name": "typescript-plugin-css-modules" + } + ] }, - "include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "src/global.d.ts"], + "include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "src/global.d.ts", "**/*.module.css"], "exclude": ["node_modules", "vite.config.ts"] } From 54c354eacabf8b05cc7e60898b2a772995a27624 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Tue, 9 Apr 2024 13:17:10 +0700 Subject: [PATCH 002/300] fix: ASC-21254 - align api signature for draft page (#210) * fix: can't upload story * fix: story image renderer * fix: align comment tray component props * fix: align story tab component props * fix: change export name to align signature api * fix: ddraft page * fix: align signature api * fix: fill color --- src/index.ts | 2 +- src/social/components/Comment/index.tsx | 2 +- .../CommunityInfo/UICommunityInfo.tsx | 2 + src/social/components/CommunityInfo/index.tsx | 7 +- src/social/pages/Application/index.tsx | 77 ++++++++------- src/social/pages/CommunityFeed/index.tsx | 81 ++-------------- .../v4/components/CommentTray/CommentTray.tsx | 32 ++++--- .../v4/components/CommentTray/sdk.stories.tsx | 69 ++++++++++++++ .../v4/components/StoryTab/StoryTab.tsx | 90 +++++++++++------- .../v4/internal-components/Comment/index.tsx | 20 ++-- .../v4/internal-components/Comment/styles.tsx | 1 + .../CommentComposeBar/CommentComposeBar.tsx | 11 +-- .../CommentList/CommentList.tsx | 6 +- .../StoryCommentComposeBar.tsx | 15 +-- .../StoryViewer/Renderers/Image.tsx | 16 ++-- .../StoryViewer/Renderers/Video.tsx | 4 +- .../internal-components/StoryViewer/index.tsx | 10 +- src/social/v4/pages/DraftsPage/DraftsPage.tsx | 94 +++++++++++-------- src/v4/social/providers/StoryProvider.tsx | 28 ++++++ 19 files changed, 332 insertions(+), 235 deletions(-) create mode 100644 src/social/v4/components/CommentTray/sdk.stories.tsx create mode 100644 src/v4/social/providers/StoryProvider.tsx diff --git a/src/index.ts b/src/index.ts index 25fbb1455..d7176e60c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,7 +20,7 @@ export { useSDK as useAmitySDK } from '~/core/hooks/useSDK'; // v4 export { - DraftsPage as AmityCreateStoryPage, + DraftsPage as AmityDraftStoryPage, StoryPage as AmityViewStoryPage, } from '~/social/v4/pages'; export { diff --git a/src/social/components/Comment/index.tsx b/src/social/components/Comment/index.tsx index 8a6ecb9e1..8b7d1847e 100644 --- a/src/social/components/Comment/index.tsx +++ b/src/social/components/Comment/index.tsx @@ -37,7 +37,7 @@ import { useCustomComponent } from '~/core/providers/CustomComponentsProvider'; import useCommentFlaggedByMe from '~/social/hooks/useCommentFlaggedByMe'; import useCommentPermission from '~/social/hooks/useCommentPermission'; import useCommentSubscription from '~/social/hooks/useCommentSubscription'; -import useStory from '~/social/hooks/useStory'; + import { ERROR_RESPONSE } from '~/social/constants'; const REPLIES_PER_PAGE = 5; diff --git a/src/social/components/CommunityInfo/UICommunityInfo.tsx b/src/social/components/CommunityInfo/UICommunityInfo.tsx index 68dc74926..a619e88f5 100644 --- a/src/social/components/CommunityInfo/UICommunityInfo.tsx +++ b/src/social/components/CommunityInfo/UICommunityInfo.tsx @@ -27,6 +27,7 @@ import { useCustomComponent } from '~/core/providers/CustomComponentsProvider'; import millify from 'millify'; import { isNonNullable } from '~/helpers/utils'; import { StoryTab } from '~/social/v4/components/StoryTab'; +import { AmityStoryTabComponentType } from '~/social/v4/components/StoryTab/StoryTab'; interface UICommunityInfoProps { communityId: string; @@ -157,6 +158,7 @@ const UICommunityInfo = ({ )} >; stories: (Amity.Story | undefined)[]; } -const CommunityInfo = ({ communityId, setStoryFile, stories }: CommunityInfoProps) => { +const CommunityInfo = ({ communityId, stories }: CommunityInfoProps) => { + const { setFile } = useStoryContext(); const haveStories = stories?.length > 0; const isStorySyncing = haveStories && stories.some((story) => story?.syncState === 'syncing'); const isStoryErrored = haveStories && stories.some((story) => story?.syncState === 'error'); @@ -72,7 +73,7 @@ const CommunityInfo = ({ communityId, setStoryFile, stories }: CommunityInfoProp onOk: () => leaveCommunity(), }) } - setStoryFile={setStoryFile} + setStoryFile={setFile} haveStories={haveStories || false} haveStoryPermission={haveStoryPermission} isStorySyncing={isStorySyncing || false} diff --git a/src/social/pages/Application/index.tsx b/src/social/pages/Application/index.tsx index a990431fe..68d4ca6c8 100644 --- a/src/social/pages/Application/index.tsx +++ b/src/social/pages/Application/index.tsx @@ -18,6 +18,7 @@ import { useNavigation } from '~/social/providers/NavigationProvider'; import useSDK from '~/core/hooks/useSDK'; import { ViewStoriesPage } from '~/social/v4/pages/StoryPage'; import { usePageBehavior } from '~/social/v4/providers/PageBehaviorProvider'; +import { StoryProvider } from '~/v4/social/providers/StoryProvider'; const ApplicationContainer = styled.div` height: 100%; @@ -69,42 +70,46 @@ const Community = () => { }, [client]); return ( - - }> - {page.type === PageTypes.Explore && } - - {page.type === PageTypes.NewsFeed && } - - {page.type === PageTypes.CommunityFeed && ( - - )} - - {page.type === PageTypes.ViewStory && ( - - - - )} - - {page.type === PageTypes.CommunityEdit && ( - - )} - - {page.type === PageTypes.Category && ( - - )} - - {page.type === PageTypes.UserFeed && ( - - )} - - {page.type === PageTypes.UserEdit && } - - + + + }> + {page.type === PageTypes.Explore && } + + {page.type === PageTypes.NewsFeed && ( + + )} + + {page.type === PageTypes.CommunityFeed && ( + + )} + + {page.type === PageTypes.ViewStory && ( + + + + )} + + {page.type === PageTypes.CommunityEdit && ( + + )} + + {page.type === PageTypes.Category && ( + + )} + + {page.type === PageTypes.UserFeed && ( + + )} + + {page.type === PageTypes.UserEdit && } + + + ); }; diff --git a/src/social/pages/CommunityFeed/index.tsx b/src/social/pages/CommunityFeed/index.tsx index 7d5dd4bab..1ce42171b 100644 --- a/src/social/pages/CommunityFeed/index.tsx +++ b/src/social/pages/CommunityFeed/index.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState, useMemo } from 'react'; -import { StoryRepository, SubscriptionLevels } from '@amityco/ts-sdk'; +import { SubscriptionLevels } from '@amityco/ts-sdk'; import { FormattedMessage } from 'react-intl'; import CommunityCreatedModal from '~/social/components/CommunityCreatedModal'; @@ -20,7 +20,7 @@ import useCommunitySubscription from '~/social/hooks/useCommunitySubscription'; import usePostsCollection from '~/social/hooks/collections/usePostsCollection'; import useFile from '~/core/hooks/useFile'; -import { notification } from '~/core/components/Notification'; + import { CommunitySideMenuOverlay, HeadTitle, @@ -31,6 +31,7 @@ import useStories from '~/social/hooks/useStories'; import { BarsIcon } from '~/icons'; import { DraftsPage } from '~/social/v4/pages/DraftsPage'; +import { useStoryContext } from '~/v4/social/providers/StoryProvider'; interface CommunityFeedProps { communityId: string; @@ -40,6 +41,7 @@ interface CommunityFeedProps { } const CommunityFeed = ({ communityId, isNewCommunity, isOpen, toggleOpen }: CommunityFeedProps) => { + const { file } = useStoryContext(); const { stories } = useStories({ targetId: communityId, targetType: 'community', @@ -79,84 +81,19 @@ const CommunityFeed = ({ communityId, isNewCommunity, isOpen, toggleOpen }: Comm const [isCreatedModalOpened, setCreatedModalOpened] = useState(isNewCommunity); - const [file, setFile] = useState(null); - const [isDraft, setIsDraft] = useState(false); - - const createStory = async ( - file: File, - imageMode: 'fit' | 'fill', - metadata: Amity.Metadata | undefined, - items: Amity.StoryItem[] | undefined = [], - ) => { - try { - const formData = new FormData(); - formData.append('files', file); - if (file?.type.includes('image')) { - setFile(null); - const { data: imageData } = await StoryRepository.createImageStory( - 'community', - communityId, - formData, - metadata, - imageMode, - items, - ); - if (imageData) { - notification.success({ - content: , - }); - } - } else { - setFile(null); - const { data: videoData } = await StoryRepository.createVideoStory( - 'community', - communityId, - formData, - metadata, - items, - ); - if (videoData) { - notification.success({ - content: , - }); - } - } - } catch (error) { - notification.info({ - content: , - }); - } - }; - - const discardStory = () => { - setFile(null); - setIsDraft(false); - }; - useEffect(() => { if (!tabs.find((tab) => tab.value === activeTab)) { setActiveTab(tabs[0].value); } }, [activeTab, tabs]); - useEffect(() => { - if (file) { - setIsDraft(true); - } - if (!file && isDraft) { - setIsDraft(false); - } - }, [file]); - - if (isDraft && file) { + if (file) { return ( ); @@ -172,7 +109,7 @@ const CommunityFeed = ({ communityId, isNewCommunity, isOpen, toggleOpen }: Comm - + void; onClickReply: ( replyTo?: string, @@ -44,17 +45,19 @@ interface CommentTrayProps { } export const CommentTray = ({ + referenceType, + referenceId, + community, + shouldAllowInteraction = true, + shouldAllowCreation = true, pageId = '*', storyId, commentId, - referenceType, - referenceId, - limit = REPLIES_PER_PAGE, replyTo, - isJoined, - isOpen, isReplying, - allowCommentInStory, + limit = REPLIES_PER_PAGE, + isOpen, + isJoined, onClose, onClickReply, onCancelReply, @@ -88,6 +91,7 @@ export const CommentTray = ({ backgroundColor: primaryColor, borderTopLeftRadius: '1rem', borderTopRightRadius: '1rem', + borderBottom: 'none', }} /> = { + title: 'Social/CommentTray', + component: CommentTray, + args: { + referenceType: 'post', + referenceId: 'post123', + community: { + communityId: 'community123', + displayName: 'Community 123', + isPublic: true, + isJoined: true, + }, + shouldAllowInteraction: true, + shouldAllowCreation: true, + pageId: '*', + storyId: 'story123', + isJoined: true, + allowCommentInStory: true, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + isOpen: true, + }, +}; + +export const WithReply: Story = { + args: { + isOpen: true, + isReplying: true, + replyTo: 'comment456', + }, +}; + +export const WithLimit: Story = { + args: { + isOpen: true, + limit: 10, + }, +}; + +export const Closed: Story = { + args: { + isOpen: false, + }, +}; + +export const WithComment: Story = { + render: (args) => { + // const {comments,isLoading} = useCommentsCollection({ + // referenceType:"story", + // referenceId:"660a3b7fc5d2301929cff8ce" + // }); + + return ; + }, + args: { + isOpen: true, + }, +}; diff --git a/src/social/v4/components/StoryTab/StoryTab.tsx b/src/social/v4/components/StoryTab/StoryTab.tsx index 08324db2a..79c2791fd 100644 --- a/src/social/v4/components/StoryTab/StoryTab.tsx +++ b/src/social/v4/components/StoryTab/StoryTab.tsx @@ -14,7 +14,13 @@ import { import StoryRing from './StoryRing'; import { notification } from '~/core/components/Notification'; +export enum AmityStoryTabComponentType { + CommunityFeed = 'communityFeed', + GlobalFeed = 'globalFeed', +} + interface StoryTabProps { + type: AmityStoryTabComponentType; haveStoryPermission: boolean; avatar: string | null; pageId?: string; @@ -31,19 +37,19 @@ interface StoryTabProps { onChange?: (file: File | null) => void; } -export const StoryTab = ({ +export const StoryTab: React.FC = ({ + type, + haveStoryPermission, + avatar, pageId = '*', - elementId = '*', title = 'Story', - haveStoryPermission = false, storyRing = false, isSeen = false, uploadingStory = false, isErrored = false, - avatar, onClick, onChange, -}: StoryTabProps) => { +}) => { const fileInputRef = useRef(null); const handleAddIconClick = () => { @@ -71,34 +77,48 @@ export const StoryTab = ({ }); }, []); - return ( - - - {storyRing && ( - - )} - - {haveStoryPermission && ( - <> - - - - )} - {isErrored && } - - - {title} - - - ); + const renderStoryTab = () => { + switch (type) { + case AmityStoryTabComponentType.CommunityFeed: + return ( + + + {storyRing && ( + + )} + + {haveStoryPermission && ( + <> + + + + )} + {isErrored && } + + + {title} + + + ); + + default: + return null; + } + }; + + return renderStoryTab(); }; diff --git a/src/social/v4/internal-components/Comment/index.tsx b/src/social/v4/internal-components/Comment/index.tsx index ea09d9aae..f666e6d90 100644 --- a/src/social/v4/internal-components/Comment/index.tsx +++ b/src/social/v4/internal-components/Comment/index.tsx @@ -50,7 +50,7 @@ import UIComment from './UIComment'; import { LIKE_REACTION_KEY } from '~/constants'; import { CommentList } from '~/social/v4/internal-components/CommentList'; import { ReactionList } from '~/social/v4/components/ReactionList'; -import { useReactionsCollection } from '~/social/v4/hooks/collections/useReactionsCollection'; +import { useTheme } from 'styled-components'; const REPLIES_PER_PAGE = 5; @@ -105,9 +105,17 @@ interface CommentProps { ) => void; style?: React.CSSProperties; onClickReactionList: (commentId: string) => void; + shouldAllowInteraction?: boolean; } -const Comment = ({ commentId, readonly, onClickReply, style }: CommentProps) => { +const Comment = ({ + commentId, + readonly, + onClickReply, + style, + shouldAllowInteraction, +}: CommentProps) => { + const theme = useTheme(); const comment = useComment(commentId); const story = useStory(comment?.referenceId); const [bottomSheet, setBottomSheet] = useState(false); @@ -233,7 +241,7 @@ const Comment = ({ commentId, readonly, onClickReply, style }: CommentProps) => ? formatMessage({ id: 'reply.edit' }) : formatMessage({ id: 'comment.edit' }), action: startEditing, - icon: , + icon: , } : null, canReport @@ -242,7 +250,7 @@ const Comment = ({ commentId, readonly, onClickReply, style }: CommentProps) => ? formatMessage({ id: 'report.undoReport' }) : formatMessage({ id: 'report.doReport' }), action: handleReportComment, - icon: , + icon: , } : null, canDelete @@ -251,7 +259,7 @@ const Comment = ({ commentId, readonly, onClickReply, style }: CommentProps) => ? formatMessage({ id: 'reply.delete' }) : formatMessage({ id: 'comment.delete' }), action: deleteComment, - icon: , + icon: , } : null, ].filter(isNonNullable); @@ -352,7 +360,7 @@ const Comment = ({ commentId, readonly, onClickReply, style }: CommentProps) => setBottomSheet(false); }} > - {bottomSheetAction.icon} + {bottomSheetAction?.icon} {bottomSheetAction.name} ))} diff --git a/src/social/v4/internal-components/Comment/styles.tsx b/src/social/v4/internal-components/Comment/styles.tsx index 48ed42adb..8967647c3 100644 --- a/src/social/v4/internal-components/Comment/styles.tsx +++ b/src/social/v4/internal-components/Comment/styles.tsx @@ -59,6 +59,7 @@ export const MobileSheetButton = styled(SecondaryButton)` align-items: center; gap: 0.5rem; width: 100%; + margin-bottom: 0.5rem; `; export const CommentBlock = styled.div` diff --git a/src/social/v4/internal-components/CommentComposeBar/CommentComposeBar.tsx b/src/social/v4/internal-components/CommentComposeBar/CommentComposeBar.tsx index f8c462d2e..03988ee4e 100644 --- a/src/social/v4/internal-components/CommentComposeBar/CommentComposeBar.tsx +++ b/src/social/v4/internal-components/CommentComposeBar/CommentComposeBar.tsx @@ -23,11 +23,11 @@ const TOTAL_MENTIONEES_LIMIT = 30; const COMMENT_LENGTH_LIMIT = 50000; interface CommentComposeBarProps { + targetId: string; className?: string; userToReply?: Amity.User['displayName'] | null; onSubmit: (text: string, mentionees: Mentionees, metadata: Metadata) => void; onCancelReply?: () => void; - storyId?: string; isReplying?: boolean; style?: React.CSSProperties; } @@ -36,16 +36,15 @@ export const CommentComposeBar = ({ style, userToReply, onSubmit, - storyId, + targetId, }: CommentComposeBarProps) => { - const story = useStory(storyId); const { currentUserId } = useSDK(); const user = useUser(currentUserId); const avatarFileUrl = useImage({ fileId: user?.avatarFileId, imageSize: 'small' }); const { text, markup, mentions, mentionees, metadata, onChange, clearAll, queryMentionees } = useSocialMention({ - targetId: story?.targetId, - targetType: story?.targetType as string, + targetId: targetId, + targetType: 'community', }); const { formatMessage } = useIntl(); @@ -55,7 +54,7 @@ export const CommentComposeBar = ({ commentInputRef.current?.focus(); }, []); - if (story == null) return ; + if (targetId == null) return ; const addComment = () => { if (text === '') return; diff --git a/src/social/v4/internal-components/CommentList/CommentList.tsx b/src/social/v4/internal-components/CommentList/CommentList.tsx index 55806d334..1431b4343 100644 --- a/src/social/v4/internal-components/CommentList/CommentList.tsx +++ b/src/social/v4/internal-components/CommentList/CommentList.tsx @@ -28,6 +28,7 @@ interface CommentListProps { ) => void; style?: React.CSSProperties; onClickReaction?: (commentId: string) => void; + shouldAllowInteraction?: boolean; } export const CommentList = ({ @@ -41,7 +42,7 @@ export const CommentList = ({ isExpanded = true, onClickReply, style, - onClickReaction, + shouldAllowInteraction, }: CommentListProps) => { const [selectedCommentId, setSelectedCommentId] = useState(''); const { comments, hasMore, loadMore } = useCommentsCollection({ @@ -51,6 +52,8 @@ export const CommentList = ({ limit, }); + console.log(comments); + const { formatMessage } = useIntl(); const handleReactionClick = (commentId: string) => { @@ -109,6 +112,7 @@ export const CommentList = ({ onClickReply={onClickReply} style={style} onClickReactionList={() => handleReactionClick(comment.commentId)} + shouldAllowInteraction={shouldAllowInteraction} /> ); })} diff --git a/src/social/v4/internal-components/StoryCommentComposeBar/StoryCommentComposeBar.tsx b/src/social/v4/internal-components/StoryCommentComposeBar/StoryCommentComposeBar.tsx index 3082b9c1b..afc4ff14f 100644 --- a/src/social/v4/internal-components/StoryCommentComposeBar/StoryCommentComposeBar.tsx +++ b/src/social/v4/internal-components/StoryCommentComposeBar/StoryCommentComposeBar.tsx @@ -16,11 +16,12 @@ import { } from '~/social/v4/internal-components/CommentComposeBar/styles'; interface StoryCommentComposeBarProps { - storyId: string; + communityId: string; comment?: Amity.Comment | null; isJoined?: boolean; isReplying?: boolean; allowCommentInStory?: boolean; + shouldAllowCreation?: boolean; replyTo?: string | null; onCancelReply?: () => void; referenceType?: string; @@ -30,9 +31,9 @@ interface StoryCommentComposeBarProps { } export const StoryCommentComposeBar = ({ - storyId, + communityId, isJoined, - allowCommentInStory, + shouldAllowCreation, isReplying, replyTo, onCancelReply, @@ -76,7 +77,7 @@ export const StoryCommentComposeBar = ({ }); }; - if (isJoined && allowCommentInStory) { + if (isJoined && shouldAllowCreation) { return ( <> {isReplying && ( @@ -91,13 +92,13 @@ export const StoryCommentComposeBar = ({ {!isReplying ? ( handleAddComment(text, mentionees, metadata)} style={style} /> ) : ( { handleReplyToComment(replyText, mentionees, metadata); @@ -110,7 +111,7 @@ export const StoryCommentComposeBar = ({ ); } - if (isJoined && allowCommentInStory) { + if (isJoined && shouldAllowCreation) { return ( {formatMessage({ id: 'storyViewer.commentSheet.disabled' })} diff --git a/src/social/v4/internal-components/StoryViewer/Renderers/Image.tsx b/src/social/v4/internal-components/StoryViewer/Renderers/Image.tsx index 927521b96..17f33ce75 100644 --- a/src/social/v4/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/social/v4/internal-components/StoryViewer/Renderers/Image.tsx @@ -51,8 +51,8 @@ export const renderer: CustomRenderer = ({ story, action, config }) => { const isLiked = !!(story && story.myReactions && story.myReactions.includes(LIKE_REACTION_KEY)); const totalLikes = story.reactions[LIKE_REACTION_KEY] || 0; - const { + storyId, syncState, reach, commentsCount, @@ -209,14 +209,16 @@ export const renderer: CustomRenderer = ({ story, action, config }) => { pageId="*" isOpen={isOpenCommentSheet} onClose={closeCommentSheet} - referenceId={selectedComment?.referenceId} - referenceType={selectedComment?.referenceType} + referenceId={selectedComment?.referenceId || ''} + referenceType={(selectedComment?.referenceType as Amity.CommentReferenceType) || 'story'} + community={community as Amity.Community} + shouldAllowCreation={community?.allowCommentInStory || true} + shouldAllowInteraction={isJoined || true} commentId={selectedComment?.commentId} isReplying={isReplying} replyTo={replyTo} - storyId={story.storyId} + storyId={storyId} isJoined={isJoined} - allowCommentInStory={community?.allowCommentInStory} onCancelReply={() => setIsReplying(false)} onClickReply={onClickReply} /> @@ -233,13 +235,13 @@ export const renderer: CustomRenderer = ({ story, action, config }) => { onClick={() => story.analytics.markLinkAsClicked()} > - {story.items?.[0].data?.customText || story.items?.[0].data.url} + {story.items?.[0]?.data?.customText || story.items?.[0].data.url} )}
setIsReplying(false)} onClickReply={onClickReply} /> diff --git a/src/social/v4/internal-components/StoryViewer/index.tsx b/src/social/v4/internal-components/StoryViewer/index.tsx index a7eb22941..18af66b30 100644 --- a/src/social/v4/internal-components/StoryViewer/index.tsx +++ b/src/social/v4/internal-components/StoryViewer/index.tsx @@ -267,14 +267,12 @@ const StoryViewer = ({ pageId, targetId, duration = 5000, onClose }: StoryViewer } }, [stories, file, currentIndex]); - if (isDraft && file) { + if (file) { return ( ); } diff --git a/src/social/v4/pages/DraftsPage/DraftsPage.tsx b/src/social/v4/pages/DraftsPage/DraftsPage.tsx index e3c365ff6..8f7a39553 100644 --- a/src/social/v4/pages/DraftsPage/DraftsPage.tsx +++ b/src/social/v4/pages/DraftsPage/DraftsPage.tsx @@ -21,31 +21,26 @@ import useImage from '~/core/hooks/useImage'; import Truncate from 'react-truncate-markup'; -import { useCustomization } from '~/social/v4/providers/CustomizationProvider'; import { BackButton, AspectRatioButton, ShareStoryButton, HyperLinkButton, } from '~/social/v4/elements'; -import { useTheme } from 'styled-components'; + import { usePageBehavior } from '~/social/v4/providers/PageBehaviorProvider'; import { HyperLink } from '~/social/v4/elements/HyperLink'; import { SubmitHandler } from 'react-hook-form'; import { HyperLinkConfig } from '../../components/HyperLinkConfig'; +import { useStoryContext } from '~/v4/social/providers/StoryProvider'; +import { StoryRepository } from '@amityco/ts-sdk'; +import { notification } from '~/core/components/Notification'; type DraftStoryProps = { - pageId: 'create_story_page'; - file: File; - creatorAvatar: string; - onCreateStory: ( - file: File, - imageMode: 'fit' | 'fill', - metadata?: Amity.Metadata, - items?: Amity.StoryItem[], - ) => void; - onDiscardStory: () => void; + targetId: string; + targetType: Amity.StoryTargetType; + mediaType: 'image' | 'video'; }; type HyperLinkFormInputs = { @@ -53,11 +48,8 @@ type HyperLinkFormInputs = { customText?: string; }; -export const DraftsPage = ({ pageId, file, onDiscardStory, onCreateStory }: DraftStoryProps) => { - const theme = useTheme(); - const { getConfig, isExcluded } = useCustomization(); - const pageConfig = getConfig(`${pageId}/*/*`); - const isPageExcluded = isExcluded(`${pageId}/*/*`); +export const DraftsPage = ({ targetId, targetType, mediaType }: DraftStoryProps) => { + const { file, setFile } = useStoryContext(); const { navigationBehavior } = usePageBehavior(); const [isHyperLinkBottomSheetOpen, setIsHyperLinkBottomSheetOpen] = useState(false); @@ -77,13 +69,6 @@ export const DraftsPage = ({ pageId, file, onDiscardStory, onCreateStory }: Draf }, ]); - const pageThemePrimaryColor = - pageConfig?.page_theme?.light_theme.primary_color || theme.v4.colors.primary.default; - const pageThemeSecondaryColor = - pageConfig?.page_theme?.light_theme.secondary_color || theme.v4.colors.secondary.default; - - if (isPageExcluded) return null; - const onSubmit: SubmitHandler = (data) => { onSubmit(data); }; @@ -108,6 +93,46 @@ export const DraftsPage = ({ pageId, file, onDiscardStory, onCreateStory }: Draf } }; + const onCreateStory = async ( + file: File | null, + imageMode: 'fit' | 'fill', + metadata?: Amity.Metadata, + items?: Amity.StoryItem[], + ) => { + if (!file) return; + const formData = new FormData(); + formData.append('files', file); + setFile(null); + if (file?.type.includes('image')) { + const { data: imageData } = await StoryRepository.createImageStory( + targetType, + targetId, + formData, + metadata, + imageMode, + items, + ); + if (imageData) { + notification.success({ + content: formatMessage({ id: 'storyViewer.notification.success' }), + }); + } + } else { + const { data: videoData } = await StoryRepository.createVideoStory( + targetType, + targetId, + formData, + metadata, + items, + ); + if (videoData) { + notification.success({ + content: formatMessage({ id: 'storyViewer.notification.success' }), + }); + } + } + }; + const discardCreateStory = () => { confirm({ title: formatMessage({ id: 'storyViewer.action.confirmModal.title' }), @@ -115,7 +140,7 @@ export const DraftsPage = ({ pageId, file, onDiscardStory, onCreateStory }: Draf cancelText: formatMessage({ id: 'general.action.cancel' }), okText: formatMessage({ id: 'delete' }), onOk: () => { - onDiscardStory(); + setFile(null); navigationBehavior.closeAction(); }, }); @@ -164,17 +189,10 @@ export const DraftsPage = ({ pageId, file, onDiscardStory, onCreateStory }: Draf
- +
- {file?.type.includes('image') && ( + {mediaType === 'image' && (
- {file?.type.includes('image') ? ( + {file && mediaType === 'image' ? ( ) : ( onCreateStory(file, imageMode, {}, hyperLink[0].data.url ? hyperLink : [])} avatar={creatorAvatar} - style={{ - color: pageThemeSecondaryColor, - backgroundColor: pageThemePrimaryColor, - }} /> void; +}; + +const StoryContext = createContext({ + file: null, + setFile: () => {}, +}); + +export const useStoryContext = () => useContext(StoryContext); + +type StoryProviderProps = { + children: React.ReactNode; +}; + +export const StoryProvider: React.FC = ({ children }) => { + const [file, setFile] = useState(null); + + const value: StoryContextType = { + file, + setFile, + }; + + return {children}; +}; From b823c8ac442791fd67c5a3132849b94d3cd59a93 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Tue, 9 Apr 2024 13:20:50 +0700 Subject: [PATCH 003/300] fix: ASC-21249 - align story tab component props (#203) * fix: can't upload story * fix: story image renderer * fix: align comment tray component props * fix: align story tab component props * fix: change export name to align signature api * fix: ddraft page * fix: align signature api * fix: story tab --- .../CommunityInfo/UICommunityInfo.tsx | 26 +---- src/social/components/CommunityInfo/index.tsx | 2 +- src/social/hooks/useStories.ts | 8 ++ .../v4/components/StoryTab/StoryRing.tsx | 4 +- .../v4/components/StoryTab/StoryTab.tsx | 102 +++++++++--------- 5 files changed, 60 insertions(+), 82 deletions(-) diff --git a/src/social/components/CommunityInfo/UICommunityInfo.tsx b/src/social/components/CommunityInfo/UICommunityInfo.tsx index a619e88f5..137278dee 100644 --- a/src/social/components/CommunityInfo/UICommunityInfo.tsx +++ b/src/social/components/CommunityInfo/UICommunityInfo.tsx @@ -46,15 +46,8 @@ interface UICommunityInfoProps { onClickLeaveCommunity: (communityId: string) => void; canLeaveCommunity: boolean; canReviewPosts: boolean; - isStorySyncing: boolean; - haveStories: boolean; - haveStoryPermission: boolean; - isStoryErrored: boolean; - isSeen: boolean; name: string; postSetting: ValueOf; - setStoryFile: React.Dispatch>; - onClickStory: (communityId: string) => void; } const UICommunityInfo = ({ @@ -67,11 +60,6 @@ const UICommunityInfo = ({ isJoined, isOfficial, isPublic, - isStorySyncing, - isSeen, - isStoryErrored, - haveStories, - haveStoryPermission, avatarFileUrl, canEditCommunity, onEditCommunity, @@ -81,8 +69,6 @@ const UICommunityInfo = ({ canReviewPosts, name, postSetting, - setStoryFile, - onClickStory, }: UICommunityInfoProps) => { const { formatMessage } = useIntl(); @@ -157,17 +143,7 @@ const UICommunityInfo = ({ )} - onClickStory(communityId)} - onChange={setStoryFile} - /> + {isJoined && canEditCommunity && ( + ); +}; + +export default Button; diff --git a/src/v4/core/components/Button/index.ts b/src/v4/core/components/Button/index.ts new file mode 100644 index 000000000..eae9c8e3b --- /dev/null +++ b/src/v4/core/components/Button/index.ts @@ -0,0 +1 @@ +export { default as Button } from './Button'; diff --git a/src/v4/core/components/Button/ui.stories.tsx b/src/v4/core/components/Button/ui.stories.tsx new file mode 100644 index 000000000..9caf7a90a --- /dev/null +++ b/src/v4/core/components/Button/ui.stories.tsx @@ -0,0 +1,67 @@ +import React from 'react'; +import { ComponentStory, ComponentMeta } from '@storybook/react'; +import Button from './Button'; +import { ArrowRight2Icon } from '~/icons'; + +export default { + title: 'Components/Button', + component: Button, + argTypes: { + variant: { + control: { + type: 'select', + options: ['primary', 'secondary'], + }, + }, + size: { + control: { + type: 'select', + options: ['small', 'medium', 'large'], + }, + }, + disabled: { + control: 'boolean', + }, + onClick: { + action: 'clicked', + }, + }, +} as ComponentMeta; + +const Template: ComponentStory = (args) => + + +); + +export const Size: ComponentStory = (args) => ( + <> + + + + +); + +export const Disabled = Template.bind({}); +Disabled.args = { + disabled: true, + children: 'Disabled Button', +}; + +export const WithIcon = Template.bind({}); +WithIcon.args = { + children: , +}; diff --git a/src/core/v4/components/Icon/Icon.tsx b/src/v4/core/components/Icon/Icon.tsx similarity index 78% rename from src/core/v4/components/Icon/Icon.tsx rename to src/v4/core/components/Icon/Icon.tsx index c5621ac13..232b8b1dc 100644 --- a/src/core/v4/components/Icon/Icon.tsx +++ b/src/v4/core/components/Icon/Icon.tsx @@ -6,11 +6,19 @@ type IconName = keyof typeof Icons; export interface IconProps { name: IconName | null; size?: number; + className?: string; style?: React.CSSProperties; onClick?: (e: React.MouseEvent) => void; } -export const Icon: React.FC = ({ name, size = 24, style, onClick, ...props }) => { +export const Icon: React.FC = ({ + name, + size = 24, + className, + style, + onClick, + ...props +}) => { const iconMap = { ...Icons, }; @@ -27,6 +35,7 @@ export const Icon: React.FC = ({ name, size = 24, style, onClick, ... data-qa-anchor={`${name}-icon`} width={size} height={size} + className={className} style={style} onClick={onClick} {...props} diff --git a/src/core/v4/components/Icon/index.ts b/src/v4/core/components/Icon/index.ts similarity index 100% rename from src/core/v4/components/Icon/index.ts rename to src/v4/core/components/Icon/index.ts diff --git a/src/v4/core/components/Typography/Typography.tsx b/src/v4/core/components/Typography/Typography.tsx new file mode 100644 index 000000000..69ac9be8e --- /dev/null +++ b/src/v4/core/components/Typography/Typography.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import clsx from 'clsx'; + +interface TypographyProps { + children: React.ReactNode; + className?: string; +} + +const Typography: React.FC & { + Heading: React.FC; + Title: React.FC; + Subtitle: React.FC; + Body: React.FC; + BodyBold: React.FC; + Caption: React.FC; + CaptionBold: React.FC; +} = ({ children, className = '' }) => { + return
{children}
; +}; + +Typography.Heading = ({ children, className = '' }) => { + return

{children}

; +}; + +Typography.Title = ({ children, className = '' }) => { + return

{children}

; +}; + +Typography.Subtitle = ({ children, className = '' }) => { + return

{children}

; +}; + +Typography.Body = ({ children, className = '' }) => { + return

{children}

; +}; + +Typography.BodyBold = ({ children, className = '' }) => { + return

{children}

; +}; + +Typography.Caption = ({ children, className = '' }) => { + return

{children}

; +}; + +Typography.CaptionBold = ({ children, className = '' }) => { + return

{children}

; +}; + +export default Typography; diff --git a/src/v4/core/components/Typography/index.ts b/src/v4/core/components/Typography/index.ts new file mode 100644 index 000000000..1b67c1953 --- /dev/null +++ b/src/v4/core/components/Typography/index.ts @@ -0,0 +1 @@ +export { default as Typography } from './Typography'; diff --git a/src/v4/core/components/Typography/ui.stories.tsx b/src/v4/core/components/Typography/ui.stories.tsx new file mode 100644 index 000000000..6d21fbb86 --- /dev/null +++ b/src/v4/core/components/Typography/ui.stories.tsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { ComponentStory, ComponentMeta } from '@storybook/react'; +import Typography from './Typography'; + +export default { + title: 'v4/Core/Components/Typography', + component: Typography, +} as ComponentMeta; + +const TypographyOverview: React.FC = () => ( +
+ Heading + Title + Subtitle + Body text + Bold body text + Caption + Bold caption +
+); + +export const Overview: ComponentStory = () => ; + +export const Heading: ComponentStory = (args) => ( + +); +Heading.args = { + children: 'Heading', +}; + +export const Title: ComponentStory = (args) => ( + +); +Title.args = { + children: 'Title', +}; + +export const Subtitle: ComponentStory = (args) => ( + +); +Subtitle.args = { + children: 'Subtitle', +}; + +export const Body: ComponentStory = (args) => ; +Body.args = { + children: 'Body text', +}; + +export const BodyBold: ComponentStory = (args) => ( + +); +BodyBold.args = { + children: 'Bold body text', +}; + +export const Caption: ComponentStory = (args) => ( + +); +Caption.args = { + children: 'Caption', +}; + +export const CaptionBold: ComponentStory = (args) => ( + +); +CaptionBold.args = { + children: 'Bold caption', +}; diff --git a/src/core/v4/components/index.ts b/src/v4/core/components/index.ts similarity index 100% rename from src/core/v4/components/index.ts rename to src/v4/core/components/index.ts diff --git a/src/v4/core/providers/AmityUIKitProvider.tsx b/src/v4/core/providers/AmityUIKitProvider.tsx new file mode 100644 index 000000000..3333b0652 --- /dev/null +++ b/src/v4/core/providers/AmityUIKitProvider.tsx @@ -0,0 +1,160 @@ +import '../../../core/providers/UiKitProvider/inter.css'; +import './index.css'; +import '../../styles/global.css'; +import amityUKitConfig from '../../../../amity-uikit.config.json'; + +import React, { useEffect, useMemo, useState } from 'react'; +import useUser from '~/core/hooks/useUser'; + +import SDKConnectorProvider from '~/core/providers/SDKConnectorProvider'; +import { SDKContext } from '~/core/providers/SDKProvider'; +import PostRendererProvider from '~/social/providers/PostRendererProvider'; +import NavigationProvider from '~/social/providers/NavigationProvider'; + +import ConfigProvider from '~/social/providers/ConfigProvider'; +import { ConfirmContainer } from '~/core/components/Confirm'; +import { NotificationsContainer } from '~/core/components/Notification'; + +import Localization from '~/core/providers/UiKitProvider/Localization'; + +import { ThemeProvider as StyledThemeProvider } from 'styled-components'; +import buildGlobalTheme from '~/core/providers/UiKitProvider/theme'; +import { Config, CustomizationProvider } from './CustomizationProvider'; +import { ThemeProvider } from './ThemeProvider'; +import { PageBehaviorProvider } from './PageBehaviorProvider'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { UIStyles } from '~/core/providers/UiKitProvider/styles'; +import AmityUIKitManager from '../AmityUIKitManager'; + +export type AmityUIKitConfig = typeof amityUKitConfig; + +interface AmityUIKitProviderProps { + authToken?: string; + userId: string; + displayName: string; + postRendererConfig?: any; + theme?: Record; + children?: React.ReactNode; + socialCommunityCreationButtonVisible?: boolean; + actionHandlers?: { + onChangePage?: (data: { type: string; [x: string]: string | boolean }) => void; + onClickCategory?: (categoryId: string) => void; + onClickCommunity?: (communityId: string) => void; + onClickUser?: (userId: string) => void; + onCommunityCreated?: (communityId: string) => void; + onEditCommunity?: (communityId: string, options?: { tab?: string }) => void; + onEditUser?: (userId: string) => void; + onMessageUser?: (userId: string) => void; + }; + pageBehavior?: { + onCloseAction?: () => void; + onClickHyperLink?: () => void; + }; + onConnectionStatusChange?: (state: Amity.SessionStates) => void; + onConnected?: () => void; + onDisconnected?: () => void; + configs?: AmityUIKitConfig; +} + +const apiKey = import.meta.env.STORYBOOK_API_KEY; +const apiRegion = import.meta.env.STORYBOOK_API_REGION; + +const AmityUIKitProvider: React.FC = ({ + authToken, + userId, + displayName, + postRendererConfig, + theme = {}, + children /* TODO localization */, + socialCommunityCreationButtonVisible, + pageBehavior, + onConnectionStatusChange, + onDisconnected, + configs, +}) => { + const queryClient = new QueryClient(); + const [client, setClient] = useState(null); + const currentUser = useUser(userId); + + const sdkContextValue = useMemo( + () => ({ + client, + currentUserId: userId || undefined, + userRoles: currentUser?.roles || [], + }), + [client, userId, currentUser?.roles], + ); + + useEffect(() => { + const setup = async () => { + try { + // Set up the AmityUIKitManager + AmityUIKitManager.setup({ apiKey, endpoint: apiRegion }); + + // Register the device and get the client instance + await AmityUIKitManager.registerDevice( + userId, + displayName || userId, + { + sessionWillRenewAccessToken: (renewal) => { + // Handle access token renewal + if (authToken) { + renewal.renewWithAuthToken(authToken); + } else { + renewal.renew(); + } + }, + }, + onConnectionStatusChange, + onDisconnected, + ); + + const newClient = AmityUIKitManager.getClient(); + setClient(newClient); + } catch (error) { + console.error('Error setting up AmityUIKitManager:', error); + } + }; + + setup(); + }, [userId, displayName, authToken, onConnectionStatusChange, onDisconnected]); + + if (!client) return null; + + return ( + + + + + + + + + + + + + {children} + + + + + + + + + + + + + + + ); +}; + +export default AmityUIKitProvider; diff --git a/src/social/v4/providers/CustomizationProvider.tsx b/src/v4/core/providers/CustomizationProvider.tsx similarity index 82% rename from src/social/v4/providers/CustomizationProvider.tsx rename to src/v4/core/providers/CustomizationProvider.tsx index 279224208..65cb19021 100644 --- a/src/social/v4/providers/CustomizationProvider.tsx +++ b/src/v4/core/providers/CustomizationProvider.tsx @@ -7,20 +7,42 @@ interface CustomizationContextValue { getConfig: (path: keyof Config['customizations']) => Record; } -interface Theme { - primary_color: string; - secondary_color: string; -} +type Theme = { + light: { + primary_color: string; + secondary_color: string; + base_color: string; + base_shade1_color: string; + base_shade2_color: string; + base_shade3_color: string; + base_shade4_color: string; + alert_color: string; + background_color: string; + }; + dark: { + primary_color: string; + secondary_color: string; + base_color: string; + base_shade1_color: string; + base_shade2_color: string; + base_shade3_color: string; + base_shade4_color: string; + alert_color: string; + background_color: string; + }; +}; export interface Config { - global_theme: { - light_theme: Theme; + preferred_theme: 'light' | 'dark' | 'default'; + theme: { + light: Theme['light']; + dark: Theme['dark']; }; excludes: string[]; customizations: { 'select_target_page/*/*': { - page_theme: { - light_theme: { + theme?: { + light: { primary_color: string; secondary_color: string; }; @@ -32,8 +54,8 @@ export interface Config { }; 'camera_page/*/*': { resolution: string; - page_theme: { - light_theme: { + theme?: { + light: { primary_color: string; secondary_color: string; }; @@ -44,8 +66,8 @@ export interface Config { background_color: string; }; 'create_story_page/*/*': { - page_theme?: { - light_theme: { + theme?: { + light: { primary_color: string; secondary_color: string; }; @@ -73,8 +95,8 @@ export interface Config { hide_avatar: boolean; }; 'story_page/*/*': { - page_theme?: { - light_theme: { + theme?: { + light: { primary_color: string; secondary_color: string; }; @@ -111,7 +133,7 @@ export interface Config { background_color?: string; }; '*/edit_comment_component/*': { - component_theme: { + theme?: { light_theme: { primary_color: string; secondary_color: string; @@ -129,8 +151,8 @@ export interface Config { background_color: string; }; '*/hyper_link_config_component/*': { - component_theme: { - light_theme: { + theme: { + light: { primary_color: string; secondary_color: string; }; @@ -202,12 +224,21 @@ export const CustomizationProvider: React.FC = ({ const [config, setConfig] = useState(null); useEffect(() => { - parseConfig(initialConfig); + if (validateConfig(initialConfig)) { + parseConfig(initialConfig); + } else { + console.error('Invalid configuration provided to CustomizationProvider'); + } }, [initialConfig]); const validateConfig = (config: Config): boolean => { // Check if mandatory fields are present - if (!config?.global_theme || !config?.excludes || !config?.customizations) { + if ( + !config?.preferred_theme || + !config?.theme || + !config?.excludes || + !config?.customizations + ) { return false; } @@ -215,14 +246,7 @@ export const CustomizationProvider: React.FC = ({ }; const parseConfig = (newConfig: Config) => { - // Validate the parsed config - const isValid = validateConfig(newConfig); - - if (isValid) { - setConfig(newConfig); - } else { - console.error('Invalid configuration:', newConfig); - } + setConfig(newConfig); }; const isExcluded = (path: string) => { diff --git a/src/social/v4/providers/PageBehaviorProvider.tsx b/src/v4/core/providers/PageBehaviorProvider.tsx similarity index 54% rename from src/social/v4/providers/PageBehaviorProvider.tsx rename to src/v4/core/providers/PageBehaviorProvider.tsx index 8103ea4d6..eeefde75f 100644 --- a/src/social/v4/providers/PageBehaviorProvider.tsx +++ b/src/v4/core/providers/PageBehaviorProvider.tsx @@ -1,8 +1,9 @@ -import React from 'react'; +import React, { useMemo, useContext } from 'react'; import { useNavigation } from '~/social/providers/NavigationProvider'; interface NavigationBehavior { - closeAction(): void; + onCloseAction(): void; + onClickHyperLink(): void; } interface PageBehavior { @@ -13,35 +14,38 @@ const PageBehaviorContext = React.createContext(undefi interface PageBehaviorProviderProps { children: React.ReactNode; - customNavigationBehavior?: Partial; + pageBehavior?: Partial; } export const PageBehaviorProvider: React.FC = ({ children, - customNavigationBehavior = {}, + pageBehavior = {}, }) => { const { onBack } = useNavigation(); const defaultNavigationBehavior: NavigationBehavior = { - closeAction: () => { + onCloseAction: () => { onBack(); }, - }; - const mergedNavigationBehavior: NavigationBehavior = { - ...defaultNavigationBehavior, - ...customNavigationBehavior, + onClickHyperLink: () => {}, }; - const pageBehavior: PageBehavior = { - navigationBehavior: mergedNavigationBehavior, - }; + const pageBehaviorMemo = useMemo(() => { + const mergedNavigationBehavior: NavigationBehavior = { + ...defaultNavigationBehavior, + ...pageBehavior, + }; + return { + navigationBehavior: mergedNavigationBehavior, + }; + }, []); return ( - {children} + {children} ); }; export const usePageBehavior = () => { - const pageBehavior = React.useContext(PageBehaviorContext); + const pageBehavior = useContext(PageBehaviorContext); if (!pageBehavior) { throw new Error('usePageBehavior must be used within a PageBehaviorProvider'); } diff --git a/src/v4/core/providers/ThemeProvider.tsx b/src/v4/core/providers/ThemeProvider.tsx new file mode 100644 index 000000000..99f464271 --- /dev/null +++ b/src/v4/core/providers/ThemeProvider.tsx @@ -0,0 +1,218 @@ +import React, { createContext, useEffect, useState } from 'react'; +import { lighten, parseToHsl, darken, hslToColorString } from 'polished'; + +const SHADE_PERCENTAGES = [0.25, 0.4, 0.5, 0.75]; + +const setCSSVariable = (variable: string, value: string) => { + document.documentElement.style.setProperty(variable, value); +}; + +const generateShades = (hexColor: string, isDarkMode = false): string[] => { + const hslColor = parseToHsl(hexColor); + + const shades = SHADE_PERCENTAGES.map((percentage) => { + if (isDarkMode) { + return darken(percentage, hslToColorString(hslColor)); + } else { + return lighten(percentage, hslToColorString(hslColor)); + } + }); + + return shades; +}; + +export const ThemeContext = createContext<{ + currentTheme: 'light' | 'dark'; + toggleTheme: () => void; +}>({ + currentTheme: 'light', + toggleTheme: () => {}, +}); + +const defaultConfig = { + preferred_theme: 'default', + theme: { + light: { + primary_color: '#1054DE', + secondary_color: '#292B32', + base_color: '#292b32', + base_shade1_color: '#636878', + base_shade2_color: '#898e9e', + base_shade3_color: '#a5a9b5', + base_shade4_color: '#ebecef', + alert_color: '#FA4D30', + background_color: '#FFFFFF', + }, + dark: { + primary_color: '#1054DE', + secondary_color: '#292B32', + base_color: '#ebecef', + base_shade1_color: '#a5a9b5', + base_shade2_color: '#6e7487', + base_shade3_color: '#40434e', + base_shade4_color: '#292b32', + alert_color: '#FA4D30', + background_color: '#191919', + }, + }, + excludes: [], + customizations: { + 'select_target_page/*/*': { + theme: {}, + title: 'Share to', + }, + 'select_target_page/*/back_button': { + back_icon: 'back.png', + }, + 'camera_page/*/*': { + resolution: '720p', + }, + 'camera_page/*/close_button': { + close_icon: 'close.png', + }, + 'create_story_page/*/*': {}, + 'create_story_page/*/back_button': { + back_icon: 'back.png', + background_color: '#1234DB', + }, + 'create_story_page/*/aspect_ratio_button': { + aspect_ratio_icon: 'aspect_ratio.png', + background_color: '1234DB', + }, + 'create_story_page/*/story_hyperlink_button': { + hyperlink_button_icon: 'hyperlink_button.png', + background_color: '#1234DB', + }, + 'create_story_page/*/hyper_link': { + hyper_link_icon: 'hyper_link.png', + background_color: '#1234DB', + }, + 'create_story_page/*/share_story_button': { + share_icon: 'share_story_button.png', + background_color: '#1234DB', + hide_avatar: false, + }, + 'story_page/*/*': {}, + 'story_page/*/progress_bar': { + progress_color: '#UD1234', + background_color: '#AB1234', + }, + 'story_page/*/overflow_menu': { + overflow_menu_icon: 'threeDot.png', + }, + 'story_page/*/close_button': { + close_icon: 'close.png', + }, + 'story_page/*/story_impression_button': { + impression_icon: 'impressionIcon.png', + }, + 'story_page/*/story_comment_button': { + comment_icon: 'comment.png', + background_color: '#2b2b2b', + }, + 'story_page/*/story_reaction_button': { + reaction_icon: 'like.png', + background_color: '#2b2b2b', + }, + 'story_page/*/create_new_story_button': { + create_new_story_icon: 'plus.png', + background_color: '#ffffff', + }, + 'story_page/*/speaker_button': { + mute_icon: 'mute.png', + unmute_icon: 'unmute.png', + background_color: '#1243EE', + }, + '*/edit_comment_component/*': { + theme: {}, + }, + '*/edit_comment_component/cancel_button': { + cancel_icon: '', + cancel_button_text: 'cancel', + background_color: '#1243EE', + }, + '*/edit_comment_component/save_button': { + save_icon: '', + save_button_text: 'Save', + background_color: '#1243EE', + }, + '*/hyper_link_config_component/*': { + theme: {}, + }, + '*/hyper_link_config_component/done_button': { + done_icon: '', + done_button_text: 'Done', + background_color: '#1243EE', + }, + '*/hyper_link_config_component/cancel_button': { + cancel_icon: '', + cancel_button_text: 'Cancel', + }, + '*/comment_tray_component/*': { + theme: {}, + }, + '*/story_tab_component/*': {}, + '*/story_tab_component/story_ring': { + progress_color: ['#339AF9', '#78FA58'], + background_color: '#AB1234', + }, + '*/story_tab_component/create_new_story_button': { + create_new_story_icon: 'plus.png', + background_color: '#1243EE', + }, + '*/*/close_button': { + close_icon: 'close.png', + }, + }, +}; + +export const ThemeProvider: React.FC<{ initialConfig?: any }> = ({ children, initialConfig }) => { + const config = initialConfig || defaultConfig; + + const [currentTheme, setCurrentTheme] = useState<'light' | 'dark'>('light'); + + useEffect(() => { + const primaryColorShades = generateShades(config.light.primary_color); + const secondaryColorShades = generateShades(config.light.secondary_color); + + setCSSVariable('--asc-color-primary-default', config.light.primary_color); + setCSSVariable('--asc-color-primary-shade1', primaryColorShades[0]); + setCSSVariable('--asc-color-primary-shade2', primaryColorShades[1]); + setCSSVariable('--asc-color-primary-shade3', primaryColorShades[2]); + setCSSVariable('--asc-color-primary-shade4', primaryColorShades[3]); + + setCSSVariable('--asc-color-secondary-default', config.light.secondary_color); + setCSSVariable('--asc-color-secondary-shade1', secondaryColorShades[0]); + setCSSVariable('--asc-color-secondary-shade2', secondaryColorShades[1]); + setCSSVariable('--asc-color-secondary-shade3', secondaryColorShades[2]); + setCSSVariable('--asc-color-secondary-shade4', secondaryColorShades[3]); + + setCSSVariable('--asc-color-base-default', config.light?.base_color); + setCSSVariable('--asc-color-base-shade1', config.light?.base_shade1_color); + setCSSVariable('--asc-color-base-shade2', config.light?.base_shade2_color); + setCSSVariable('--asc-color-base-shade3', config.light?.base_shade3_color); + setCSSVariable('--asc-color-base-shade4', config.light?.base_shade4_color); + + setCSSVariable('--asc-color-alert', config.light?.alert_color); + setCSSVariable('--asc-color-background', config.light?.background_color); + }, [currentTheme, config]); + + useEffect(() => { + if (config.preferred_theme === 'default') { + const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); + const handleChange = (e: MediaQueryListEvent) => { + setCurrentTheme(e.matches ? 'dark' : 'light'); + }; + mediaQuery.addEventListener('change', handleChange); + return () => mediaQuery.removeEventListener('change', handleChange); + } + }, [config.preferred_theme]); + + const toggleTheme = () => { + setCurrentTheme((prevTheme) => (prevTheme === 'light' ? 'dark' : 'light')); + }; + + return ( + {children} + ); +}; diff --git a/src/v4/core/providers/UIStyles.module.css b/src/v4/core/providers/UIStyles.module.css new file mode 100644 index 000000000..423afd13c --- /dev/null +++ b/src/v4/core/providers/UIStyles.module.css @@ -0,0 +1,27 @@ +.uiStyles { + font-family: var(--typography-body-font-family); + font-size: var(--typography-body-font-size); + font-weight: var(--typography-body-font-weight); + line-height: var(--typography-body-line-height); + color: var(--palette-base-main); + width: 100%; + height: 100%; + overflow: hidden; + } + + .uiStyles input, + .uiStyles div { + box-sizing: border-box; + } + + .uiStyles * { + font-size: var(--typography-body-font-size); + line-height: 1.5; + } + + .uiStyles pre { + font-family: var(--typography-body-font-family); + font-size: var(--typography-body-font-size); + font-weight: var(--typography-body-font-weight); + line-height: var(--typography-body-line-height); + } \ No newline at end of file diff --git a/src/v4/core/providers/index.css b/src/v4/core/providers/index.css new file mode 100644 index 000000000..19b92c9fd --- /dev/null +++ b/src/v4/core/providers/index.css @@ -0,0 +1,56 @@ +@keyframes react-loading-skeleton { + 100% { + transform: translateX(100%); + } + } + + .react-loading-skeleton { + --base-color: #ebebeb; + --highlight-color: #f5f5f5; + --animation-duration: 1.5s; + --animation-direction: normal; + --pseudo-element-display: block; /* Enable animation */ + + background-color: var(--base-color); + + width: 100%; + border-radius: 0.25rem; + display: inline-flex; + line-height: 1; + + position: relative; + user-select: none; + overflow: hidden; + z-index: 1; /* Necessary for overflow: hidden to work correctly in Safari */ + } + + .react-loading-skeleton::after { + content: ' '; + display: var(--pseudo-element-display); + position: absolute; + top: 0; + left: 0; + right: 0; + height: 100%; + background-repeat: no-repeat; + background-image: linear-gradient( + 90deg, + var(--base-color), + var(--highlight-color), + var(--base-color) + ); + transform: translateX(-100%); + + animation-name: react-loading-skeleton; + animation-direction: var(--animation-direction); + animation-duration: var(--animation-duration); + animation-timing-function: ease-in-out; + animation-iteration-count: infinite; + } + + @media (prefers-reduced-motion) { + .react-loading-skeleton { + --pseudo-element-display: none; /* Disable animation */ + } + } + \ No newline at end of file diff --git a/src/v4/core/providers/index.ts b/src/v4/core/providers/index.ts new file mode 100644 index 000000000..74c545319 --- /dev/null +++ b/src/v4/core/providers/index.ts @@ -0,0 +1 @@ +export { default as AmityUIKitProvider } from './AmityUIKitProvider'; diff --git a/src/v4/core/providers/typography.module.css b/src/v4/core/providers/typography.module.css new file mode 100644 index 000000000..f34a87552 --- /dev/null +++ b/src/v4/core/providers/typography.module.css @@ -0,0 +1,43 @@ + + +.ascTypographyHeading { + font-family: var(--asc-text-global-font-family); + font-size: 1.25rem; + line-height: 1.5rem; + font-weight: 600; + } + + .ascTypographyTitle { + font-family: var(--asc-text-global-font-family); + font-size: 1rem; + line-height: 1.5rem; + font-weight: 600; + } + + .ascTypographyBodyBold { + font-family: var(--asc-text-global-font-family); + font-size: 0.875rem; + line-height: 1.25rem; + font-weight: 600; + } + + .ascTypographyBody { + font-family: var(--asc-text-global-font-family); + font-size: 0.875rem; + line-height: 1.25rem; + font-weight: 400; + } + + .ascTypographyCaptionBold { + font-family: var(--asc-text-global-font-family); + font-size: 0.75rem; + line-height: 1rem; + font-weight: 600; + } + + .ascTypographyCaption { + font-family: var(--asc-text-global-font-family); + font-size: 0.75rem; + line-height: 1rem; + font-weight: 400; + } \ No newline at end of file diff --git a/src/social/v4/components/CommentEdition/CommentEdition.tsx b/src/v4/social/components/CommentEdition/CommentEdition.tsx similarity index 91% rename from src/social/v4/components/CommentEdition/CommentEdition.tsx rename to src/v4/social/components/CommentEdition/CommentEdition.tsx index 63375cbd5..eab6b2734 100644 --- a/src/social/v4/components/CommentEdition/CommentEdition.tsx +++ b/src/v4/social/components/CommentEdition/CommentEdition.tsx @@ -1,10 +1,12 @@ import React from 'react'; -import { useCustomization } from '~/social/v4/providers/CustomizationProvider'; + import { ButtonContainer, CommentEditContainer, CommentEditTextarea } from './styles'; import { QueryMentioneesFnType } from '~/social/hooks/useSocialMention'; -import { CancelButton, SaveButton } from '~/social/v4/elements'; + import { useTheme } from 'styled-components'; +import { CancelButton, SaveButton } from '../../elements'; +import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; interface CommentEditionProps { pageId?: '*'; diff --git a/src/social/v4/components/CommentEdition/index.ts b/src/v4/social/components/CommentEdition/index.ts similarity index 100% rename from src/social/v4/components/CommentEdition/index.ts rename to src/v4/social/components/CommentEdition/index.ts diff --git a/src/social/v4/components/CommentEdition/styles.tsx b/src/v4/social/components/CommentEdition/styles.tsx similarity index 99% rename from src/social/v4/components/CommentEdition/styles.tsx rename to src/v4/social/components/CommentEdition/styles.tsx index 3be7bc2ff..85ea22d2c 100644 --- a/src/social/v4/components/CommentEdition/styles.tsx +++ b/src/v4/social/components/CommentEdition/styles.tsx @@ -1,4 +1,4 @@ -import styled from 'styled-components'; +import styled, { DefaultTheme } from 'styled-components'; import React, { ReactNode } from 'react'; import UIOptionMenu from '~/core/components/OptionMenu'; diff --git a/src/v4/social/components/CommentTray/CommentTray.module.css b/src/v4/social/components/CommentTray/CommentTray.module.css new file mode 100644 index 000000000..691fc8838 --- /dev/null +++ b/src/v4/social/components/CommentTray/CommentTray.module.css @@ -0,0 +1,51 @@ +.container { + display: flex; + flex-direction: column; + height: 100%; + background-color: var(--asc-color-base-inverse); + border: 1px solid var(--asc-color-base-shade4); + + } + + .header { + display: flex; + align-items: center; + justify-content: center; + text-align: center; + padding: var(--asc-spacing-m1); + background-color: var(--asc-color-base-inverse); + color: var(--asc-color-base-default); + font-size: 18px; + font-weight: bold; + border-bottom: 1px solid var(--asc-color-base-shade4); + } + + .roundedHeader { + border-top-left-radius: 1rem; + border-top-right-radius: 1rem; + } + + .content { + flex: 1; + overflow-y: auto; + padding: var(--asc-spacing-m1); + } + + .scroller { + height: 100%; + } + + .composeBarContainer { + padding: var(--asc-spacing-m1); + background-color: var(--asc-color-base-inverse); + } + + .nestedBackdrop { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + z-index: -1; + } \ No newline at end of file diff --git a/src/social/v4/components/CommentTray/CommentTray.tsx b/src/v4/social/components/CommentTray/CommentTray.tsx similarity index 85% rename from src/social/v4/components/CommentTray/CommentTray.tsx rename to src/v4/social/components/CommentTray/CommentTray.tsx index 538df0a0a..fb3636e06 100644 --- a/src/social/v4/components/CommentTray/CommentTray.tsx +++ b/src/v4/social/components/CommentTray/CommentTray.tsx @@ -1,7 +1,8 @@ import React, { useState } from 'react'; -import { CommentList } from '~/social/v4/internal-components/CommentList'; -import { StoryCommentComposeBar } from '~/social/v4/internal-components/StoryCommentComposeBar'; -import { MobileSheetComposeBarContainer } from '~/social/v4/internal-components/StoryViewer/styles'; + +import { CommentList } from '../../internal-components/CommentList'; +import { MobileSheetComposeBarContainer } from '../../internal-components/StoryViewer/styles'; +import { StoryCommentComposeBar } from '../../internal-components/StoryCommentComposeBar'; const REPLIES_PER_PAGE = 5; diff --git a/src/social/v4/components/CommentTray/index.ts b/src/v4/social/components/CommentTray/index.ts similarity index 100% rename from src/social/v4/components/CommentTray/index.ts rename to src/v4/social/components/CommentTray/index.ts diff --git a/src/v4/social/components/CommentTray/ui.stories.tsx b/src/v4/social/components/CommentTray/ui.stories.tsx new file mode 100644 index 000000000..9cd1e8b37 --- /dev/null +++ b/src/v4/social/components/CommentTray/ui.stories.tsx @@ -0,0 +1,98 @@ +import React from 'react'; +import { ComponentStory, ComponentMeta } from '@storybook/react'; +import { ThemeProvider } from 'styled-components'; +import { CustomizationProvider } from '~/v4/core/providers/CustomizationProvider'; +import { CommentTray } from './CommentTray'; +import buildGlobalTheme from '~/core/providers/UiKitProvider/theme'; +import { theme } from '../../theme'; + +export default { + title: 'Components/CommentTray', + component: CommentTray, + decorators: [ + (Story) => ( + + +
+ +
+
+
+ ), + ], +} as ComponentMeta; + +const Template: ComponentStory = (args) => ; + +export const Default = Template.bind({}); +Default.args = { + pageId: '*', + storyId: 'story123', + commentId: 'comment123', + referenceType: 'story', + referenceId: 'story123', + replyTo: 'user123', + isReplying: false, + limit: 5, + isOpen: true, + isJoined: true, + allowCommentInStory: true, + onClose: () => {}, + onClickReply: () => {}, + onCancelReply: () => {}, +}; + +export const Replying = Template.bind({}); +Replying.args = { + ...Default.args, + isReplying: true, +}; + +export const NotJoined = Template.bind({}); +NotJoined.args = { + ...Default.args, + isJoined: false, +}; diff --git a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css new file mode 100644 index 000000000..6a9a04467 --- /dev/null +++ b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css @@ -0,0 +1,100 @@ +.hyperlinkFormContainer { + padding: var(--asc-spacing-m1); + border-radius: var(--asc-border-radius-md); + } + + .form { + display: flex; + flex-direction: column; + gap: var(--asc-spacing-l1); + } + + .inputContainer { + display: flex; + flex-direction: column; + gap: var(--asc-spacing-xxs2); + } + + .input { + width: 100%; + padding: var(--asc-spacing-s1); + border: none; + border-bottom: 1px solid var(--asc-color-base-shade4); + outline: none; + color: inherit; + } + + .input.hasError { + border-bottom-color: var(--asc-color-alert-default); + color: var(--asc-color-alert-default); + } + + .label { + display: block; + } + + .label::after { + content: none; + color: var(--asc-color-alert-default); + } + + .label.required::after { + content: '*'; + } + + .description { + color: var(--asc-color-base-shade2); + } + + .errorText { + color: var(--asc-color-alert-default); + } + + .characterCount { + color: var(--asc-color-base-shade1); + text-align: right; + margin-top: 0.3rem; + } + + .headerContainer { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 var(--asc-spacing-m1); + } + + .labelContainer { + display: flex; + justify-content: space-between; + align-items: center; + } + + .headerTitle { + color: var(--asc-color-base-default); + } + + .styledSecondaryButton { + color: var(--asc-color-base-default); + } + + .removeIcon { + width: 1.5rem; + height: 1.5rem; + fill: var(--asc-color-alert-default); + } + + .removeLinkButton { + display: flex; + justify-content: flex-start; + align-items: center; + gap: var(--asc-spacing-s1); + color: var(--asc-color-alert-default); + border-radius: 0; + } + + .divider { + width: 100%; + height: 0.0625rem; + align-self: stretch; + background-color: var(--asc-color-base-shade4); + } \ No newline at end of file diff --git a/src/social/v4/components/HyperLinkConfig/HyperLinkConfig.tsx b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx similarity index 70% rename from src/social/v4/components/HyperLinkConfig/HyperLinkConfig.tsx rename to src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx index d9e3d924e..7db0b8cdc 100644 --- a/src/social/v4/components/HyperLinkConfig/HyperLinkConfig.tsx +++ b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx @@ -1,38 +1,21 @@ import React from 'react'; -import { BottomSheet } from '~/core/v4/components'; - -import { - MobileSheet, - MobileSheetContainer, - MobileSheetContent, - MobileSheetHeader, -} from '~/core/v4/components/BottomSheet/styles'; - import { useIntl } from 'react-intl'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; -import { - Description, - Form, - HyperlinkFormContainer, - Input, - Label, - ErrorText, - InputContainer, - CharacterCount, - LabelContainer, - HeaderContainer, - HeaderTitle, - StyledSecondaryButton, - RemoveLinkButton, - RemoveIcon, - Divider, -} from './styles'; import { SecondaryButton } from '~/core/components/Button'; import { confirm } from '~/core/components/Confirm'; import useSDK from '~/core/hooks/useSDK'; -import { useCustomization } from '../../providers/CustomizationProvider'; +import { BottomSheet } from '~/v4/core/components'; +import { + MobileSheet, + MobileSheetContainer, + MobileSheetContent, + MobileSheetHeader, +} from '~/v4/core/components/BottomSheet/styles'; +import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; +import { Trash2Icon } from '~/icons'; +import styles from './HyperLinkConfig.module.css'; interface HyperLinkConfigProps { pageId: '*'; @@ -56,7 +39,7 @@ export const HyperLinkConfig = ({ const componentId = 'hyper_link_config_component'; const { getConfig } = useCustomization(); const componentConfig = getConfig(`${pageId}/${componentId}/*`); - const componentTheme = componentConfig?.component_theme.light_theme || {}; + const componentTheme = componentConfig?.theme.light || {}; const cancelButtonConfig = getConfig(`*/hyper_link_config_component/cancel_button`); const doneButtonConfig = getConfig(`*/hyper_link_config_component/done_button`); @@ -67,7 +50,6 @@ export const HyperLinkConfig = ({ const schema = z.object({ url: z.string().refine(async (value) => { if (!value) return true; - // since validateUrls() will throw an error if the url is not whitelisted so need to catch it and return false instead const hasWhitelistedUrls = await client?.validateUrls([value]).catch(() => false); return hasWhitelistedUrls; }, formatMessage({ id: 'storyCreation.hyperlink.validation.error.whitelisted' })), @@ -76,8 +58,6 @@ export const HyperLinkConfig = ({ .optional() .refine(async (value) => { if (!value) return true; - // since validateUrls() will throw an error if the url is not whitelisted so need to catch it and return false instead - // TO FIX: use schema.safeParseAsync() const hasBlockedWord = await client?.validateTexts([value]).catch(() => false); return hasBlockedWord; }, formatMessage({ id: 'storyCreation.hyperlink.validation.error.blocked' })), @@ -137,7 +117,7 @@ export const HyperLinkConfig = ({ color: componentTheme?.secondary_color, }} > - +
{cancelButtonConfig?.cancel_button_text || formatMessage({ id: 'storyCreation.hyperlink.bottomSheet.cancel' })} @@ -145,10 +125,10 @@ export const HyperLinkConfig = ({ )} - +
{formatMessage({ id: 'storyCreation.hyperlink.bottomSheet.title' })} - - + {doneButtonConfig?.done_button_text || formatMessage({ id: 'storyCreation.hyperlink.bottomSheet.submit' })} {doneButtonConfig?.done_icon && ( )} - - + +
- -
- -
- } - onCancel={onCancel} - > - {content} - -); - -let spawnNewConfirm: any; // for modfying ConfirmContainer state outside +}: any) => { + return ( + + {type === 'confirm' && ( + + {cancelText} + + )} + + {okText} + + + } + onCancel={onCancel} + > + {content} + + ); +}; // rendered by provider, to allow spawning of confirm from confirm function below -export const ConfirmContainer = () => { - const [confirm, setConfirm] = useState(null); - spawnNewConfirm = (confirmData: any) => { - setConfirm(confirmData); - }; +export const ConfirmComponent = () => { + const { confirmData, closeConfirm } = useConfirmContext(); - if (!confirm) return null; + if (!confirmData) return null; - const closeConfirm = () => setConfirm(null); + const onCancel = () => { + closeConfirm(); + confirmData?.onCancel && confirmData.onCancel(); + }; - const attachCanceling = (fn: any) => () => { + const onOk = () => { closeConfirm(); - fn && fn(); + confirmData?.onOk && confirmData.onOk(); }; - return ( - - ); + return ; }; -/* - Usage: - confirm({ - title: 'Delete post', - content: - 'This post will be permanently deleted. You’ll no longer to see and find this post. Continue?', - okText: 'Delete', - onOk: onDelete, - }); - - This interface rely on ConfirmContainer being rendered by UIKITProvider in the react tree -*/ -export const confirm = (confirmData: any) => spawnNewConfirm({ ...confirmData, type: 'confirm' }); - -export const info = (data: any) => - spawnNewConfirm({ ...data, type: 'info', OkButton: PrimaryButton }); - export default Confirm; diff --git a/src/core/providers/ConfirmProvider.tsx b/src/core/providers/ConfirmProvider.tsx new file mode 100644 index 000000000..2e70970d5 --- /dev/null +++ b/src/core/providers/ConfirmProvider.tsx @@ -0,0 +1,52 @@ +import React, { createContext, ReactNode, useContext, useState } from 'react'; +import { PrimaryButton } from '../components/Button/styles'; + +type ConfirmType = { + onCancel?: () => void; + onOk?: () => void; + type?: 'confirm' | 'info'; + OkButton?: ReactNode; + CancelButton?: ReactNode; + title?: ReactNode; + content?: ReactNode; + okText?: ReactNode; + cancelText?: ReactNode; + 'data-qa-anchor'?: string; +}; + +interface ConfirmContextProps { + confirmData: ConfirmType | null; + confirm: (data: ConfirmType) => void; + info: (data: ConfirmType) => void; + closeConfirm: () => void; +} + +export const ConfirmContext = createContext({ + confirmData: null, + confirm: () => {}, + info: () => {}, + closeConfirm: () => {}, +}); + +export const useConfirmContext = () => useContext(ConfirmContext); + +export const ConfirmProvider: React.FC = ({ children }) => { + const [confirmData, setConfirmData] = useState(null); + + const closeConfirm = () => { + setConfirmData(null); + }; + + const confirm = (confirmData: ConfirmType) => { + setConfirmData({ ...confirmData, type: 'confirm' }); + }; + + const info = (data: ConfirmType) => + setConfirmData({ ...data, type: 'info', OkButton: PrimaryButton }); + + return ( + + {children} + + ); +}; diff --git a/src/core/providers/UiKitProvider/index.tsx b/src/core/providers/UiKitProvider/index.tsx index 876029570..70d898c5b 100644 --- a/src/core/providers/UiKitProvider/index.tsx +++ b/src/core/providers/UiKitProvider/index.tsx @@ -5,7 +5,7 @@ import { Client as ASCClient } from '@amityco/ts-sdk'; import { ThemeProvider } from 'styled-components'; import { NotificationsContainer } from '~/core/components/Notification'; -import { ConfirmContainer } from '~/core/components/Confirm'; +import { ConfirmComponent } from '~/core/components/Confirm'; import ConfigProvider from '~/social/providers/ConfigProvider'; import Localization from './Localization'; import buildGlobalTheme from './theme'; @@ -20,6 +20,7 @@ import PostRendererProvider, { } from '~/social/providers/PostRendererProvider'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { ConfirmProvider } from '../ConfirmProvider'; interface UiKitProviderProps { apiKey: string; @@ -130,20 +131,22 @@ const UiKitProvider = ({ - - - - {children} - - - - - + + + + + {children} + + + + + + diff --git a/src/social/components/Comment/index.tsx b/src/social/components/Comment/index.tsx index 8b7d1847e..2254bef56 100644 --- a/src/social/components/Comment/index.tsx +++ b/src/social/components/Comment/index.tsx @@ -1,7 +1,6 @@ import React, { memo, useState } from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; -import { confirm } from '~/core/components/Confirm'; import useComment from '~/social/hooks/useComment'; import CommentComposeBar from '~/social/components/CommentComposeBar'; import CommentList from '~/social/components/CommentList'; @@ -39,6 +38,7 @@ import useCommentPermission from '~/social/hooks/useCommentPermission'; import useCommentSubscription from '~/social/hooks/useCommentSubscription'; import { ERROR_RESPONSE } from '~/social/constants'; +import { useConfirmContext } from '~/core/providers/ConfirmProvider'; const REPLIES_PER_PAGE = 5; @@ -90,6 +90,7 @@ interface CommentProps { const Comment = ({ commentId, readonly }: CommentProps) => { const comment = useComment(commentId); const post = usePost(comment?.referenceId); + const { confirm } = useConfirmContext(); const commentAuthor = useUser(comment?.userId); const commentAuthorAvatar = useFile(commentAuthor?.avatarFileId); diff --git a/src/social/components/CommentComposeBar/index.tsx b/src/social/components/CommentComposeBar/index.tsx index 4ebd7e5d6..28cbb6ac4 100644 --- a/src/social/components/CommentComposeBar/index.tsx +++ b/src/social/components/CommentComposeBar/index.tsx @@ -9,8 +9,6 @@ import useSDK from '~/core/hooks/useSDK'; import { LoadingIndicator } from '~/core/components/ProgressBar/styles'; import { useIntl, FormattedMessage } from 'react-intl'; -import { info } from '~/core/components/Confirm'; - import { Avatar, CommentComposeBarContainer, @@ -21,6 +19,7 @@ import { import { backgroundImage as UserImage } from '~/icons/User'; import { useCustomComponent } from '~/core/providers/CustomComponentsProvider'; import useImage from '~/core/hooks/useImage'; +import { useConfirmContext } from '~/core/providers/ConfirmProvider'; const TOTAL_MENTIONEES_LIMIT = 30; const COMMENT_LENGTH_LIMIT = 50000; @@ -42,6 +41,7 @@ const CommentComposeBar = ({ const post = usePost(postId); const { currentUserId } = useSDK(); + const { info } = useConfirmContext(); const user = useUser(currentUserId); const avatarFileUrl = useImage({ fileId: user?.avatarFileId, imageSize: 'small' }); const { text, markup, mentions, mentionees, metadata, onChange, queryMentionees } = diff --git a/src/social/components/CommunityCreationModal/index.tsx b/src/social/components/CommunityCreationModal/index.tsx index 04209be04..42ded2835 100644 --- a/src/social/components/CommunityCreationModal/index.tsx +++ b/src/social/components/CommunityCreationModal/index.tsx @@ -4,19 +4,19 @@ import { CommunityRepository } from '@amityco/ts-sdk'; import { useIntl } from 'react-intl'; import Modal from '~/core/components/Modal'; -import { confirm } from '~/core/components/Confirm'; -import { CommunityForm } from './styles'; import CreateCommunityForm from '../CommunityForm/CreateCommunityForm'; import { useCustomComponent } from '~/core/providers/CustomComponentsProvider'; +import { useConfirmContext } from '~/core/providers/ConfirmProvider'; interface CommunityCreationModalProps { isOpen: boolean; - onClose: (communityId: string) => void; + onClose: (communityId?: string) => void; } const CommunityCreationModal = ({ isOpen, onClose }: CommunityCreationModalProps) => { const { formatMessage } = useIntl(); + const { confirm } = useConfirmContext(); if (!isOpen) return null; @@ -26,7 +26,7 @@ const CommunityCreationModal = ({ isOpen, onClose }: CommunityCreationModalProps content: formatMessage({ id: 'CommunityCreationModal.content' }), cancelText: formatMessage({ id: 'CommunityCreationModal.cancelText' }), okText: formatMessage({ id: 'CommunityCreationModal.okText' }), - onOk: onClose, + onOk: () => onClose(), }); const handleSubmit = async (data: Parameters[0]) => { diff --git a/src/social/components/CommunityInfo/index.tsx b/src/social/components/CommunityInfo/index.tsx index 2ee57194f..8adfd8fbe 100644 --- a/src/social/components/CommunityInfo/index.tsx +++ b/src/social/components/CommunityInfo/index.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { CommunityPostSettings } from '@amityco/ts-sdk'; import UICommunityInfo from './UICommunityInfo'; -import { leaveCommunityConfirmModal } from './leaveScenarioModals'; import { useCommunityInfo } from './hooks'; import { useNavigation } from '~/social/providers/NavigationProvider'; @@ -10,6 +9,8 @@ import useSDK from '~/core/hooks/useSDK'; import { useStoryContext } from '~/v4/social/providers/StoryProvider'; import { checkStoryPermission } from '~/utils'; +import { FormattedMessage } from 'react-intl'; +import { useConfirmContext } from '~/core/providers/ConfirmProvider'; interface CommunityInfoProps { communityId: string; @@ -40,6 +41,8 @@ const CommunityInfo = ({ communityId, stories }: CommunityInfoProps) => { canReviewCommunityPosts, } = useCommunityInfo(communityId); + const { info, confirm } = useConfirmContext(); + const categoryNames = (communityCategories || []).map((category) => category.name); if (community == null) return null; @@ -47,6 +50,15 @@ const CommunityInfo = ({ communityId, stories }: CommunityInfoProps) => { const canLeaveCommunity = community.isJoined || false; const { membersCount, description, isJoined } = community; + const leaveCommunityConfirmModal = ({ onOk }: { onOk: () => void }) => + confirm({ + 'data-qa-anchor': 'leave-community', + title: , + content: , + okText: , + onOk: () => onOk(), + }); + return ( { onOk: () => leaveCommunity(), }) } - setStoryFile={setFile} + setStoryFile={(storyFile) => setFile(storyFile)} haveStories={haveStories || false} haveStoryPermission={haveStoryPermission} isStorySyncing={isStorySyncing || false} diff --git a/src/social/components/CommunityInfo/leaveScenarioModals.tsx b/src/social/components/CommunityInfo/leaveScenarioModals.tsx deleted file mode 100644 index 8991c8c54..000000000 --- a/src/social/components/CommunityInfo/leaveScenarioModals.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React from 'react'; -import { FormattedMessage } from 'react-intl'; -import { info, confirm } from '~/core/components/Confirm'; - -const LAST_MODERATOR_ERROR_CODE = 400317; -const ONLY_ONE_MODERATOR_ERROR_CODE = 400318; - -const lastModeratorModal = () => - info({ - title: , - content: , - okText: , - }); - -const onlyOneModeratorModal = () => - info({ - title: , - content: , - okText: , - }); - -// async function onClickLeaveCommunityOk( -// communityId: string, -// leaveCommunity: (communityId: string) => void, -// ) { -// try { -// await leaveCommunity(communityId); -// } catch (error) { -// if (error instanceof Error) { -// // Member > 0, moderator === 1 -// if (error.code === LAST_MODERATOR_ERROR_CODE) { -// onlyOneModeratorModal(); -// } -// // Member === 1, moderator === 1 -// if (error.code === ONLY_ONE_MODERATOR_ERROR_CODE) { -// lastModeratorModal(); -// } -// } -// } -// } - -export const leaveCommunityConfirmModal = ({ onOk }: { onOk: () => void }) => - confirm({ - 'data-qa-anchor': 'leave-community', - title: , - content: , - okText: , - onOk: () => onOk(), - }); diff --git a/src/social/components/CommunityMembers/CommunityMemberItem.tsx b/src/social/components/CommunityMembers/CommunityMemberItem.tsx index 850fa029f..1b00a70d4 100644 --- a/src/social/components/CommunityMembers/CommunityMemberItem.tsx +++ b/src/social/components/CommunityMembers/CommunityMemberItem.tsx @@ -6,13 +6,13 @@ import OptionMenu from '~/core/components/OptionMenu'; import UserHeader from '~/social/components/UserHeader'; import useUser from '~/core/hooks/useUser'; import { MemberInfo, CommunityMemberContainer } from './styles'; -import { confirm } from '~/core/components/Confirm'; import { isModerator } from '~/helpers/permissions'; import { MemberRoles } from '~/social/constants'; import { isNonNullable } from '~/helpers/utils'; import useUserFlaggedByMe from '~/social/hooks/useUserFlaggedByMe'; import useUserSubscription from '~/social/hooks/useUserSubscription'; import { SubscriptionLevels } from '@amityco/ts-sdk'; +import { useConfirmContext } from '~/core/providers/ConfirmProvider'; const { COMMUNITY_MODERATOR, CHANNEL_MODERATOR } = MemberRoles; @@ -45,6 +45,7 @@ const CommunityMemberItem = ({ const { formatMessage } = useIntl(); const { isFlaggedByMe, toggleFlagUser } = useUserFlaggedByMe(userId); const isGlobalBanned = user?.isGlobalBanned; + const { confirm } = useConfirmContext(); useUserSubscription({ userId, diff --git a/src/social/components/CommunityPermissions/utils.tsx b/src/social/components/CommunityPermissions/utils.tsx index 5f72c780f..49795c19e 100644 --- a/src/social/components/CommunityPermissions/utils.tsx +++ b/src/social/components/CommunityPermissions/utils.tsx @@ -1,7 +1,7 @@ import { CommunityPostSettings, CommunityRepository } from '@amityco/ts-sdk'; import React, { useCallback, useEffect, useState } from 'react'; import { FormattedMessage } from 'react-intl'; -import { info } from '~/core/components/Confirm'; + import useCommunity from '~/social/hooks/useCommunity'; // TODO: check CommunityPostSettings diff --git a/src/social/components/SideSectionMyCommunity/index.tsx b/src/social/components/SideSectionMyCommunity/index.tsx index 21add3978..fad1a9e3f 100644 --- a/src/social/components/SideSectionMyCommunity/index.tsx +++ b/src/social/components/SideSectionMyCommunity/index.tsx @@ -21,7 +21,7 @@ const SideSectionMyCommunity = ({ className, activeCommunity }: SideSectionMyCom const open = () => setIsOpen(true); - const close = (communityId: string) => { + const close = (communityId?: string) => { setIsOpen(false); communityId && onCommunityCreated(communityId); }; diff --git a/src/social/components/UserInfo/UIUserInfo.tsx b/src/social/components/UserInfo/UIUserInfo.tsx index 8888efd89..6f4057084 100644 --- a/src/social/components/UserInfo/UIUserInfo.tsx +++ b/src/social/components/UserInfo/UIUserInfo.tsx @@ -30,7 +30,6 @@ import { ProfileNameWrapper, } from './styles'; -import { confirm } from '~/core/components/Confirm'; import { isNonNullable } from '~/helpers/utils'; import useUser from '~/core/hooks/useUser'; import { UserRepository } from '@amityco/ts-sdk'; @@ -38,6 +37,7 @@ import useFollowersList from '~/core/hooks/useFollowersList'; import { useCustomComponent } from '~/core/providers/CustomComponentsProvider'; import useUserFlaggedByMe from '~/social/hooks/useUserFlaggedByMe'; import useFollowersCollection from '~/core/hooks/collections/useFollowersCollection'; +import { useConfirmContext } from '~/core/providers/ConfirmProvider'; interface UIUserInfoProps { userId?: string | null; @@ -88,6 +88,7 @@ const UIUserInfo = ({ const user = useUser(userId); const { formatMessage } = useIntl(); const { isFlaggedByMe } = useUserFlaggedByMe(userId || undefined); + const { confirm } = useConfirmContext(); const { followers: pendingUsers } = useFollowersCollection({ userId: currentUserId, diff --git a/src/social/components/post/Creator/index.tsx b/src/social/components/post/Creator/index.tsx index e006c9aab..ecde207da 100644 --- a/src/social/components/post/Creator/index.tsx +++ b/src/social/components/post/Creator/index.tsx @@ -9,7 +9,6 @@ import { CommunityPostSettings, } from '@amityco/ts-sdk'; -import { info } from '~/core/components/Confirm'; import useImage from '~/core/hooks/useImage'; import useUser from '~/core/hooks/useUser'; @@ -45,6 +44,7 @@ import useSDK from '~/core/hooks/useSDK'; import useSocialMention from '~/social/hooks/useSocialMention'; import useCommunityModeratorsCollection from '~/social/hooks/collections/useCommunityModeratorsCollection'; import { ERROR_RESPONSE } from '~/social/constants'; +import { useConfirmContext } from '~/core/providers/ConfirmProvider'; const useTargetData = ({ targetId, @@ -87,14 +87,6 @@ const useTargetData = ({ const MAX_FILES_PER_POST = 10; -const overCharacterModal = () => - info({ - title: , - content: , - okText: , - type: 'info', - }); - interface PostCreatorBarProps { className?: string; targetType: string; @@ -123,6 +115,7 @@ const PostCreatorBar = ({ const { currentUserId } = useSDK(); const { setNavigationBlocker } = useNavigation(); const user = useUser(currentUserId); + const { info } = useConfirmContext(); // default to me if (targetType === 'global' || targetType === 'myFeed') { @@ -165,6 +158,15 @@ const PostCreatorBar = ({ }); const [isCreating, setIsCreating] = useState(false); + const overCharacterModal = () => { + info({ + title: , + content: , + okText: , + type: 'info', + }); + }; + async function onCreatePost() { if (!target.targetId) return; try { diff --git a/src/social/components/post/PollComposer/PollModal.tsx b/src/social/components/post/PollComposer/PollModal.tsx index 819e61627..1b629c5bf 100644 --- a/src/social/components/post/PollComposer/PollModal.tsx +++ b/src/social/components/post/PollComposer/PollModal.tsx @@ -3,10 +3,10 @@ import { FormattedMessage, useIntl } from 'react-intl'; import { PollRepository } from '@amityco/ts-sdk'; import Modal from '~/core/components/Modal'; -import { confirm } from '~/core/components/Confirm'; import PollComposer from '~/social/components/post/PollComposer'; import { notification } from '~/core/components/Notification'; import { ERROR_RESPONSE } from '~/social/constants'; +import { useConfirmContext } from '~/core/providers/ConfirmProvider'; interface PollModalProps { targetId?: string | null; @@ -23,6 +23,7 @@ interface PollModalProps { const PollModal = ({ targetId, targetType, onClose, onCreatePoll }: PollModalProps) => { const [isDirty, setDirty] = useState(false); const { formatMessage } = useIntl(); + const { confirm } = useConfirmContext(); const handleSubmit = async ( data: Parameters[0], diff --git a/src/social/components/post/PollComposer/index.tsx b/src/social/components/post/PollComposer/index.tsx index af707ab21..988d61987 100644 --- a/src/social/components/post/PollComposer/index.tsx +++ b/src/social/components/post/PollComposer/index.tsx @@ -27,8 +27,9 @@ import InputCounter, { COUNTER_VALUE_PLACEHOLDER } from '~/core/components/Input import AnswerTypeSelector from '~/social/components/post/PollComposer/AnswerTypeSelector'; import Button from '~/core/components/Button'; import { MAXIMUM_MENTIONEES } from '~/social/constants'; -import { info } from '~/core/components/Confirm'; + import { PollRepository } from '@amityco/ts-sdk'; +import { useConfirmContext } from '~/core/providers/ConfirmProvider'; const MAX_QUESTION_LENGTH = 500; const MIN_OPTIONS_AMOUNT = 2; @@ -78,6 +79,7 @@ const PollComposer = ({ onChange: mentionOnChange, } = useSocialMention({ targetId: targetId || undefined, targetType }); const [submitting, setSubmitting] = useState(false); + const { info } = useConfirmContext(); const defaultValues: FormValues = { question: '', diff --git a/src/social/components/post/Post/DefaultPostRenderer.tsx b/src/social/components/post/Post/DefaultPostRenderer.tsx index 6aae2e5ad..7058e0dbe 100644 --- a/src/social/components/post/Post/DefaultPostRenderer.tsx +++ b/src/social/components/post/Post/DefaultPostRenderer.tsx @@ -1,7 +1,6 @@ import React, { useState } from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; import Button, { PrimaryButton } from '~/core/components/Button'; -import { confirm, info } from '~/core/components/Confirm'; import Modal from '~/core/components/Modal'; import { notification } from '~/core/components/Notification'; import { isNonNullable } from '~/helpers/utils'; @@ -23,6 +22,7 @@ import useCommunity from '~/social/hooks/useCommunity'; import useSDK from '~/core/hooks/useSDK'; import usePostSubscription from '~/social/hooks/usePostSubscription'; import { SubscriptionLevels } from '@amityco/ts-sdk'; +import { useConfirmContext } from '~/core/providers/ConfirmProvider'; // Number of lines to show in a text post before truncating. const MAX_TEXT_LINES_DEFAULT = 8; @@ -52,6 +52,7 @@ const DefaultPostRenderer = ({ const [isEditing, setIsEditing] = useState(false); const openEditingPostModal = () => setIsEditing(true); const closeEditingPostModal = () => setIsEditing(false); + const { info, confirm } = useConfirmContext(); function showHasBeenReviewedMessageIfNeeded(error: unknown) { if (error instanceof Error) { @@ -119,15 +120,16 @@ const DefaultPostRenderer = ({ } }; - const confirmDeletePost = () => + const confirmDeletePost = () => { confirm({ title: formatMessage({ id: 'post.deletePost' }), content: formatMessage({ id: isPostUnderReview ? 'post.confirmPendingDelete' : 'post.confirmDelete', }), okText: formatMessage({ id: 'delete' }), - onOk: handleDeletePost, + onOk: () => handleDeletePost?.(post?.postId), }); + }; const pollPost = childrenPosts.find((childPost) => childPost.dataType === 'poll'); diff --git a/src/social/pages/CommunityEdit/ExtraAction.tsx b/src/social/pages/CommunityEdit/ExtraAction.tsx index fee50c810..cd6e93219 100644 --- a/src/social/pages/CommunityEdit/ExtraAction.tsx +++ b/src/social/pages/CommunityEdit/ExtraAction.tsx @@ -1,7 +1,6 @@ import React, { ReactNode } from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; -import { confirm } from '~/core/components/Confirm'; import { ExtraActionContainer, ExtraActionContainerHeader, @@ -13,6 +12,7 @@ import { } from './styles'; import { CommunityRepository } from '@amityco/ts-sdk'; +import { useConfirmContext } from '~/core/providers/ConfirmProvider'; interface ExtraActionProps { title?: ReactNode; @@ -81,6 +81,7 @@ export const CloseCommunityAction = ({ onCommunityClosed, }: CloseCommunityActionProps) => { const { formatMessage } = useIntl(); + const { confirm } = useConfirmContext(); const closeConfirm = () => confirm({ diff --git a/src/social/pages/UserFeed/Followers/FollowersList.tsx b/src/social/pages/UserFeed/Followers/FollowersList.tsx index 9f83dbfdb..5faa41aa2 100644 --- a/src/social/pages/UserFeed/Followers/FollowersList.tsx +++ b/src/social/pages/UserFeed/Followers/FollowersList.tsx @@ -9,7 +9,7 @@ import useUser from '~/core/hooks/useUser'; import { UserRepository } from '@amityco/ts-sdk'; import { useNavigation } from '~/social/providers/NavigationProvider'; import { notification } from '~/core/components/Notification'; -import { confirm } from '~/core/components/Confirm'; + import { Grid, Header, @@ -23,6 +23,7 @@ import useUserFlaggedByMe from '~/social/hooks/useUserFlaggedByMe'; import useFollowersSubscription from '~/social/hooks/useFollowersSubscription'; import useSDK from '~/core/hooks/useSDK'; import useFollowersCollection from '~/core/hooks/collections/useFollowersCollection'; +import { useConfirmContext } from '~/core/providers/ConfirmProvider'; interface UserItemProps { profileUserId: string; @@ -35,6 +36,7 @@ export const UserItem = ({ profileUserId, currentUserId, userId, onClick }: User const user = useUser(userId); const avatarFileUrl = useImage({ fileId: user?.avatarFileId, imageSize: 'small' }); const { onClickUser } = useNavigation(); + const { confirm } = useConfirmContext(); const { formatMessage } = useIntl(); const { isFlaggedByMe, toggleFlagUser } = useUserFlaggedByMe(userId || undefined); diff --git a/src/social/pages/UserFeed/Followers/FollowingsList.tsx b/src/social/pages/UserFeed/Followers/FollowingsList.tsx index f84f57cb9..d0d105f2b 100644 --- a/src/social/pages/UserFeed/Followers/FollowingsList.tsx +++ b/src/social/pages/UserFeed/Followers/FollowingsList.tsx @@ -9,7 +9,7 @@ import useUser from '~/core/hooks/useUser'; import { UserRepository } from '@amityco/ts-sdk'; import { useNavigation } from '~/social/providers/NavigationProvider'; import { notification } from '~/core/components/Notification'; -import { confirm } from '~/core/components/Confirm'; + import { Grid, Header, @@ -22,6 +22,7 @@ import useUserFlaggedByMe from '~/social/hooks/useUserFlaggedByMe'; import useFollowingsSubscription from '~/social/hooks/useFollowingsSubscription'; import useSDK from '~/core/hooks/useSDK'; import useFollowingsCollection from '~/core/hooks/collections/useFollowingsCollection'; +import { useConfirmContext } from '~/core/providers/ConfirmProvider'; interface UserItemProps { profileUserId: string; @@ -33,6 +34,7 @@ interface UserItemProps { export const UserItem = ({ profileUserId, currentUserId, userId, onClick }: UserItemProps) => { const user = useUser(userId); const { onClickUser } = useNavigation(); + const { confirm } = useConfirmContext(); const { formatMessage } = useIntl(); const { isFlaggedByMe, toggleFlagUser } = useUserFlaggedByMe(userId); diff --git a/src/social/providers/NavigationProvider.tsx b/src/social/providers/NavigationProvider.tsx index 718d51476..435b56b3e 100644 --- a/src/social/providers/NavigationProvider.tsx +++ b/src/social/providers/NavigationProvider.tsx @@ -1,6 +1,7 @@ import React, { createContext, useCallback, useContext, useState, useMemo, ReactNode } from 'react'; import { FormattedMessage } from 'react-intl'; -import { confirm } from '~/core/components/Confirm'; +import { useConfirmContext } from '~/core/providers/ConfirmProvider'; + import { PageTypes } from '~/social/constants'; type Page = @@ -74,12 +75,6 @@ let defaultValue: ContextValue = { onBack: () => {}, }; -const defaultAskForConfirmation = ({ onSuccess: onOk, ...params }: { onSuccess: () => void }) => - confirm({ - ...params, - onOk, - }); - export const defaultNavigationBlocker = { title: , content: , @@ -131,7 +126,7 @@ interface NavigationProviderProps { } export default function NavigationProvider({ - askForConfirmation = defaultAskForConfirmation, + askForConfirmation, children, onChangePage: onChangePageProp, onClickCategory, @@ -156,11 +151,15 @@ export default function NavigationProvider({ | undefined >(); + const { confirm } = useConfirmContext(); + + const confirmation = askForConfirmation ?? confirm; + const confirmPageChange = useCallback(async () => { if (navigationBlocker) { // for more info about this, see https://ekoapp.atlassian.net/browse/UP-3462?focusedCommentId=77155 return new Promise((resolve) => { - askForConfirmation({ + confirmation({ ...navigationBlocker, onSuccess: () => { setNavigationBlocker?.(undefined); diff --git a/src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx b/src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx index adaa470aa..3dded2ebb 100644 --- a/src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx +++ b/src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx @@ -5,11 +5,11 @@ import HomeIndicator from '../HomeIndicator'; import styles from './styles.module.css'; import InputText from '~/v4/core/components/InputText'; import useMention from '~/v4/chat/hooks/useMention'; -import { confirm } from '~/v4/core/components/ConfirmModal'; import { useIntl } from 'react-intl'; import useChannelPermission from '~/v4/chat/hooks/useChannelPermission'; import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; import { notification } from '~/v4/chat/components/LiveChatNotification'; +import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; const COMPOSEBAR_MAX_CHARACTER_LIMIT = 200; @@ -42,6 +42,7 @@ export const AmityLiveChatMessageComposeBar = ({ const [mentionList, setMentionList] = useState<{ [key: ComposeBarMention['id']]: ComposeBarMention; }>({}); + const { confirm } = useConfirmContext(); const { getConfig } = useCustomization(); const componentConfig = getConfig('live_chat/message_composer/*'); diff --git a/src/v4/chat/components/AmityLiveChatMessageList/index.tsx b/src/v4/chat/components/AmityLiveChatMessageList/index.tsx index c40d6ac11..5ba3aefb7 100644 --- a/src/v4/chat/components/AmityLiveChatMessageList/index.tsx +++ b/src/v4/chat/components/AmityLiveChatMessageList/index.tsx @@ -6,13 +6,13 @@ import useSDK from '~/core/hooks/useSDK'; import AmityLiveChatMessageSenderView from '../AmityLiveChatMessageSenderView'; import AmityLiveChatMessageReceiverView from '../AmityLiveChatMessageReceiverView'; import { copyMessage, deleteMessage, flagMessage } from '~/v4/utils'; -import { confirm } from '~/v4/core/components/ConfirmModal'; import useMessagesCollection from '~/chat/hooks/collections/useMessagesCollection'; import { FormattedMessage, useIntl } from 'react-intl'; import { Typography } from '~/v4/core/components'; import Redo from '~/v4/icons/Redo'; import { notification } from '~/v4/chat/components/LiveChatNotification'; import { unFlagMessage } from '~/v4/utils/unFlagMessage'; +import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; interface AmityLiveChatMessageListProps { channel: Amity.Channel; @@ -27,6 +27,7 @@ export const AmityLiveChatMessageList = ({ const containerRef = React.useRef(null); const { formatMessage } = useIntl(); const [height, setHeight] = React.useState(undefined); + const { confirm } = useConfirmContext(); const { messages: rawMessages, diff --git a/src/v4/core/components/ConfirmModal/index.tsx b/src/v4/core/components/ConfirmModal/index.tsx index f5d7d1e18..13dab2ecf 100644 --- a/src/v4/core/components/ConfirmModal/index.tsx +++ b/src/v4/core/components/ConfirmModal/index.tsx @@ -1,8 +1,9 @@ -import React, { useState } from 'react'; -import Modal from '../Modal'; +import React from 'react'; +import Modal from '~/v4/core/components/Modal'; import { Button } from '~/v4/core/components/Button'; import clsx from 'clsx'; import styles from './styles.module.css'; +import { useConfirmContext } from '~/core/providers/ConfirmProvider'; const Confirm = ({ 'data-qa-anchor': dataQaAnchor = '', @@ -41,51 +42,26 @@ const Confirm = ({ } onCancel={onCancel} > -
{content}
+
{content}
); -let spawnNewConfirm: any; // for modfying ConfirmContainer state outside +export const ConfirmComponent = () => { + const { confirmData, closeConfirm } = useConfirmContext(); -// rendered by provider, to allow spawning of confirm from confirm function below -export const ConfirmContainer = () => { - const [confirm, setConfirm] = useState(null); - spawnNewConfirm = (confirmData: any) => { - setConfirm(confirmData); - }; - - if (!confirm) return null; + if (!confirmData) return null; - const closeConfirm = () => setConfirm(null); + const onCancel = () => { + closeConfirm(); + confirmData?.onCancel && confirmData.onCancel(); + }; - const attachCanceling = (fn: any) => () => { + const onOk = () => { closeConfirm(); - fn && fn(); + confirmData?.onOk && confirmData.onOk(); }; - return ( - - ); + return ; }; -/* - Usage: - confirm({ - title: 'Delete post', - content: - 'This post will be permanently deleted. You’ll no longer to see and find this post. Continue?', - okText: 'Delete', - onOk: onDelete, - }); - - This interface rely on ConfirmContainer being rendered by UIKITProvider in the react tree -*/ -export const confirm = (confirmData: any) => spawnNewConfirm({ ...confirmData, type: 'confirm' }); - -export const info = (data: any) => spawnNewConfirm({ ...data, type: 'info' }); - export default Confirm; diff --git a/src/v4/core/providers/AmityUIKitProvider.tsx b/src/v4/core/providers/AmityUIKitProvider.tsx index 7bace1f10..2c0097ee4 100644 --- a/src/v4/core/providers/AmityUIKitProvider.tsx +++ b/src/v4/core/providers/AmityUIKitProvider.tsx @@ -11,7 +11,7 @@ import PostRendererProvider from '~/social/providers/PostRendererProvider'; import NavigationProvider from '~/social/providers/NavigationProvider'; import ConfigProvider from '~/social/providers/ConfigProvider'; -import { ConfirmContainer } from '~/v4/core/components/ConfirmModal'; +import { ConfirmComponent } from '~/v4/core/components/ConfirmModal'; import { NotificationsContainer } from '~/v4/core/components/Notification'; import Localization from '~/core/providers/UiKitProvider/Localization'; @@ -24,6 +24,7 @@ import { PageBehaviorProvider } from './PageBehaviorProvider'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { UIStyles } from '~/core/providers/UiKitProvider/styles'; import AmityUIKitManager from '../AmityUIKitManager'; +import { ConfirmProvider } from '~/v4/core/providers/ConfirmProvider'; export type AmityUIKitConfig = Config; @@ -135,22 +136,24 @@ const AmityUIKitProvider: React.FC = ({ - - - - - {children} - - - - - - + + + + + + {children} + + + + + + + diff --git a/src/v4/core/providers/ConfirmProvider.tsx b/src/v4/core/providers/ConfirmProvider.tsx new file mode 100644 index 000000000..b290db3c9 --- /dev/null +++ b/src/v4/core/providers/ConfirmProvider.tsx @@ -0,0 +1,53 @@ +import React, { createContext, ReactNode, useContext, useState } from 'react'; +import { PrimaryButton } from '~/core/components/Button/styles'; + +type ConfirmType = { + onCancel?: () => void; + onOk?: () => void; + type?: 'confirm' | 'info'; + OkButton?: ReactNode; + CancelButton?: ReactNode; + title?: ReactNode; + content?: ReactNode; + okText?: ReactNode; + cancelText?: ReactNode; + 'data-qa-anchor'?: string; + theme?: 'light' | 'dark'; +}; + +interface ConfirmContextProps { + confirmData: ConfirmType | null; + confirm: (data: ConfirmType) => void; + info: (data: ConfirmType) => void; + closeConfirm: () => void; +} + +export const ConfirmContext = createContext({ + confirmData: null, + confirm: () => {}, + info: () => {}, + closeConfirm: () => {}, +}); + +export const useConfirmContext = () => useContext(ConfirmContext); + +export const ConfirmProvider: React.FC = ({ children }) => { + const [confirmData, setConfirmData] = useState(null); + + const closeConfirm = () => { + setConfirmData(null); + }; + + const confirm = (confirmData: ConfirmType) => { + setConfirmData({ ...confirmData, type: 'confirm' }); + }; + + const info = (data: ConfirmType) => + setConfirmData({ ...data, type: 'info', OkButton: PrimaryButton }); + + return ( + + {children} + + ); +}; diff --git a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx index 7db0b8cdc..f44cb3af2 100644 --- a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx +++ b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx @@ -4,7 +4,7 @@ import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; import { SecondaryButton } from '~/core/components/Button'; -import { confirm } from '~/core/components/Confirm'; + import useSDK from '~/core/hooks/useSDK'; import { BottomSheet } from '~/v4/core/components'; import { @@ -16,6 +16,7 @@ import { import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; import { Trash2Icon } from '~/icons'; import styles from './HyperLinkConfig.module.css'; +import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; interface HyperLinkConfigProps { pageId: '*'; @@ -36,6 +37,7 @@ export const HyperLinkConfig = ({ onSubmit, onRemove, }: HyperLinkConfigProps) => { + const { confirm } = useConfirmContext(); const componentId = 'hyper_link_config_component'; const { getConfig } = useCustomization(); const componentConfig = getConfig(`${pageId}/${componentId}/*`); diff --git a/src/v4/social/components/ViewStoryPage/index.tsx b/src/v4/social/components/ViewStoryPage/index.tsx index 176a5ae5b..f188769a7 100644 --- a/src/v4/social/components/ViewStoryPage/index.tsx +++ b/src/v4/social/components/ViewStoryPage/index.tsx @@ -10,7 +10,7 @@ import { notification } from '~/core/components/Notification'; import { useMedia } from 'react-use'; import useStories from '~/social/hooks/useStories'; import useSDK from '~/core/hooks/useSDK'; -import { confirm } from '~/core/components/Confirm'; + import { isNonNullable } from '~/v4/helpers/utils'; import { ArrowLeftCircle, ArrowRightCircle, Trash2Icon } from '~/icons'; @@ -22,6 +22,7 @@ import { renderers } from '../../internal-components/StoryViewer/Renderers'; import { checkStoryPermission } from '~/utils'; import { AmityDraftStoryPage } from '../../pages'; import { useStoryContext } from '../../providers/StoryProvider'; +import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; interface StoryViewerProps { pageId: 'story_page'; @@ -34,6 +35,7 @@ const StoryViewer = ({ pageId, targetId, duration = 5000, onClose }: StoryViewer const { getConfig, isExcluded } = useCustomization(); const pageConfig = getConfig(`${pageId}/*/*`); const isPageExcluded = isExcluded(`${pageId}/*/*`); + const { confirm } = useConfirmContext(); if (isPageExcluded) return null; diff --git a/src/v4/social/internal-components/Comment/index.tsx b/src/v4/social/internal-components/Comment/index.tsx index 2e4a511fe..3c9eaafe4 100644 --- a/src/v4/social/internal-components/Comment/index.tsx +++ b/src/v4/social/internal-components/Comment/index.tsx @@ -1,7 +1,6 @@ import React, { memo, useState } from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; -import { confirm } from '~/core/components/Confirm'; import useComment from '~/social/hooks/useComment'; import { notification } from '~/core/components/Notification'; @@ -50,6 +49,7 @@ import { CommentList } from '~/v4/social/internal-components/CommentList'; import { ReactionList } from '~/v4/social/components/ReactionList'; import { useTheme } from 'styled-components'; import useGetStoryByStoryId from '../../hooks/useGetStoryByStoryId'; +import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; const REPLIES_PER_PAGE = 5; @@ -119,6 +119,7 @@ const Comment = ({ const story = useGetStoryByStoryId(comment?.referenceId); const [bottomSheet, setBottomSheet] = useState(false); const [selectedCommentId, setSelectedCommentId] = useState(''); + const { confirm } = useConfirmContext(); const commentAuthor = useUser(comment?.userId); const commentAuthorAvatar = useImage({ fileId: commentAuthor?.avatarFileId, imageSize: 'small' }); diff --git a/src/v4/social/internal-components/CommentComposeBar/CommentComposeBar.tsx b/src/v4/social/internal-components/CommentComposeBar/CommentComposeBar.tsx index 5d0d310d5..fb353edfd 100644 --- a/src/v4/social/internal-components/CommentComposeBar/CommentComposeBar.tsx +++ b/src/v4/social/internal-components/CommentComposeBar/CommentComposeBar.tsx @@ -6,7 +6,6 @@ import { Mentionees, Metadata } from '~/v4/helpers/utils'; import useSDK from '~/core/hooks/useSDK'; import { LoadingIndicator } from '~/core/components/ProgressBar/styles'; import { FormattedMessage, useIntl } from 'react-intl'; -import { info } from '~/core/components/Confirm'; import { AddCommentButton, @@ -17,6 +16,7 @@ import { import { backgroundImage as UserImage } from '~/icons/User'; import useImage from '~/core/hooks/useImage'; +import { useConfirmContext } from '~/core/providers/ConfirmProvider'; const TOTAL_MENTIONEES_LIMIT = 30; const COMMENT_LENGTH_LIMIT = 50000; @@ -46,6 +46,7 @@ export const CommentComposeBar = ({ targetType: 'community', }); const { formatMessage } = useIntl(); + const { info } = useConfirmContext(); const commentInputRef = useRef(null); diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index ecbc702d3..a097fc000 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import { useIntl } from 'react-intl'; import { extractColors } from 'extract-colors'; -import { confirm } from '~/core/components/Confirm'; + import { readFileAsync } from '~/helpers'; import useUser from '~/core/hooks/useUser'; import useSDK from '~/core/hooks/useSDK'; @@ -22,6 +22,7 @@ import { HyperLink } from '../../elements/HyperLink'; import { HyperlinkFormContainer } from '../../components/HyperLinkConfig/styles'; import { HyperLinkConfig } from '../../components'; import { useNavigation } from '~/social/providers/NavigationProvider'; +import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; type AmityStoryMediaType = { type: 'image'; url: string } | { type: 'video'; url: string }; @@ -42,6 +43,7 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor const { file, setFile } = useStoryContext(); const { navigationBehavior } = usePageBehavior(); const [isHyperLinkBottomSheetOpen, setIsHyperLinkBottomSheetOpen] = useState(false); + const { confirm } = useConfirmContext(); const [hyperLink, setHyperLink] = useState< { diff --git a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx index 01ad1cc6b..a29e0f9a6 100644 --- a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx @@ -16,7 +16,6 @@ import { isNonNullable } from '~/v4/helpers/utils'; import { extractColors } from 'extract-colors'; import { useNavigation } from '~/social/providers/NavigationProvider'; -import { confirm } from '~/core/components/Confirm'; import { HiddenInput, @@ -32,6 +31,7 @@ import { renderers } from '../../internal-components/StoryViewer/Renderers'; import { AmityDraftStoryPage } from '..'; import { checkStoryPermission } from '~/utils'; import { useStoryContext } from '../../providers/StoryProvider'; +import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; interface CommunityFeedStoryProps { communityId: string; @@ -41,6 +41,7 @@ const DURATION = 5000; export const CommunityFeedStory = ({ communityId }: CommunityFeedStoryProps) => { const { onBack } = useNavigation(); + const { confirm } = useConfirmContext(); const { stories } = useStories({ targetId: communityId, diff --git a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx index b49fc73f8..d6fd00a04 100644 --- a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx @@ -16,7 +16,6 @@ import { isNonNullable } from '~/helpers/utils'; import { extractColors } from 'extract-colors'; import { useNavigation } from '~/social/providers/NavigationProvider'; -import { confirm } from '~/core/components/Confirm'; import { HiddenInput, @@ -32,6 +31,7 @@ import { renderers } from '../../internal-components/StoryViewer/Renderers'; import { AmityDraftStoryPage } from '..'; import { checkStoryPermission } from '~/utils'; import { useStoryContext } from '../../providers/StoryProvider'; +import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; interface CommunityFeedStoryProps { communityId: string; @@ -41,6 +41,7 @@ const DURATION = 5000; export const GlobalFeedStory = ({ communityId }: CommunityFeedStoryProps) => { const { onBack } = useNavigation(); + const { confirm } = useConfirmContext(); const { stories } = useStories({ targetId: communityId, From 20878a7882fd8efe7e58dfc37a8885a174d0765d Mon Sep 17 00:00:00 2001 From: Bonn Date: Wed, 24 Apr 2024 13:34:31 +0700 Subject: [PATCH 033/300] fix: ASC-00000 - notification context (#286) * fix: NotificationProvider * fix: add NotificationProvider into UiKitProvider * chore: move LiveChatNotificationProvider to chat --- src/chat/components/Chat/CreateChatModal.tsx | 3 +- .../EditChatMemberComposer/index.tsx | 3 +- .../ChatDetails/EditChatMemberModal.tsx | 4 +- src/chat/components/Message/Options.tsx | 3 +- src/chat/pages/Application/index.tsx | 3 +- src/core/components/Notification/index.tsx | 54 +-------- src/core/hooks/useAsyncCallback.ts | 3 +- src/core/hooks/useErrorNotification.ts | 5 +- src/core/providers/NotificationProvider.tsx | 103 ++++++++++++++++++ src/core/providers/UiKitProvider/index.tsx | 31 +++--- src/social/components/Comment/index.tsx | 3 +- .../CommunityForm/EditCommunityForm.tsx | 3 +- .../CommunityMembers/CommunityMemberItem.tsx | 3 +- src/social/components/EngagementBar/index.tsx | 3 +- src/social/components/UserInfo/index.tsx | 3 +- src/social/components/post/Creator/index.tsx | 3 +- src/social/components/post/Creator/utils.tsx | 11 -- .../post/PollComposer/PollModal.tsx | 3 +- .../post/Post/DefaultPostRenderer.tsx | 3 +- .../UserFeed/Followers/FollowersList.tsx | 3 +- .../UserFeed/Followers/FollowingsList.tsx | 3 +- .../pages/UserFeed/Followers/PendingList.tsx | 4 +- .../AmityLiveChatMessageComposeBar/index.tsx | 3 +- .../AmityLiveChatMessageList/index.tsx | 7 +- .../components/LiveChatNotification/index.tsx | 52 +-------- src/v4/chat/pages/AmityLiveChatPage/index.tsx | 15 ++- .../LiveChatNotificationProvider.module.css | 6 + .../LiveChatNotificationProvider.tsx | 89 +++++++++++++++ src/v4/core/components/Notification/index.tsx | 55 +--------- src/v4/core/hooks.ts | 16 +++ src/v4/core/providers/AmityUIKitProvider.tsx | 39 ++++--- .../providers/NotificationProvider.module.css | 44 ++++++++ .../core/providers/NotificationProvider.tsx | 90 +++++++++++++++ .../social/components/ViewStoryPage/index.tsx | 3 +- .../internal-components/Comment/index.tsx | 3 +- src/v4/social/pages/DraftsPage/DraftsPage.tsx | 3 +- .../pages/StoryPage/CommunityFeedStory.tsx | 3 +- .../pages/StoryPage/GlobalFeedStory.tsx | 3 +- src/v4/utils/copyMessage.tsx | 11 -- src/v4/utils/index.ts | 1 - 40 files changed, 460 insertions(+), 240 deletions(-) create mode 100644 src/core/providers/NotificationProvider.tsx create mode 100644 src/v4/chat/providers/LiveChatNotificationProvider.module.css create mode 100644 src/v4/chat/providers/LiveChatNotificationProvider.tsx create mode 100644 src/v4/core/hooks.ts create mode 100644 src/v4/core/providers/NotificationProvider.module.css create mode 100644 src/v4/core/providers/NotificationProvider.tsx delete mode 100644 src/v4/utils/copyMessage.tsx diff --git a/src/chat/components/Chat/CreateChatModal.tsx b/src/chat/components/Chat/CreateChatModal.tsx index e8a281475..7aab94db5 100644 --- a/src/chat/components/Chat/CreateChatModal.tsx +++ b/src/chat/components/Chat/CreateChatModal.tsx @@ -4,8 +4,8 @@ import { ChannelRepository } from '@amityco/ts-sdk'; import Modal from '~/core/components/Modal'; import ChatComposer from '~/chat/components/Chat/ChatComposer'; -import { notification } from '~/core/components/Notification'; import { useConfirmContext } from '~/core/providers/ConfirmProvider'; +import { useNotifications } from '~/core/providers/NotificationProvider'; type Props = { onClose: () => void; @@ -14,6 +14,7 @@ type Props = { const CreateChatModal = ({ onClose }: Props) => { const { formatMessage } = useIntl(); const { confirm } = useConfirmContext(); + const notification = useNotifications(); const handleSubmit = async (data: Parameters[0]) => { try { diff --git a/src/chat/components/ChatDetails/EditChatMemberComposer/index.tsx b/src/chat/components/ChatDetails/EditChatMemberComposer/index.tsx index d32b222a4..e0835bd78 100644 --- a/src/chat/components/ChatDetails/EditChatMemberComposer/index.tsx +++ b/src/chat/components/ChatDetails/EditChatMemberComposer/index.tsx @@ -20,8 +20,8 @@ import { } from './styles'; import { ChannelRepository } from '@amityco/ts-sdk'; import { isNonNullable } from '~/helpers/utils'; -import { notification } from '~/core/components/Notification'; import useChannelMembersCollection from '~/chat/hooks/collections/useChannelMembersCollection'; +import { useNotifications } from '~/core/providers/NotificationProvider'; const FormBlock = ({ children }: { children: ReactNode }) => ( @@ -52,6 +52,7 @@ const EditChatMemberComposerForm = ({ onSubmit, }: EditChatMemberComposerFormProps) => { const [isSubmitting, setIsSubmitting] = React.useState(false); + const notification = useNotifications(); const defaultValues = { userIds: memberIds, diff --git a/src/chat/components/ChatDetails/EditChatMemberModal.tsx b/src/chat/components/ChatDetails/EditChatMemberModal.tsx index 6cace2e0c..944edfdeb 100644 --- a/src/chat/components/ChatDetails/EditChatMemberModal.tsx +++ b/src/chat/components/ChatDetails/EditChatMemberModal.tsx @@ -3,10 +3,9 @@ import { useIntl } from 'react-intl'; import { ChannelRepository } from '@amityco/ts-sdk'; import Modal from '~/core/components/Modal'; -import ChatComposer from '~/chat/components/Chat/ChatComposer'; -import { notification } from '~/core/components/Notification'; import EditChatMemberComposer from './EditChatMemberComposer'; import { useConfirmContext } from '~/core/providers/ConfirmProvider'; +import { useNotifications } from '~/core/providers/NotificationProvider'; type Props = { channelId: string; @@ -16,6 +15,7 @@ type Props = { const EditChatMemberModal = ({ channelId, onClose }: Props) => { const { formatMessage } = useIntl(); const { confirm } = useConfirmContext(); + const notification = useNotifications(); const handleSubmit = async ( data: Parameters[1], diff --git a/src/chat/components/Message/Options.tsx b/src/chat/components/Message/Options.tsx index 3fe171287..89627b593 100644 --- a/src/chat/components/Message/Options.tsx +++ b/src/chat/components/Message/Options.tsx @@ -5,11 +5,11 @@ import { FormattedMessage, useIntl } from 'react-intl'; import Popover from '~/core/components/Popover'; import Menu, { MenuItem } from '~/core/components/Menu'; -import { notification } from '~/core/components/Notification'; import { MessageOptionsIcon, SaveIcon, CloseIcon, EditingInput, EditingContainer } from './styles'; import useMessageFlaggedByMe from '~/chat/hooks/useMessageFlaggedByMe'; import useMessageSubscription from '~/social/hooks/useMessageSubscription'; +import { useNotifications } from '~/core/providers/NotificationProvider'; const StyledPopover = styled(Popover)<{ align?: string; className?: string }>` ${({ align, theme }) => align === 'end' && `color: ${theme.palette.neutral.main};`} @@ -55,6 +55,7 @@ const Options = ({ // const popupContainerRef = useRef(); const [text, setText] = useState(''); const [isEditing, setIsEditing] = useState(false); + const notification = useNotifications(); const edit: React.MouseEventHandler = (e) => { e.stopPropagation(); diff --git a/src/chat/pages/Application/index.tsx b/src/chat/pages/Application/index.tsx index 4ea6317aa..486356481 100644 --- a/src/chat/pages/Application/index.tsx +++ b/src/chat/pages/Application/index.tsx @@ -2,7 +2,6 @@ import React, { useState, useEffect } from 'react'; import { ChannelRepository, Client as ASCClient } from '@amityco/ts-sdk'; import { useIntl } from 'react-intl'; -import { notification } from '~/core/components/Notification'; import RecentChat from '~/chat/components/RecentChat'; import Chat from '~/chat/components/Chat'; import ChatDetails from '~/chat/components/ChatDetails'; @@ -10,6 +9,7 @@ import ChatDetails from '~/chat/components/ChatDetails'; import { ApplicationContainer } from './styles'; import CreateChatModal from '~/chat/components/Chat/CreateChatModal'; import EditChatMemberModal from '~/chat/components/ChatDetails/EditChatMemberModal'; +import { useNotifications } from '~/core/providers/NotificationProvider'; type PartialChannel = Pick; @@ -37,6 +37,7 @@ const ChatApplication = ({ const { formatMessage } = useIntl(); const [currentChannelData, setCurrentChannelData] = useState(null); const [shouldShowChatDetails, setShouldShowChatDetails] = useState(false); + const notification = useNotifications(); const showChatDetails = () => setShouldShowChatDetails(true); const hideChatDetails = () => setShouldShowChatDetails(false); diff --git a/src/core/components/Notification/index.tsx b/src/core/components/Notification/index.tsx index bd806a239..7e8e157bd 100644 --- a/src/core/components/Notification/index.tsx +++ b/src/core/components/Notification/index.tsx @@ -1,8 +1,7 @@ -import React, { ReactNode, useState } from 'react'; +import React, { ReactNode, useEffect, useState } from 'react'; +import { useNotificationData } from '~/core/providers/NotificationProvider'; -import { NotificationContainer, Notifications, SuccessIcon, InfoIcon, ErrorIcon } from './styles'; - -const DEFAULT_NOTIFICATION_DURATION = 3000; +import { NotificationContainer, Notifications } from './styles'; interface NotificationProps { className?: string; @@ -10,42 +9,15 @@ interface NotificationProps { icon?: ReactNode; } -interface NotificationData { - id: number; - content: ReactNode; - icon?: ReactNode; -} - -type NotificationInput = Omit & { duration?: number }; - const Notification = ({ className, content, icon }: NotificationProps) => ( {icon} {content} ); -let spawnNewNotification: (notificationData: NotificationInput) => void; // for modifying NotificationContainer state outside - // rendered by provider, to allow spawning of notification from notification function below export const NotificationsContainer = () => { - const [notifications, setNotifications] = useState([]); - - const removeNotification = (id: number) => - setNotifications && - setNotifications((prevNotifications) => - prevNotifications.filter((notification) => notification.id !== id), - ); - - spawnNewNotification = ({ - duration = DEFAULT_NOTIFICATION_DURATION, - ...notificationData - }: NotificationInput) => { - const id = Date.now(); - - setNotifications([{ id, ...notificationData }, ...notifications]); - - setTimeout(() => removeNotification(id), duration); - }; + const notifications = useNotificationData(); return ( @@ -56,22 +28,4 @@ export const NotificationsContainer = () => { ); }; -/* - Usage: - notification.success({ - content: 'Report Sent', - }); - - This interface rely on NotificationsContainer being rendered by UIKITProvider in the react tree -*/ -export const notification = { - success: (data: Omit) => - spawnNewNotification({ ...data, icon: }), - info: (data: Omit) => - spawnNewNotification({ ...data, icon: }), - error: (data: Omit) => - spawnNewNotification({ ...data, icon: }), - show: (data: Omit) => spawnNewNotification(data), -}; - export default Notification; diff --git a/src/core/hooks/useAsyncCallback.ts b/src/core/hooks/useAsyncCallback.ts index 001ec2db1..aebafe1d9 100644 --- a/src/core/hooks/useAsyncCallback.ts +++ b/src/core/hooks/useAsyncCallback.ts @@ -1,5 +1,5 @@ import { useCallback, useState } from 'react'; -import { notification } from '~/core/components/Notification'; +import { useNotifications } from '../providers/NotificationProvider'; /** * @deprecated @@ -10,6 +10,7 @@ export function useAsyncCallback, U, V extends (...args deps: unknown[], ): [(...args: T) => Promise, boolean] { const [loading, setLoading] = useState(false); + const notification = useNotifications(); const newCallback = useCallback(async (...args: T) => { try { diff --git a/src/core/hooks/useErrorNotification.ts b/src/core/hooks/useErrorNotification.ts index 91fa7294f..782483872 100644 --- a/src/core/hooks/useErrorNotification.ts +++ b/src/core/hooks/useErrorNotification.ts @@ -1,9 +1,10 @@ import { useEffect, useState } from 'react'; - -import { notification } from '~/core/components/Notification'; +import { useNotifications } from '~/v4/core/providers/NotificationProvider'; const useErrorNotification = () => { const [error, setError] = useState(null); + const notification = useNotifications(); + useEffect(() => { if (error) { notification.error({ diff --git a/src/core/providers/NotificationProvider.tsx b/src/core/providers/NotificationProvider.tsx new file mode 100644 index 000000000..4de106b3e --- /dev/null +++ b/src/core/providers/NotificationProvider.tsx @@ -0,0 +1,103 @@ +import React, { createContext, ReactNode, useContext, useState } from 'react'; +import styled from 'styled-components'; +import { Check, ExclamationCircle, Remove } from '~/icons'; + +export const SuccessIcon = styled(Check).attrs<{ icon?: ReactNode }>({ width: 18, height: 18 })` + margin-right: 8px; +`; + +export const InfoIcon = styled(ExclamationCircle).attrs<{ icon?: ReactNode }>({ + width: 18, + height: 18, +})` + margin-right: 8px; +`; + +export const ErrorIcon = styled(Remove).attrs<{ icon?: ReactNode }>({ width: 18, height: 18 })` + margin-right: 8px; +`; + +interface Notification { + id: number; + content: ReactNode; + icon?: ReactNode; + duration?: number; +} + +type NotificationInput = Omit & { duration?: number }; + +interface NotificationContextProps { + notifications: Notification[]; + notificationFunction: { + success: (data: Omit) => void; + info: (data: Omit) => void; + error: (data: Omit) => void; + show: (data: Omit) => void; + }; +} + +export const NotificationContext = createContext({ + notifications: [], + notificationFunction: { + success: () => {}, + info: () => {}, + error: () => {}, + show: () => {}, + }, +}); + +const DEFAULT_NOTIFICATION_DURATION = 3000; + +export const NotificationProvider: React.FC = ({ children }) => { + const [notifications, setNotifications] = useState([]); + + const removeNotification = (id: number) => + setNotifications && + setNotifications((prevNotifications) => + prevNotifications.filter((notification) => notification.id !== id), + ); + + const addNotifications = (data: NotificationInput) => { + const id = Date.now(); + setNotifications((prevNotifications) => [ + ...prevNotifications, + { + id, + ...data, + }, + ]); + + setTimeout(() => { + removeNotification(id); + }, data?.duration || DEFAULT_NOTIFICATION_DURATION); + }; + + return ( + ) => + addNotifications({ ...data, icon: }), + info: (data: Omit) => + addNotifications({ ...data, icon: }), + error: (data: Omit) => + addNotifications({ ...data, icon: }), + show: (data: Omit) => addNotifications(data), + }, + }} + > + {children} + + ); +}; + +export const useNotificationData = () => { + const { notifications } = useContext(NotificationContext); + return notifications; +}; + +export const useNotifications = () => { + const { notificationFunction } = useContext(NotificationContext); + return notificationFunction; +}; diff --git a/src/core/providers/UiKitProvider/index.tsx b/src/core/providers/UiKitProvider/index.tsx index 70d898c5b..140e224f2 100644 --- a/src/core/providers/UiKitProvider/index.tsx +++ b/src/core/providers/UiKitProvider/index.tsx @@ -21,6 +21,7 @@ import PostRendererProvider, { import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ConfirmProvider } from '../ConfirmProvider'; +import { NotificationProvider } from '~/core/providers/NotificationProvider'; interface UiKitProviderProps { apiKey: string; @@ -132,20 +133,22 @@ const UiKitProvider = ({ - - - - {children} - - - - - + + + + + {children} + + + + + + diff --git a/src/social/components/Comment/index.tsx b/src/social/components/Comment/index.tsx index 2254bef56..a60124f48 100644 --- a/src/social/components/Comment/index.tsx +++ b/src/social/components/Comment/index.tsx @@ -4,7 +4,6 @@ import { FormattedMessage, useIntl } from 'react-intl'; import useComment from '~/social/hooks/useComment'; import CommentComposeBar from '~/social/components/CommentComposeBar'; import CommentList from '~/social/components/CommentList'; -import { notification } from '~/core/components/Notification'; import StyledComment from './StyledComment'; import useSocialMention from '~/social/hooks/useSocialMention'; import usePost from '~/social/hooks/usePost'; @@ -39,6 +38,7 @@ import useCommentSubscription from '~/social/hooks/useCommentSubscription'; import { ERROR_RESPONSE } from '~/social/constants'; import { useConfirmContext } from '~/core/providers/ConfirmProvider'; +import { useNotifications } from '~/core/providers/NotificationProvider'; const REPLIES_PER_PAGE = 5; @@ -91,6 +91,7 @@ const Comment = ({ commentId, readonly }: CommentProps) => { const comment = useComment(commentId); const post = usePost(comment?.referenceId); const { confirm } = useConfirmContext(); + const notification = useNotifications(); const commentAuthor = useUser(comment?.userId); const commentAuthorAvatar = useFile(commentAuthor?.avatarFileId); diff --git a/src/social/components/CommunityForm/EditCommunityForm.tsx b/src/social/components/CommunityForm/EditCommunityForm.tsx index 8c65dd609..177d333bc 100644 --- a/src/social/components/CommunityForm/EditCommunityForm.tsx +++ b/src/social/components/CommunityForm/EditCommunityForm.tsx @@ -6,7 +6,6 @@ import Radio from '~/core/components/Radio'; import AvatarUploader from './AvatarUploader'; -import { notification } from '~/core/components/Notification'; import CategorySelector from './CategorySelector'; import { @@ -32,6 +31,7 @@ import { } from './styles'; import { EditFormValues, useEditCommunityForm } from './hooks'; import { CommunityRepository } from '@amityco/ts-sdk'; +import { useNotifications } from '~/core/providers/NotificationProvider'; interface FormBlockProps { title?: React.ReactNode; @@ -83,6 +83,7 @@ const EditCommunityForm = ({ const { errors, isValid } = formState; const displayName = watch('displayName'); const description = watch('description'); + const notification = useNotifications(); const formBodyRef = useRef(null); diff --git a/src/social/components/CommunityMembers/CommunityMemberItem.tsx b/src/social/components/CommunityMembers/CommunityMemberItem.tsx index 1b00a70d4..300f33b3b 100644 --- a/src/social/components/CommunityMembers/CommunityMemberItem.tsx +++ b/src/social/components/CommunityMembers/CommunityMemberItem.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; -import { notification } from '~/core/components/Notification'; import OptionMenu from '~/core/components/OptionMenu'; import UserHeader from '~/social/components/UserHeader'; import useUser from '~/core/hooks/useUser'; @@ -13,6 +12,7 @@ import useUserFlaggedByMe from '~/social/hooks/useUserFlaggedByMe'; import useUserSubscription from '~/social/hooks/useUserSubscription'; import { SubscriptionLevels } from '@amityco/ts-sdk'; import { useConfirmContext } from '~/core/providers/ConfirmProvider'; +import { useNotifications } from '~/core/providers/NotificationProvider'; const { COMMUNITY_MODERATOR, CHANNEL_MODERATOR } = MemberRoles; @@ -46,6 +46,7 @@ const CommunityMemberItem = ({ const { isFlaggedByMe, toggleFlagUser } = useUserFlaggedByMe(userId); const isGlobalBanned = user?.isGlobalBanned; const { confirm } = useConfirmContext(); + const notification = useNotifications(); useUserSubscription({ userId, diff --git a/src/social/components/EngagementBar/index.tsx b/src/social/components/EngagementBar/index.tsx index d3d60446a..23887c296 100644 --- a/src/social/components/EngagementBar/index.tsx +++ b/src/social/components/EngagementBar/index.tsx @@ -7,10 +7,10 @@ import { Mentionees, Metadata } from '~/helpers/utils'; import { useCustomComponent } from '~/core/providers/CustomComponentsProvider'; import useReactionSubscription from '~/social/hooks/useReactionSubscription'; import usePostSubscription from '~/social/hooks/usePostSubscription'; -import { notification } from '~/core/components/Notification'; import { FormattedMessage } from 'react-intl'; import useSocialMention from '~/social/hooks/useSocialMention'; import { ERROR_RESPONSE } from '~/social/constants'; +import { useNotifications } from '~/core/providers/NotificationProvider'; interface EngagementBarProps { postId: string; @@ -19,6 +19,7 @@ interface EngagementBarProps { const EngagementBar = ({ postId, readonly = false }: EngagementBarProps) => { const [isComposeBarDisplayed, setComposeBarDisplayed] = useState(false); + const notification = useNotifications(); const toggleComposeBar = () => setComposeBarDisplayed((prevValue) => !prevValue); const hideComposeBar = () => setComposeBarDisplayed(false); diff --git a/src/social/components/UserInfo/index.tsx b/src/social/components/UserInfo/index.tsx index 51f1c823c..cadefb7f3 100644 --- a/src/social/components/UserInfo/index.tsx +++ b/src/social/components/UserInfo/index.tsx @@ -5,7 +5,6 @@ import useUser from '~/core/hooks/useUser'; import { useNavigation } from '~/social/providers/NavigationProvider'; import useFollowCount from '~/core/hooks/useFollowCount'; import useImage from '~/core/hooks/useImage'; -import { notification } from '~/core/components/Notification'; import useSDK from '~/core/hooks/useSDK'; import { SubscriptionLevels, UserRepository } from '@amityco/ts-sdk'; import useFollowStatus from '~/core/hooks/useFollowStatus'; @@ -14,6 +13,7 @@ import UIUserInfo from './UIUserInfo'; import useUserSubscription from '~/social/hooks/useUserSubscription'; import useFollowersSubscription from '~/social/hooks/useFollowersSubscription'; import useFollowingsSubscription from '~/social/hooks/useFollowingsSubscription'; +import { useNotifications } from '~/core/providers/NotificationProvider'; interface UserInfoProps { userId?: string | null; @@ -35,6 +35,7 @@ const UserInfo = ({ const { currentUserId } = useSDK(); const { formatMessage } = useIntl(); const { onEditUser } = useNavigation(); + const notification = useNotifications(); const user = useUser(userId); const avatarFileUrl = useImage({ fileId: user?.avatarFileId, imageSize: 'small' }); diff --git a/src/social/components/post/Creator/index.tsx b/src/social/components/post/Creator/index.tsx index ecde207da..01958cb2d 100644 --- a/src/social/components/post/Creator/index.tsx +++ b/src/social/components/post/Creator/index.tsx @@ -13,7 +13,6 @@ import useImage from '~/core/hooks/useImage'; import useUser from '~/core/hooks/useUser'; import useErrorNotification from '~/core/hooks/useErrorNotification'; -import { notification } from '~/core/components/Notification'; import { backgroundImage as UserImage } from '~/icons/User'; import { backgroundImage as CommunityImage } from '~/icons/Community'; @@ -45,6 +44,7 @@ import useSocialMention from '~/social/hooks/useSocialMention'; import useCommunityModeratorsCollection from '~/social/hooks/collections/useCommunityModeratorsCollection'; import { ERROR_RESPONSE } from '~/social/constants'; import { useConfirmContext } from '~/core/providers/ConfirmProvider'; +import { useNotifications } from '~/core/providers/NotificationProvider'; const useTargetData = ({ targetId, @@ -116,6 +116,7 @@ const PostCreatorBar = ({ const { setNavigationBlocker } = useNavigation(); const user = useUser(currentUserId); const { info } = useConfirmContext(); + const notification = useNotifications(); // default to me if (targetType === 'global' || targetType === 'myFeed') { diff --git a/src/social/components/post/Creator/utils.tsx b/src/social/components/post/Creator/utils.tsx index 07468adb0..082b63ae9 100644 --- a/src/social/components/post/Creator/utils.tsx +++ b/src/social/components/post/Creator/utils.tsx @@ -1,5 +1,4 @@ import { PostRepository } from '@amityco/ts-sdk'; -import { notification } from '~/core/components/Notification'; import promisify from '~/helpers/promisify'; export const MAX_IMAGES = 10; @@ -12,16 +11,6 @@ type Author = { communityId: string; userId: string }; export const isIdenticalAuthor = (a: Author, b: Author) => !!getAuthorId(a) && getAuthorId(a) === getAuthorId(b); -export const maxImagesWarning = () => - notification.info({ - content: 'You reached the maximum attachment of 10', - }); - -export const maxFilesWarning = () => - notification.info({ - content: 'The selected file is larger than 1GB. Please select a new file. ', - }); - type CreatePostParams = Parameters[0]; export async function createPost({ diff --git a/src/social/components/post/PollComposer/PollModal.tsx b/src/social/components/post/PollComposer/PollModal.tsx index 1b629c5bf..6e7c73cb2 100644 --- a/src/social/components/post/PollComposer/PollModal.tsx +++ b/src/social/components/post/PollComposer/PollModal.tsx @@ -4,9 +4,9 @@ import { PollRepository } from '@amityco/ts-sdk'; import Modal from '~/core/components/Modal'; import PollComposer from '~/social/components/post/PollComposer'; -import { notification } from '~/core/components/Notification'; import { ERROR_RESPONSE } from '~/social/constants'; import { useConfirmContext } from '~/core/providers/ConfirmProvider'; +import { useNotifications } from '~/core/providers/NotificationProvider'; interface PollModalProps { targetId?: string | null; @@ -24,6 +24,7 @@ const PollModal = ({ targetId, targetType, onClose, onCreatePoll }: PollModalPro const [isDirty, setDirty] = useState(false); const { formatMessage } = useIntl(); const { confirm } = useConfirmContext(); + const notification = useNotifications(); const handleSubmit = async ( data: Parameters[0], diff --git a/src/social/components/post/Post/DefaultPostRenderer.tsx b/src/social/components/post/Post/DefaultPostRenderer.tsx index 7058e0dbe..0da4efad6 100644 --- a/src/social/components/post/Post/DefaultPostRenderer.tsx +++ b/src/social/components/post/Post/DefaultPostRenderer.tsx @@ -2,7 +2,6 @@ import React, { useState } from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; import Button, { PrimaryButton } from '~/core/components/Button'; import Modal from '~/core/components/Modal'; -import { notification } from '~/core/components/Notification'; import { isNonNullable } from '~/helpers/utils'; import EngagementBar from '~/social/components/EngagementBar'; import ChildrenContent from '~/social/components/post/ChildrenContent'; @@ -23,6 +22,7 @@ import useSDK from '~/core/hooks/useSDK'; import usePostSubscription from '~/social/hooks/usePostSubscription'; import { SubscriptionLevels } from '@amityco/ts-sdk'; import { useConfirmContext } from '~/core/providers/ConfirmProvider'; +import { useNotifications } from '~/core/providers/NotificationProvider'; // Number of lines to show in a text post before truncating. const MAX_TEXT_LINES_DEFAULT = 8; @@ -53,6 +53,7 @@ const DefaultPostRenderer = ({ const openEditingPostModal = () => setIsEditing(true); const closeEditingPostModal = () => setIsEditing(false); const { info, confirm } = useConfirmContext(); + const notification = useNotifications(); function showHasBeenReviewedMessageIfNeeded(error: unknown) { if (error instanceof Error) { diff --git a/src/social/pages/UserFeed/Followers/FollowersList.tsx b/src/social/pages/UserFeed/Followers/FollowersList.tsx index 5faa41aa2..19109365d 100644 --- a/src/social/pages/UserFeed/Followers/FollowersList.tsx +++ b/src/social/pages/UserFeed/Followers/FollowersList.tsx @@ -8,7 +8,6 @@ import { isLoadingItem } from '~/utils'; import useUser from '~/core/hooks/useUser'; import { UserRepository } from '@amityco/ts-sdk'; import { useNavigation } from '~/social/providers/NavigationProvider'; -import { notification } from '~/core/components/Notification'; import { Grid, @@ -24,6 +23,7 @@ import useFollowersSubscription from '~/social/hooks/useFollowersSubscription'; import useSDK from '~/core/hooks/useSDK'; import useFollowersCollection from '~/core/hooks/collections/useFollowersCollection'; import { useConfirmContext } from '~/core/providers/ConfirmProvider'; +import { useNotifications } from '~/core/providers/NotificationProvider'; interface UserItemProps { profileUserId: string; @@ -37,6 +37,7 @@ export const UserItem = ({ profileUserId, currentUserId, userId, onClick }: User const avatarFileUrl = useImage({ fileId: user?.avatarFileId, imageSize: 'small' }); const { onClickUser } = useNavigation(); const { confirm } = useConfirmContext(); + const notification = useNotifications(); const { formatMessage } = useIntl(); const { isFlaggedByMe, toggleFlagUser } = useUserFlaggedByMe(userId || undefined); diff --git a/src/social/pages/UserFeed/Followers/FollowingsList.tsx b/src/social/pages/UserFeed/Followers/FollowingsList.tsx index d0d105f2b..2b8bf09e4 100644 --- a/src/social/pages/UserFeed/Followers/FollowingsList.tsx +++ b/src/social/pages/UserFeed/Followers/FollowingsList.tsx @@ -8,7 +8,6 @@ import { isLoadingItem } from '~/utils'; import useUser from '~/core/hooks/useUser'; import { UserRepository } from '@amityco/ts-sdk'; import { useNavigation } from '~/social/providers/NavigationProvider'; -import { notification } from '~/core/components/Notification'; import { Grid, @@ -23,6 +22,7 @@ import useFollowingsSubscription from '~/social/hooks/useFollowingsSubscription' import useSDK from '~/core/hooks/useSDK'; import useFollowingsCollection from '~/core/hooks/collections/useFollowingsCollection'; import { useConfirmContext } from '~/core/providers/ConfirmProvider'; +import { useNotifications } from '~/core/providers/NotificationProvider'; interface UserItemProps { profileUserId: string; @@ -35,6 +35,7 @@ export const UserItem = ({ profileUserId, currentUserId, userId, onClick }: User const user = useUser(userId); const { onClickUser } = useNavigation(); const { confirm } = useConfirmContext(); + const notification = useNotifications(); const { formatMessage } = useIntl(); const { isFlaggedByMe, toggleFlagUser } = useUserFlaggedByMe(userId); diff --git a/src/social/pages/UserFeed/Followers/PendingList.tsx b/src/social/pages/UserFeed/Followers/PendingList.tsx index f263af2ef..2da713f4e 100644 --- a/src/social/pages/UserFeed/Followers/PendingList.tsx +++ b/src/social/pages/UserFeed/Followers/PendingList.tsx @@ -4,15 +4,17 @@ import { FormattedMessage } from 'react-intl'; import { ButtonsContainer, UserHeaderContainer } from '~/social/pages/UserFeed/Followers/styles'; import UserHeader from '~/social/components/UserHeader'; import Button, { PrimaryButton } from '~/core/components/Button'; -import { notification } from '~/core/components/Notification'; import { Grid } from '~/social/components/community/CategoryCommunitiesList/styles'; import Skeleton from '~/core/components/Skeleton'; import LoadMore from '~/core/components/LoadMore'; import { UserRepository } from '@amityco/ts-sdk'; import { isLoadingItem } from '~/utils'; import useFollowersCollection from '~/core/hooks/collections/useFollowersCollection'; +import { useNotifications } from '~/core/providers/NotificationProvider'; const PendingItem = ({ userId }: { userId: string }) => { + const notification = useNotifications(); + const followAccept = async () => { if (!userId) return; await UserRepository.Relationship.acceptMyFollower(userId); diff --git a/src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx b/src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx index 3dded2ebb..ef558cfcd 100644 --- a/src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx +++ b/src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx @@ -8,8 +8,8 @@ import useMention from '~/v4/chat/hooks/useMention'; import { useIntl } from 'react-intl'; import useChannelPermission from '~/v4/chat/hooks/useChannelPermission'; import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; -import { notification } from '~/v4/chat/components/LiveChatNotification'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; +import { useLiveChatNotifications } from '~/v4/chat/providers/LiveChatNotificationProvider'; const COMPOSEBAR_MAX_CHARACTER_LIMIT = 200; @@ -43,6 +43,7 @@ export const AmityLiveChatMessageComposeBar = ({ [key: ComposeBarMention['id']]: ComposeBarMention; }>({}); const { confirm } = useConfirmContext(); + const notification = useLiveChatNotifications(); const { getConfig } = useCustomization(); const componentConfig = getConfig('live_chat/message_composer/*'); diff --git a/src/v4/chat/components/AmityLiveChatMessageList/index.tsx b/src/v4/chat/components/AmityLiveChatMessageList/index.tsx index 5ba3aefb7..329b18e2f 100644 --- a/src/v4/chat/components/AmityLiveChatMessageList/index.tsx +++ b/src/v4/chat/components/AmityLiveChatMessageList/index.tsx @@ -5,14 +5,15 @@ import LivechatLoadingIndicator from '~/v4/chat/components/LivechatLoadingIndica import useSDK from '~/core/hooks/useSDK'; import AmityLiveChatMessageSenderView from '../AmityLiveChatMessageSenderView'; import AmityLiveChatMessageReceiverView from '../AmityLiveChatMessageReceiverView'; -import { copyMessage, deleteMessage, flagMessage } from '~/v4/utils'; +import { deleteMessage, flagMessage } from '~/v4/utils'; import useMessagesCollection from '~/chat/hooks/collections/useMessagesCollection'; import { FormattedMessage, useIntl } from 'react-intl'; import { Typography } from '~/v4/core/components'; import Redo from '~/v4/icons/Redo'; -import { notification } from '~/v4/chat/components/LiveChatNotification'; import { unFlagMessage } from '~/v4/utils/unFlagMessage'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; +import { useLiveChatNotifications } from '~/v4/chat/providers/LiveChatNotificationProvider'; +import { useCopyMessage } from '~/v4/core/hooks'; interface AmityLiveChatMessageListProps { channel: Amity.Channel; @@ -28,6 +29,8 @@ export const AmityLiveChatMessageList = ({ const { formatMessage } = useIntl(); const [height, setHeight] = React.useState(undefined); const { confirm } = useConfirmContext(); + const notification = useLiveChatNotifications(); + const copyMessage = useCopyMessage(); const { messages: rawMessages, diff --git a/src/v4/chat/components/LiveChatNotification/index.tsx b/src/v4/chat/components/LiveChatNotification/index.tsx index 0e800cdf0..7fdb99d57 100644 --- a/src/v4/chat/components/LiveChatNotification/index.tsx +++ b/src/v4/chat/components/LiveChatNotification/index.tsx @@ -1,11 +1,8 @@ -import React, { ReactNode, useState } from 'react'; -import ExclamationCircle from '~/v4/icons/ExclamationCircle'; -import CheckCircle from '~/icons/CheckCircle'; +import React, { ReactNode } from 'react'; import clsx from 'clsx'; import styles from './styles.module.css'; import { Typography } from '~/v4/core/components/index'; - -const DEFAULT_NOTIFICATION_DURATION = 3000; +import { useLiveChatNotificationData } from '~/v4/chat/providers/LiveChatNotificationProvider'; interface NotificationProps { className?: string; @@ -13,14 +10,6 @@ interface NotificationProps { icon?: ReactNode; } -interface NotificationData { - id: number; - content: ReactNode; - icon?: ReactNode; -} - -type NotificationInput = Omit & { duration?: number }; - const LiveChatNotification = ({ className, content, icon }: NotificationProps) => (
{icon} @@ -28,27 +17,8 @@ const LiveChatNotification = ({ className, content, icon }: NotificationProps) =
); -let spawnNewNotification: (notificationData: NotificationInput) => void; // for modifying NotificationContainer state outside - export const LiveChatNotificationsContainer = () => { - const [notifications, setNotifications] = useState([]); - - const removeNotification = (id: number) => - setNotifications && - setNotifications((prevNotifications) => - prevNotifications.filter((notification) => notification.id !== id), - ); - - spawnNewNotification = ({ - duration = DEFAULT_NOTIFICATION_DURATION, - ...notificationData - }: NotificationInput) => { - const id = Date.now(); - - setNotifications([{ id, ...notificationData }, ...notifications]); - - setTimeout(() => removeNotification(id), duration); - }; + const notifications = useLiveChatNotificationData(); return (
@@ -59,20 +29,4 @@ export const LiveChatNotificationsContainer = () => { ); }; -/* - Usage: - notification.success({ - content: 'Report Sent', - }); - - This interface rely on LiveChatNotificationsContainer being rendered by live chat page only -*/ -export const notification = { - success: (data: Omit) => - spawnNewNotification({ ...data, icon: }), - error: (data: Omit) => - spawnNewNotification({ ...data, icon: }), - show: (data: Omit) => spawnNewNotification(data), -}; - export default LiveChatNotification; diff --git a/src/v4/chat/pages/AmityLiveChatPage/index.tsx b/src/v4/chat/pages/AmityLiveChatPage/index.tsx index ccb60b199..b9c9dcca2 100644 --- a/src/v4/chat/pages/AmityLiveChatPage/index.tsx +++ b/src/v4/chat/pages/AmityLiveChatPage/index.tsx @@ -1,8 +1,9 @@ import React, { useRef } from 'react'; import useChannel from '~/v4/chat/hooks/useChannel'; -import AmityLiveChatHeader from 'v4/chat/components/AmityLiveChatHeader'; +import AmityLiveChatHeader from '~/v4/chat/components/AmityLiveChatHeader'; import ChatContainer from './ChatContainer'; import styles from './styles.module.css'; +import { LiveChatNotificationProvider } from '~/v4/chat/providers/LiveChatNotificationProvider'; interface AmityLiveChatPageProps { channelId: Amity.Channel['channelId']; @@ -13,12 +14,14 @@ export const AmityLiveChatPage = ({ channelId }: AmityLiveChatPageProps) => { const ref = useRef(null); return ( -
-
- + +
+
+ +
+
- -
+ ); }; diff --git a/src/v4/chat/providers/LiveChatNotificationProvider.module.css b/src/v4/chat/providers/LiveChatNotificationProvider.module.css new file mode 100644 index 000000000..f46b11921 --- /dev/null +++ b/src/v4/chat/providers/LiveChatNotificationProvider.module.css @@ -0,0 +1,6 @@ +.icon { + width: 1.5rem; + height: 1.5rem; + fill: var(--asc-color-base-inverse); + margin-right: var(--asc-spacing-s2); +} diff --git a/src/v4/chat/providers/LiveChatNotificationProvider.tsx b/src/v4/chat/providers/LiveChatNotificationProvider.tsx new file mode 100644 index 000000000..3c1a87f94 --- /dev/null +++ b/src/v4/chat/providers/LiveChatNotificationProvider.tsx @@ -0,0 +1,89 @@ +import React, { createContext, ReactNode, useContext, useState } from 'react'; +import CheckCircle from '~/v4/icons/CheckCircle'; +import ExclamationCircle from '~/v4/icons/ExclamationCircle'; + +import styles from './LiveChatNotificationProvider.module.css'; + +interface LiveChatNotification { + id: number; + content: ReactNode; + icon?: ReactNode; + duration?: number; +} + +type LiveChatNotificationInput = Omit & { duration?: number }; + +interface LiveChatNotificationContextProps { + liveChatNotifications: LiveChatNotification[]; + liveChatNotificationFunction: { + success: (data: Omit) => void; + error: (data: Omit) => void; + show: (data: Omit) => void; + }; +} + +export const LiveChatNotificationContext = createContext({ + liveChatNotifications: [], + liveChatNotificationFunction: { + success: () => {}, + error: () => {}, + show: () => {}, + }, +}); + +const DEFAULT_NOTIFICATION_DURATION = 3000; + +export const LiveChatNotificationProvider: React.FC = ({ children }) => { + const [liveChatNotifications, setLiveChatNotifications] = useState([]); + + const removeLiveChatNotification = (id: number) => + setLiveChatNotifications && + setLiveChatNotifications((prevLiveChatNotifications) => + prevLiveChatNotifications.filter((notification) => notification.id !== id), + ); + + const addLiveChatNotifications = (data: LiveChatNotificationInput) => { + const id = Date.now(); + setLiveChatNotifications((prevLiveChatNotifications) => [ + ...prevLiveChatNotifications, + { + id, + ...data, + }, + ]); + + setTimeout(() => { + removeLiveChatNotification(id); + }, data?.duration || DEFAULT_NOTIFICATION_DURATION); + }; + + return ( + ) => + addLiveChatNotifications({ ...data, icon: }), + error: (data: Omit) => + addLiveChatNotifications({ + ...data, + icon: , + }), + show: (data: Omit) => addLiveChatNotifications(data), + }, + }} + > + {children} + + ); +}; + +export const useLiveChatNotificationData = () => { + const { liveChatNotifications } = useContext(LiveChatNotificationContext); + return liveChatNotifications; +}; + +export const useLiveChatNotifications = () => { + const { liveChatNotificationFunction } = useContext(LiveChatNotificationContext); + return liveChatNotificationFunction; +}; diff --git a/src/v4/core/components/Notification/index.tsx b/src/v4/core/components/Notification/index.tsx index 4fb1a82e2..471fd8cd8 100644 --- a/src/v4/core/components/Notification/index.tsx +++ b/src/v4/core/components/Notification/index.tsx @@ -1,11 +1,7 @@ -import React, { ReactNode, useState } from 'react'; -import Check from '~/v4/icons/Check'; -import ExclamationCircle from '~/v4/icons/ExclamationCircle'; -import Remove from '~/v4/icons/Remove'; +import React, { ReactNode } from 'react'; import clsx from 'clsx'; import styles from './styles.module.css'; - -const DEFAULT_NOTIFICATION_DURATION = 3000; +import { useNotificationData } from '~/v4/core/providers/NotificationProvider'; interface NotificationProps { className?: string; @@ -13,42 +9,15 @@ interface NotificationProps { icon?: ReactNode; } -interface NotificationData { - id: number; - content: ReactNode; - icon?: ReactNode; -} - -type NotificationInput = Omit & { duration?: number }; - const Notification = ({ className, content, icon }: NotificationProps) => (
{icon} {content}
); -let spawnNewNotification: (notificationData: NotificationInput) => void; // for modifying NotificationContainer state outside - // rendered by provider, to allow spawning of notification from notification function below export const NotificationsContainer = () => { - const [notifications, setNotifications] = useState([]); - - const removeNotification = (id: number) => - setNotifications && - setNotifications((prevNotifications) => - prevNotifications.filter((notification) => notification.id !== id), - ); - - spawnNewNotification = ({ - duration = DEFAULT_NOTIFICATION_DURATION, - ...notificationData - }: NotificationInput) => { - const id = Date.now(); - - setNotifications([{ id, ...notificationData }, ...notifications]); - - setTimeout(() => removeNotification(id), duration); - }; + const notifications = useNotificationData(); return (
@@ -59,22 +28,4 @@ export const NotificationsContainer = () => { ); }; -/* - Usage: - notification.success({ - content: 'Report Sent', - }); - - This interface rely on NotificationsContainer being rendered by UIKITProvider in the react tree -*/ -export const notification = { - success: (data: Omit) => - spawnNewNotification({ ...data, icon: }), - info: (data: Omit) => - spawnNewNotification({ ...data, icon: }), - error: (data: Omit) => - spawnNewNotification({ ...data, icon: }), - show: (data: Omit) => spawnNewNotification(data), -}; - export default Notification; diff --git a/src/v4/core/hooks.ts b/src/v4/core/hooks.ts new file mode 100644 index 000000000..7f62da8d1 --- /dev/null +++ b/src/v4/core/hooks.ts @@ -0,0 +1,16 @@ +import { useIntl } from 'react-intl'; +import { useNotifications } from '~/v4/core/providers/NotificationProvider'; + +export const useCopyMessage = () => { + const notification = useNotifications(); + const { formatMessage } = useIntl(); + + const copyMessage = async (message: string) => { + await navigator.clipboard.writeText(message); + notification.show({ + content: formatMessage({ id: 'livechat.notification.copy.message' }), + }); + }; + + return copyMessage; +}; diff --git a/src/v4/core/providers/AmityUIKitProvider.tsx b/src/v4/core/providers/AmityUIKitProvider.tsx index 2c0097ee4..934a4720d 100644 --- a/src/v4/core/providers/AmityUIKitProvider.tsx +++ b/src/v4/core/providers/AmityUIKitProvider.tsx @@ -25,6 +25,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { UIStyles } from '~/core/providers/UiKitProvider/styles'; import AmityUIKitManager from '../AmityUIKitManager'; import { ConfirmProvider } from '~/v4/core/providers/ConfirmProvider'; +import { NotificationProvider } from '~/v4/core/providers/NotificationProvider'; export type AmityUIKitConfig = Config; @@ -136,24 +137,26 @@ const AmityUIKitProvider: React.FC = ({ - - - - - - {children} - - - - - - - + + + + + + + {children} + + + + + + + + diff --git a/src/v4/core/providers/NotificationProvider.module.css b/src/v4/core/providers/NotificationProvider.module.css new file mode 100644 index 000000000..ab5a7aafa --- /dev/null +++ b/src/v4/core/providers/NotificationProvider.module.css @@ -0,0 +1,44 @@ +/* styles.module.css */ +.icon { + width: 1.125rem; + height: 1.125rem; + margin-right: 8px; +} + +.notifications { + position: fixed; + padding-top: 50px; + top: 0; + left: 0; + right: 0; + display: flex; + flex-direction: column; + align-items: center; + z-index: 99999; + pointer-events: none; +} + +.notificationContainer { + width: 480px; + padding: 8px 30px; + display: flex; + justify-content: center; + align-items: center; + color: white; + border-radius: 4px; + margin-bottom: 10px; + animation-duration: 0.3s; + animation-name: appear; + pointer-events: auto; + background-color: var(--asc-color-base-shade4); +} + +@keyframes appear { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} diff --git a/src/v4/core/providers/NotificationProvider.tsx b/src/v4/core/providers/NotificationProvider.tsx new file mode 100644 index 000000000..97a4c8d85 --- /dev/null +++ b/src/v4/core/providers/NotificationProvider.tsx @@ -0,0 +1,90 @@ +import React, { createContext, ReactNode, useContext, useState } from 'react'; +import Check from '~/v4/icons/Check'; +import ExclamationCircle from '~/v4/icons/ExclamationCircle'; +import Remove from '~/v4/icons/Remove'; +import styles from './NotificationProvider.module.css'; + +interface Notification { + id: number; + content: ReactNode; + icon?: ReactNode; + duration?: number; +} + +type NotificationInput = Omit & { duration?: number }; + +interface NotificationContextProps { + notifications: Notification[]; + notificationFunction: { + success: (data: Omit) => void; + info: (data: Omit) => void; + error: (data: Omit) => void; + show: (data: Omit) => void; + }; +} + +export const NotificationContext = createContext({ + notifications: [], + notificationFunction: { + success: () => {}, + info: () => {}, + error: () => {}, + show: () => {}, + }, +}); + +const DEFAULT_NOTIFICATION_DURATION = 3000; + +export const NotificationProvider: React.FC = ({ children }) => { + const [notifications, setNotifications] = useState([]); + + const removeNotification = (id: number) => + setNotifications && + setNotifications((prevNotifications) => + prevNotifications.filter((notification) => notification.id !== id), + ); + + const addNotifications = (data: NotificationInput) => { + const id = Date.now(); + setNotifications((prevNotifications) => [ + ...prevNotifications, + { + id, + ...data, + }, + ]); + + setTimeout(() => { + removeNotification(id); + }, data?.duration || DEFAULT_NOTIFICATION_DURATION); + }; + + return ( + ) => + addNotifications({ ...data, icon: }), + info: (data: Omit) => + addNotifications({ ...data, icon: }), + error: (data: Omit) => + addNotifications({ ...data, icon: }), + show: (data: Omit) => addNotifications(data), + }, + }} + > + {children} + + ); +}; + +export const useNotificationData = () => { + const { notifications } = useContext(NotificationContext); + return notifications; +}; + +export const useNotifications = () => { + const { notificationFunction } = useContext(NotificationContext); + return notificationFunction; +}; diff --git a/src/v4/social/components/ViewStoryPage/index.tsx b/src/v4/social/components/ViewStoryPage/index.tsx index f188769a7..bb81e37c0 100644 --- a/src/v4/social/components/ViewStoryPage/index.tsx +++ b/src/v4/social/components/ViewStoryPage/index.tsx @@ -5,7 +5,6 @@ import { extractColors } from 'extract-colors'; import { FinalColor } from 'extract-colors/lib/types/Color'; import useImage from '~/core/hooks/useImage'; import { useIntl } from 'react-intl'; -import { notification } from '~/core/components/Notification'; import { useMedia } from 'react-use'; import useStories from '~/social/hooks/useStories'; @@ -23,6 +22,7 @@ import { checkStoryPermission } from '~/utils'; import { AmityDraftStoryPage } from '../../pages'; import { useStoryContext } from '../../providers/StoryProvider'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; +import { useNotifications } from '~/v4/core/providers/NotificationProvider'; interface StoryViewerProps { pageId: 'story_page'; @@ -36,6 +36,7 @@ const StoryViewer = ({ pageId, targetId, duration = 5000, onClose }: StoryViewer const pageConfig = getConfig(`${pageId}/*/*`); const isPageExcluded = isExcluded(`${pageId}/*/*`); const { confirm } = useConfirmContext(); + const notification = useNotifications(); if (isPageExcluded) return null; diff --git a/src/v4/social/internal-components/Comment/index.tsx b/src/v4/social/internal-components/Comment/index.tsx index 3c9eaafe4..b93888388 100644 --- a/src/v4/social/internal-components/Comment/index.tsx +++ b/src/v4/social/internal-components/Comment/index.tsx @@ -3,7 +3,6 @@ import { FormattedMessage, useIntl } from 'react-intl'; import useComment from '~/social/hooks/useComment'; -import { notification } from '~/core/components/Notification'; import useMention from '~/v4/chat/hooks/useMention'; import { @@ -50,6 +49,7 @@ import { ReactionList } from '~/v4/social/components/ReactionList'; import { useTheme } from 'styled-components'; import useGetStoryByStoryId from '../../hooks/useGetStoryByStoryId'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; +import { useNotifications } from '~/v4/core/providers/NotificationProvider'; const REPLIES_PER_PAGE = 5; @@ -120,6 +120,7 @@ const Comment = ({ const [bottomSheet, setBottomSheet] = useState(false); const [selectedCommentId, setSelectedCommentId] = useState(''); const { confirm } = useConfirmContext(); + const notification = useNotifications(); const commentAuthor = useUser(comment?.userId); const commentAuthorAvatar = useImage({ fileId: commentAuthor?.avatarFileId, imageSize: 'small' }); diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index a097fc000..3ddbfe255 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -16,13 +16,13 @@ import { AspectRatioButton, BackButton, HyperLinkButton, ShareStoryButton } from import { useStoryContext } from '../../providers/StoryProvider'; import { StoryVideoPreview } from './styles'; import { StoryRepository } from '@amityco/ts-sdk'; -import { notification } from '~/core/components/Notification'; import { HyperLink } from '../../elements/HyperLink'; import { HyperlinkFormContainer } from '../../components/HyperLinkConfig/styles'; import { HyperLinkConfig } from '../../components'; import { useNavigation } from '~/social/providers/NavigationProvider'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; +import { useNotifications } from '~/v4/core/providers/NotificationProvider'; type AmityStoryMediaType = { type: 'image'; url: string } | { type: 'video'; url: string }; @@ -44,6 +44,7 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor const { navigationBehavior } = usePageBehavior(); const [isHyperLinkBottomSheetOpen, setIsHyperLinkBottomSheetOpen] = useState(false); const { confirm } = useConfirmContext(); + const notification = useNotifications(); const [hyperLink, setHyperLink] = useState< { diff --git a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx index a29e0f9a6..67f6e9d65 100644 --- a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx @@ -8,7 +8,6 @@ import { useMedia } from 'react-use'; import { useIntl } from 'react-intl'; import { FinalColor } from 'extract-colors/lib/types/Color'; -import { notification } from '~/core/components/Notification'; import { StoryRepository } from '@amityco/ts-sdk'; import { CreateStoryButton } from '../../elements'; import { Trash2Icon } from '~/icons'; @@ -32,6 +31,7 @@ import { AmityDraftStoryPage } from '..'; import { checkStoryPermission } from '~/utils'; import { useStoryContext } from '../../providers/StoryProvider'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; +import { useNotifications } from '~/v4/core/providers/NotificationProvider'; interface CommunityFeedStoryProps { communityId: string; @@ -42,6 +42,7 @@ const DURATION = 5000; export const CommunityFeedStory = ({ communityId }: CommunityFeedStoryProps) => { const { onBack } = useNavigation(); const { confirm } = useConfirmContext(); + const notification = useNotifications(); const { stories } = useStories({ targetId: communityId, diff --git a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx index d6fd00a04..59b99b11d 100644 --- a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx @@ -8,7 +8,6 @@ import { useMedia } from 'react-use'; import { useIntl } from 'react-intl'; import { FinalColor } from 'extract-colors/lib/types/Color'; -import { notification } from '~/core/components/Notification'; import { StoryRepository } from '@amityco/ts-sdk'; import { CreateStoryButton } from '../../elements'; import { Trash2Icon } from '~/icons'; @@ -32,6 +31,7 @@ import { AmityDraftStoryPage } from '..'; import { checkStoryPermission } from '~/utils'; import { useStoryContext } from '../../providers/StoryProvider'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; +import { useNotifications } from '~/v4/core/providers/NotificationProvider'; interface CommunityFeedStoryProps { communityId: string; @@ -42,6 +42,7 @@ const DURATION = 5000; export const GlobalFeedStory = ({ communityId }: CommunityFeedStoryProps) => { const { onBack } = useNavigation(); const { confirm } = useConfirmContext(); + const notification = useNotifications(); const { stories } = useStories({ targetId: communityId, diff --git a/src/v4/utils/copyMessage.tsx b/src/v4/utils/copyMessage.tsx deleted file mode 100644 index a702720b1..000000000 --- a/src/v4/utils/copyMessage.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import { FormattedMessage } from 'react-intl'; -import { notification } from '~/v4/core/components/Notification'; - -export const copyMessage = (message: string) => { - navigator.clipboard.writeText(message).then(() => { - notification.show({ - content: , - }); - }); -}; diff --git a/src/v4/utils/index.ts b/src/v4/utils/index.ts index 085a82c06..ed2618a9b 100644 --- a/src/v4/utils/index.ts +++ b/src/v4/utils/index.ts @@ -1,4 +1,3 @@ -export { copyMessage } from './copyMessage'; export { deleteMessage } from './deleteMessage'; export { flagMessage } from './flagMessage'; export * from './generateShadeColors'; From 0197c270fba18c01a0cc4ad5e193ed348de19897 Mon Sep 17 00:00:00 2001 From: Bonn Date: Wed, 24 Apr 2024 14:07:03 +0700 Subject: [PATCH 034/300] fix: ASC-00000 - add login step on UiKitProvider (#287) * fix: login * fix: Update src/core/providers/UiKitProvider/index.tsx --- src/core/providers/UiKitProvider/index.tsx | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/core/providers/UiKitProvider/index.tsx b/src/core/providers/UiKitProvider/index.tsx index 140e224f2..ca56d683e 100644 --- a/src/core/providers/UiKitProvider/index.tsx +++ b/src/core/providers/UiKitProvider/index.tsx @@ -58,6 +58,7 @@ const UiKitProvider = ({ apiKey, apiRegion, apiEndpoint, + authToken, userId, displayName, customComponents = {}, @@ -68,7 +69,6 @@ const UiKitProvider = ({ actionHandlers, onConnectionStatusChange, onDisconnected, - pageBehavior, }: UiKitProviderProps) => { const queryClient = new QueryClient(); const [isConnected, setIsConnected] = useState(false); @@ -98,6 +98,20 @@ const UiKitProvider = ({ setClient(ascClient); } +await ASCClient.login( + { userId, displayName, authToken }, + { + sessionWillRenewAccessToken(renewal) { + // secure mode + if (authToken) { + renewal.renewWithAuthToken(authToken); + return; + } + + renewal.renew(); + }, + }, + ); setIsConnected(true); if (stateChangeRef.current == null) { @@ -114,7 +128,11 @@ const UiKitProvider = ({ } useEffect(() => { - login(); + async function run() { + await login(); + } + + run(); return () => { stateChangeRef.current?.(); From 0942cb07912fbee39d60f1fbb6585d187001ac2f Mon Sep 17 00:00:00 2001 From: Pitchaya T <33589608+ptchayap@users.noreply.github.com> Date: Wed, 24 Apr 2024 15:45:43 +0700 Subject: [PATCH 035/300] fix: message remain on compose bar after sending success (#289) --- .../chat/components/AmityLiveChatMessageComposeBar/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx b/src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx index ef558cfcd..a4435f8dd 100644 --- a/src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx +++ b/src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx @@ -81,6 +81,8 @@ export const AmityLiveChatMessageComposeBar = ({ metadata, parentId: replyMessage?.messageId || undefined, }); + + onChange({ text: '', plainText: '', mentions: [] }); } catch (error) { const errorMessage = (error as Error).message; let notificationMessage = formatMessage({ id: 'livechat.error.sendingMessage' }); @@ -96,7 +98,7 @@ export const AmityLiveChatMessageComposeBar = ({ notification.error({ content: notificationMessage, }); - onChange({ text, plainText: text, mentions: [] }); + return; } From ab87cd0158defe95ae9e6fba2da69f14227ebf4e Mon Sep 17 00:00:00 2001 From: Pitchaya T <33589608+ptchayap@users.noreply.github.com> Date: Thu, 25 Apr 2024 10:44:10 +0700 Subject: [PATCH 036/300] fix: user muted state (#293) --- src/i18n/en.json | 3 +++ .../AmityLiveChatMessageComposeBar/index.tsx | 8 +++++-- .../hooks/collections/useSearchChannelUser.ts | 2 +- .../ChatContainer/ChatReadyState.tsx | 21 +++++++++++++++---- .../ChatContainer/styles.module.css | 6 +++--- .../components/InputText/styles.module.css | 6 +----- 6 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/i18n/en.json b/src/i18n/en.json index 2e08c43e8..2e77c1e71 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -421,6 +421,9 @@ "livechat.delete.message.error": "Unable to delete message. Please try again.", + "livechat.member.muted": "You’ve been muted by the channel moderator", + "livechat.member.muted.error": "User is muted", + "livechat.member.banned.title": "You are banned from chat", "livechat.member.banned.description": "You won’t be able to participate in this chat until you’ve been unbanned.", "livechat.report.message.success": "Message reported", diff --git a/src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx b/src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx index a4435f8dd..90877b8df 100644 --- a/src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx +++ b/src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx @@ -24,6 +24,7 @@ interface AmityLiveChatMessageComposeBarProps { channel: Amity.Channel; composeAction: ComposeActionTypes; suggestionRef?: RefObject; + disabled?: boolean; } type ComposeBarMention = { @@ -38,6 +39,7 @@ export const AmityLiveChatMessageComposeBar = ({ channel, suggestionRef, composeAction: { replyMessage, mentionMessage, clearReplyMessage, clearMention }, + disabled, }: AmityLiveChatMessageComposeBarProps) => { const [mentionList, setMentionList] = useState<{ [key: ComposeBarMention['id']]: ComposeBarMention; @@ -85,7 +87,7 @@ export const AmityLiveChatMessageComposeBar = ({ onChange({ text: '', plainText: '', mentions: [] }); } catch (error) { const errorMessage = (error as Error).message; - let notificationMessage = formatMessage({ id: 'livechat.error.sendingMessage' }); + let notificationMessage = formatMessage({ id: 'livechat.error.sendingMessage.error' }); if (errorMessage === 'Amity SDK (400308): Text contain blocked word') { notificationMessage = formatMessage({ id: 'livechat.error.sendingMessage.blockedWord' }); @@ -93,6 +95,8 @@ export const AmityLiveChatMessageComposeBar = ({ errorMessage === 'Amity SDK (400309): Data contain link that is not in whitelist' ) { notificationMessage = formatMessage({ id: 'livechat.error.sendingMessage.notAllowLink' }); + } else if (errorMessage === 'Amity SDK (400302): User is muted') { + notificationMessage = formatMessage({ id: 'livechat.member.muted.error' }); } notification.error({ @@ -119,7 +123,7 @@ export const AmityLiveChatMessageComposeBar = ({ suggestionRef={suggestionRef} data-qa-anchor="live-chat-compose-bar" multiline - disabled={channel.isMuted} + disabled={disabled} placeholder={ componentConfig?.placeholder_text || formatMessage({ diff --git a/src/v4/chat/hooks/collections/useSearchChannelUser.ts b/src/v4/chat/hooks/collections/useSearchChannelUser.ts index faaca7df7..ae4da933b 100644 --- a/src/v4/chat/hooks/collections/useSearchChannelUser.ts +++ b/src/v4/chat/hooks/collections/useSearchChannelUser.ts @@ -9,7 +9,7 @@ const useSearchChannelUser = ( const { items, ...rest } = useLiveCollection({ fetcher: ChannelRepository.Membership.searchMembers, params: { channelId, search: search || '', memberships }, - shouldCall: () => !!channelId && !!search, + shouldCall: () => !!channelId, }); return { diff --git a/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ChatReadyState.tsx b/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ChatReadyState.tsx index ec47ac2ab..9303dd7a7 100644 --- a/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ChatReadyState.tsx +++ b/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ChatReadyState.tsx @@ -13,10 +13,15 @@ import mentionStyles from '~/v4/core/components/InputText/styles.module.css'; import { FormattedMessage } from 'react-intl'; import useCurrentUserChannelMembership from '~/v4/chat/hooks/useCurrentUserChannelMembership'; import CommentAltExclamation from '~/v4/icons/CommentAltExclamation'; +import useSearchChannelUser from '~/v4/chat/hooks/collections/useSearchChannelUser'; +import useSDK from '~/core/hooks/useSDK'; const ChatReadyState = ({ channel }: { channel: Amity.Channel }) => { const isOnline = useConnectionStates(); - const currentUserMembership = useCurrentUserChannelMembership(channel.channelId); + + const currentUserId = useSDK().currentUserId; + const { channelMembers } = useSearchChannelUser(channel.channelId, ['member', 'banned', 'muted']); + const currentUserMembership = channelMembers.find((member) => member.userId === currentUserId); const [replyMessage, setReplyMessage] = useState | undefined>(undefined); const [mentionMessage, setMentionMessage] = useState | undefined>( @@ -57,14 +62,21 @@ const ChatReadyState = ({ channel }: { channel: Amity.Channel }) => { {isOnline && ( <>
- {channel.isMuted && ( + {channel.isMuted ? (
- +
- )} + ) : currentUserMembership?.isMuted ? ( +
+ + + + +
+ ) : null} {replyMessage && ( {
Date: Thu, 25 Apr 2024 11:04:12 +0700 Subject: [PATCH 037/300] fix: ASC-22026 - moderator cannot send message on muted channel (#294) * fix: user muted state * fix: moderator cannot send message on muted channel --- .../AmityLiveChatPage/ChatContainer/ChatReadyState.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ChatReadyState.tsx b/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ChatReadyState.tsx index 9303dd7a7..8d414bbe4 100644 --- a/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ChatReadyState.tsx +++ b/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ChatReadyState.tsx @@ -11,14 +11,16 @@ import MutedIcon from '~/v4/icons/Muted'; import { Typography } from '~/v4/core/components/Typography'; import mentionStyles from '~/v4/core/components/InputText/styles.module.css'; import { FormattedMessage } from 'react-intl'; -import useCurrentUserChannelMembership from '~/v4/chat/hooks/useCurrentUserChannelMembership'; import CommentAltExclamation from '~/v4/icons/CommentAltExclamation'; import useSearchChannelUser from '~/v4/chat/hooks/collections/useSearchChannelUser'; import useSDK from '~/core/hooks/useSDK'; +import useChannelPermission from '~/v4/chat/hooks/useChannelPermission'; const ChatReadyState = ({ channel }: { channel: Amity.Channel }) => { const isOnline = useConnectionStates(); + const { isModerator } = useChannelPermission(channel.channelId); + const currentUserId = useSDK().currentUserId; const { channelMembers } = useSearchChannelUser(channel.channelId, ['member', 'banned', 'muted']); const currentUserMembership = channelMembers.find((member) => member.userId === currentUserId); @@ -62,7 +64,7 @@ const ChatReadyState = ({ channel }: { channel: Amity.Channel }) => { {isOnline && ( <>
- {channel.isMuted ? ( + {!isModerator && channel.isMuted ? (
@@ -96,7 +98,7 @@ const ChatReadyState = ({ channel }: { channel: Amity.Channel }) => {
Date: Thu, 25 Apr 2024 11:08:18 +0700 Subject: [PATCH 038/300] fix: ASC-219999 - incorrect comment count color (#283) * fix: font * fix: story tab gap * fix: modal --- src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx | 2 +- .../social/elements/CommentButton/CommentButton.module.css | 5 +++-- src/v4/social/elements/ReactButton/styles.tsx | 1 - 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx b/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx index 1873d88cb..47630fb9c 100644 --- a/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx +++ b/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx @@ -37,7 +37,7 @@ export const StoryTabGlobalFeed: React.FC = () => { next={loadMoreStories} hasMore={hasMore} loader={

Loading...

} - style={{ display: 'flex', overflowX: 'auto' }} + style={{ display: 'flex', overflowX: 'auto', gap: '0.5rem' }} scrollThreshold={0.9} scrollableTarget="containerRef" > diff --git a/src/v4/social/elements/CommentButton/CommentButton.module.css b/src/v4/social/elements/CommentButton/CommentButton.module.css index ef3a87d73..29c15780b 100644 --- a/src/v4/social/elements/CommentButton/CommentButton.module.css +++ b/src/v4/social/elements/CommentButton/CommentButton.module.css @@ -1,11 +1,12 @@ .uiCommentButton { + font-family: "Inter"; font-weight: var(--asc-text-font-weight-bold); - color: var(--asc-color-base-inverse); + color: var(--asc-color-white); display: flex; align-items: center; justify-content: space-between; gap: var(--asc-spacing-xxs2); - border-radius: var(--asc-border-radius-full); + border-radius: 1.5rem; padding: var(--asc-spacing-s1) var(--asc-spacing-s2); background-color: var(--asc-color-base-default); cursor: pointer; diff --git a/src/v4/social/elements/ReactButton/styles.tsx b/src/v4/social/elements/ReactButton/styles.tsx index 01548cb86..467d54247 100644 --- a/src/v4/social/elements/ReactButton/styles.tsx +++ b/src/v4/social/elements/ReactButton/styles.tsx @@ -2,7 +2,6 @@ import styled from 'styled-components'; export const UIReactButton = styled.button` ${({ theme }) => theme.typography.bodyBold}; - color: #fff; display: flex; align-items: center; justify-content: space-between; From 1bfad593417670f80a58e27d10f863cbc12f0225 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Thu, 25 Apr 2024 11:42:51 +0700 Subject: [PATCH 039/300] fix: ASC-21979 - can't click header on video preview (#282) * fix: use css var * fix: move v4 story * fix: move folder * fix: remove index path * fix: styled to css module --- .../pages/Application/Application.module.css | 24 ++++++ src/v4/social/pages/Application/index.tsx | 82 +++++++++++++++++++ .../social/pages/Application/sdk.stories.tsx | 24 ++++++ .../pages/DraftsPage/DraftsPage.module.css | 47 ++++++----- src/v4/social/pages/DraftsPage/DraftsPage.tsx | 27 +++--- 5 files changed, 171 insertions(+), 33 deletions(-) create mode 100644 src/v4/social/pages/Application/Application.module.css create mode 100644 src/v4/social/pages/Application/index.tsx create mode 100644 src/v4/social/pages/Application/sdk.stories.tsx diff --git a/src/v4/social/pages/Application/Application.module.css b/src/v4/social/pages/Application/Application.module.css new file mode 100644 index 000000000..85a1fbe2a --- /dev/null +++ b/src/v4/social/pages/Application/Application.module.css @@ -0,0 +1,24 @@ +.applicationContainer { + height: 100%; + width: 100%; + } + + .styledCommunitySideMenu { + display: none; + } + + @media (min-width: 768px) { + .styledCommunitySideMenu { + min-height: 100%; + display: block; + } + } + + .wrapper { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + } \ No newline at end of file diff --git a/src/v4/social/pages/Application/index.tsx b/src/v4/social/pages/Application/index.tsx new file mode 100644 index 000000000..53e16b9fa --- /dev/null +++ b/src/v4/social/pages/Application/index.tsx @@ -0,0 +1,82 @@ +import React, { useEffect, useState } from 'react'; +import styles from './Application.module.css'; +import { PageTypes } from '~/social/constants'; +import MainLayout from '~/social/layouts/Main'; +import CommunitySideMenu from '~/social/components/CommunitySideMenu'; +import ExplorePage from '~/social/pages/Explore'; +import NewsFeedPage from '~/social/pages/NewsFeed'; +import UserFeedPage from '~/social/pages/UserFeed'; +import CategoryCommunitiesPage from '~/social/pages/CategoryCommunities'; +import CommunityEditPage from '~/social/pages/CommunityEdit'; +import ProfileSettings from '~/social/components/ProfileSettings'; +import { useNavigation } from '~/social/providers/NavigationProvider'; +import useSDK from '~/core/hooks/useSDK'; +import { AmityViewStoryPage } from '~/v4/social/pages/StoryPage'; +import { StoryProvider } from '~/v4/social/providers/StoryProvider'; +import CommunityFeed from '~/social/pages/CommunityFeed'; + +const Community = () => { + const { page } = useNavigation(); + const { client } = useSDK(); + const [socialSettings, setSocialSettings] = useState(null); + const [open, setOpen] = useState(false); + + const toggleOpen = () => { + setOpen(!open); + }; + + useEffect(() => { + if (client === null) return; + async function run() { + const settings = await client?.getSocialSettings(); + if (settings) { + setSocialSettings(settings); + } + } + run(); + }, [client]); + + return ( + +
+ + +
+ } + > + {page.type === PageTypes.Explore && } + {page.type === PageTypes.NewsFeed && ( + + )} + {page.type === PageTypes.CommunityFeed && ( + + )} + {page.type === PageTypes.ViewStory && ( +
+ +
+ )} + {page.type === PageTypes.CommunityEdit && ( + + )} + {page.type === PageTypes.Category && ( + + )} + {page.type === PageTypes.UserFeed && ( + + )} + {page.type === PageTypes.UserEdit && } + +
+ + ); +}; + +export default Community; diff --git a/src/v4/social/pages/Application/sdk.stories.tsx b/src/v4/social/pages/Application/sdk.stories.tsx new file mode 100644 index 000000000..e6875f829 --- /dev/null +++ b/src/v4/social/pages/Application/sdk.stories.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import UiKitApp from './index'; + +export default { + title: 'V4/Social', +}; + +export const SDKCommunityAppV4 = { + render: (props) => { + return ; + }, + name: 'ApplicationV4', + + args: { + shouldHideExplore: false, + socialCommunityCreationButtonVisible: true, + }, + + argTypes: { + shouldHideExplore: { control: { type: 'boolean' } }, + socialCommunityCreationButtonVisible: { control: { type: 'boolean' } }, + onMemberClick: { action: 'onMemberClick()' }, + }, +}; diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.module.css b/src/v4/social/pages/DraftsPage/DraftsPage.module.css index 210791a06..fddc35208 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.module.css +++ b/src/v4/social/pages/DraftsPage/DraftsPage.module.css @@ -4,6 +4,16 @@ width: 23.438rem; height: 40.875rem; position: relative; + font-family: var(--asc-text-global-font-family); + font-style: var(--asc-text-global-font-style); +} + +.headerContainer { + position: absolute; + top: 0; + left: 0; + right: 0; + z-index: 1; } .header { @@ -11,31 +21,29 @@ width: 100%; justify-content: space-between; align-items: center; - position: absolute; - top: 0; - padding: 1rem; + padding: var(--asc-spacing-m1); } .topRightButtons { display: flex; width: 100%; justify-content: flex-end; - gap: 8px; + gap: var(--asc-spacing-s2); } .mainContainer { - flex: 1; - display: flex; - justify-content: center; - align-items: center; - overflow: hidden; - border-top-left-radius: 0.75rem; - border-top-right-radius: 0.75rem; - background: linear-gradient( - 180deg, - var(--draft-image-container-color-0, #000) 0%, - var(--draft-image-container-color-last, #000) 100% - ); + flex: 1; + display: flex; + justify-content: center; + align-items: center; + overflow: hidden; + border-top-left-radius: var(--asc-border-radius-lg); + border-top-right-radius: var(--asc-border-radius-lg); + background: linear-gradient( + 180deg, + var(--draft-image-container-color-0, var(--asc-color-black)) 0%, + var(--draft-image-container-color-last, var(--asc-color-black)) 100% + ); } .previewImage { @@ -46,8 +54,7 @@ .footer { display: flex; justify-content: flex-end; - padding: 16px; - background-color: black; + padding: var(--asc-spacing-m1); + background-color: var(--asc-color-black); width: 100%; -} - +} \ No newline at end of file diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index 3ddbfe255..fc55bd598 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -1,7 +1,6 @@ import React, { useEffect, useState } from 'react'; import { useIntl } from 'react-intl'; import { extractColors } from 'extract-colors'; - import { readFileAsync } from '~/helpers'; import useUser from '~/core/hooks/useUser'; import useSDK from '~/core/hooks/useSDK'; @@ -178,21 +177,23 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor return ( <>
-
- -
- {mediaType?.type === 'image' && ( - +
+ +
+ {mediaType?.type === 'image' && ( + + )} + setIsHyperLinkBottomSheetOpen(true)} /> - )} - setIsHyperLinkBottomSheetOpen(true)} - /> +
From e141fb80524f08354cc0585ab6bc48d2ac68774b Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Thu, 25 Apr 2024 11:57:38 +0700 Subject: [PATCH 040/300] fix: use v4 (#295) --- src/v4/core/components/ConfirmModal/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v4/core/components/ConfirmModal/index.tsx b/src/v4/core/components/ConfirmModal/index.tsx index 13dab2ecf..7bb1f5804 100644 --- a/src/v4/core/components/ConfirmModal/index.tsx +++ b/src/v4/core/components/ConfirmModal/index.tsx @@ -3,7 +3,7 @@ import Modal from '~/v4/core/components/Modal'; import { Button } from '~/v4/core/components/Button'; import clsx from 'clsx'; import styles from './styles.module.css'; -import { useConfirmContext } from '~/core/providers/ConfirmProvider'; +import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; const Confirm = ({ 'data-qa-anchor': dataQaAnchor = '', From 5b229e2924708196701b54ca8083c557c6847873 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Thu, 25 Apr 2024 12:28:18 +0700 Subject: [PATCH 041/300] fix: ASC-20502 - shouldAllowCreation condition (#296) * fix: shouldAllowCreation * fix: type --- src/v4/social/components/CommentTray/CommentTray.tsx | 4 ++-- .../StoryCommentComposeBar/StoryCommentComposeBar.tsx | 4 ++-- .../internal-components/StoryViewer/Renderers/Image.tsx | 4 ++-- .../internal-components/StoryViewer/Renderers/Video.tsx | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/v4/social/components/CommentTray/CommentTray.tsx b/src/v4/social/components/CommentTray/CommentTray.tsx index fb3636e06..93e33403e 100644 --- a/src/v4/social/components/CommentTray/CommentTray.tsx +++ b/src/v4/social/components/CommentTray/CommentTray.tsx @@ -11,7 +11,7 @@ interface CommentTrayProps { referenceId: string; community: Amity.Community; shouldAllowInteraction: boolean; - shouldAllowCreation: boolean; + shouldAllowCreation?: boolean; } export const CommentTray = ({ @@ -19,7 +19,7 @@ export const CommentTray = ({ referenceId, community = {} as Amity.Community, shouldAllowInteraction = true, - shouldAllowCreation = true, + shouldAllowCreation = false, }: CommentTrayProps) => { const [isReplying, setIsReplying] = useState(false); const [replyTo, setReplyTo] = useState(null); diff --git a/src/v4/social/internal-components/StoryCommentComposeBar/StoryCommentComposeBar.tsx b/src/v4/social/internal-components/StoryCommentComposeBar/StoryCommentComposeBar.tsx index 100c93c9f..79b3aecfc 100644 --- a/src/v4/social/internal-components/StoryCommentComposeBar/StoryCommentComposeBar.tsx +++ b/src/v4/social/internal-components/StoryCommentComposeBar/StoryCommentComposeBar.tsx @@ -44,7 +44,7 @@ export const StoryCommentComposeBar = ({ data: { text: commentText, }, - mentionees, + mentionees: mentionees as Amity.UserMention[], metadata, }); }; @@ -62,7 +62,7 @@ export const StoryCommentComposeBar = ({ }, parentId: replyTo?.commentId, metadata, - mentionees, + mentionees: mentionees as Amity.UserMention[], }); }; diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index 63e653e28..1cddc6862 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -208,8 +208,8 @@ export const renderer: CustomRenderer = ({ story, action, config }) => { referenceId={storyId} referenceType="story" community={community as Amity.Community} - shouldAllowCreation={community?.allowCommentInStory || true} - shouldAllowInteraction={isJoined || true} + shouldAllowCreation={community?.allowCommentInStory} + shouldAllowInteraction={isJoined} /> diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx index 5831ae7c7..5e450346e 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx @@ -263,8 +263,8 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler (selectedComment?.referenceType as Amity.CommentReferenceType) || 'story' } community={community as Amity.Community} - shouldAllowCreation={community?.allowCommentInStory || true} - shouldAllowInteraction={isJoined || true} + shouldAllowCreation={community?.allowCommentInStory} + shouldAllowInteraction={isJoined} /> From 4955abc3e2a542e60c2f2bb72b35fa8c62f97434 Mon Sep 17 00:00:00 2001 From: Bonn Date: Thu, 25 Apr 2024 13:08:38 +0700 Subject: [PATCH 042/300] fix: ASC-20883 - remove latestComments prop (#290) * fix: pass latestComments to CommentList component * fix: remove latestComments prop --- src/social/components/CommentList/index.tsx | 31 ++++--------------- .../EngagementBar/UIEngagementBar.tsx | 19 +++++++----- 2 files changed, 17 insertions(+), 33 deletions(-) diff --git a/src/social/components/CommentList/index.tsx b/src/social/components/CommentList/index.tsx index bc888dcba..0f4c71f02 100644 --- a/src/social/components/CommentList/index.tsx +++ b/src/social/components/CommentList/index.tsx @@ -14,7 +14,6 @@ interface CommentListProps { readonly?: boolean; isExpanded?: boolean; limit?: number; - latestComments?: Amity.Comment[]; } const CommentList = ({ @@ -24,21 +23,15 @@ const CommentList = ({ limit = 5, readonly = false, isExpanded = true, - latestComments, }: CommentListProps) => { const { formatMessage } = useIntl(); const isReplyComment = !!parentId; - const { - comments: fetchedComments, - hasMore, - loadMore, - } = useCommentsCollection({ + const { comments, hasMore, loadMore } = useCommentsCollection({ parentId, referenceId, referenceType, limit, - shouldCall: () => latestComments?.length === 0, }); usePostSubscription({ @@ -47,8 +40,6 @@ const CommentList = ({ shouldSubscribe: () => referenceType === 'post' && !parentId, }); - const comments = latestComments || fetchedComments; - const loadMoreText = isReplyComment ? formatMessage({ id: 'collapsible.viewMoreReplies' }) : formatMessage({ id: 'collapsible.viewMoreComments' }); @@ -59,11 +50,7 @@ const CommentList = ({ ) : null; - if ( - (latestComments?.length === 0 || comments.length === 0) && - referenceType === 'story' && - !isReplyComment - ) { + if (comments.length === 0 && referenceType === 'story' && !isReplyComment) { return ( {formatMessage({ id: 'storyViewer.commentSheet.empty' })} @@ -71,7 +58,7 @@ const CommentList = ({ ); } - if (latestComments?.length === 0 || comments.length === 0) return null; + if (comments.length === 0) return null; return ( ( - - )) - : comments.map((comment) => ( - - )) - } + contentSlot={comments.map((comment) => ( + + ))} /> ); }; diff --git a/src/social/components/EngagementBar/UIEngagementBar.tsx b/src/social/components/EngagementBar/UIEngagementBar.tsx index 9b236fc4c..08340c882 100644 --- a/src/social/components/EngagementBar/UIEngagementBar.tsx +++ b/src/social/components/EngagementBar/UIEngagementBar.tsx @@ -73,7 +73,9 @@ const UIEngagementBar = ({ - + {latestComments.length > 0 ? ( + + ) : null} {isComposeBarDisplayed && ( - + {latestComments.length > 0 ? ( + + ) : null} )} From 313ffdf59f8f9fb627e4ba0fedc4fd5b47e3d57b Mon Sep 17 00:00:00 2001 From: Bonn Date: Thu, 25 Apr 2024 14:46:43 +0700 Subject: [PATCH 043/300] fix: add v3 provider (#298) --- src/v4/core/providers/AmityUIKitProvider.tsx | 46 ++++++++++++-------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/src/v4/core/providers/AmityUIKitProvider.tsx b/src/v4/core/providers/AmityUIKitProvider.tsx index 934a4720d..d7f7b4a97 100644 --- a/src/v4/core/providers/AmityUIKitProvider.tsx +++ b/src/v4/core/providers/AmityUIKitProvider.tsx @@ -12,7 +12,9 @@ import NavigationProvider from '~/social/providers/NavigationProvider'; import ConfigProvider from '~/social/providers/ConfigProvider'; import { ConfirmComponent } from '~/v4/core/components/ConfirmModal'; +import { ConfirmComponent as LegacyConfirmComponent } from '~/core/components/Confirm'; import { NotificationsContainer } from '~/v4/core/components/Notification'; +import { NotificationsContainer as LegacyNotificationsContainer } from '~/core/components/Notification'; import Localization from '~/core/providers/UiKitProvider/Localization'; @@ -25,7 +27,9 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { UIStyles } from '~/core/providers/UiKitProvider/styles'; import AmityUIKitManager from '../AmityUIKitManager'; import { ConfirmProvider } from '~/v4/core/providers/ConfirmProvider'; +import { ConfirmProvider as LegacyConfirmProvider } from '~/core/providers/ConfirmProvider'; import { NotificationProvider } from '~/v4/core/providers/NotificationProvider'; +import { NotificationProvider as LegacyNotificationProvider } from '~/core/providers/NotificationProvider'; export type AmityUIKitConfig = Config; @@ -138,24 +142,30 @@ const AmityUIKitProvider: React.FC = ({ - - - - - - {children} - - - - - - - + + + + + + + + {children} + + + + + + + + + + + From 0052c57341cbff729cc342647a24375a4445e900 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Thu, 25 Apr 2024 16:56:30 +0700 Subject: [PATCH 044/300] fix: ASC-21980 - hyper link background color (#299) * fix: use v4 * fix: hyperlink position and bg color * fix: remove styled component --- .../components/HyperLinkConfig/styles.tsx | 104 ------------------ .../pages/DraftsPage/DraftsPage.module.css | 11 ++ src/v4/social/pages/DraftsPage/DraftsPage.tsx | 5 +- 3 files changed, 13 insertions(+), 107 deletions(-) delete mode 100644 src/v4/social/components/HyperLinkConfig/styles.tsx diff --git a/src/v4/social/components/HyperLinkConfig/styles.tsx b/src/v4/social/components/HyperLinkConfig/styles.tsx deleted file mode 100644 index 43c043a07..000000000 --- a/src/v4/social/components/HyperLinkConfig/styles.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import styled from 'styled-components'; -import { SecondaryButton } from '~/core/components/Button'; - -import { Trash2Icon } from '~/icons'; - -export const HyperlinkFormContainer = styled.div` - padding: 1rem; - border-radius: var(--asc-border-radius-md); - margin: auto; -`; - -export const Form = styled.form` - display: flex; - flex-direction: column; - gap: 2rem; -`; - -export const InputContainer = styled.div` - display: flex; - flex-direction: column; - gap: 0.25rem; -`; - -export const Input = styled.input<{ hasError?: boolean }>` - width: 100%; - padding: 0.5rem; - border: none; - border-bottom: 1px solid - ${({ hasError, theme }) => - hasError ? theme.v4.colors.alert.default : theme.v4.colors.base.shade4}; - outline: none; - color: ${({ hasError, theme }) => (hasError ? theme.v4.colors.alert.default : 'inherit')}; -`; - -export const Label = styled.label<{ required?: boolean }>` - ${({ theme }) => theme.v4.typography.title}; - display: block; - - &::after { - content: ${({ required }) => (required ? "'*'" : 'none')}; - color: ${({ theme }) => theme.v4.colors.alert.default}; - } -`; - -export const Description = styled.label` - ${({ theme }) => theme.v4.typography.caption}; - color: ${({ theme }) => theme.v4.colors.base.shade2}; -`; - -export const ErrorText = styled.span` - ${({ theme }) => theme.v4.typography.caption}; - color: ${({ theme }) => theme.v4.colors.alert.default}; -`; - -export const CharacterCount = styled.div` - ${({ theme }) => theme.v4.typography.caption}; - color: ${({ theme }) => theme.v4.colors.base.shade1}; - text-align: right; - margin-top: 0.3rem; -`; - -export const HeaderContainer = styled.div` - display: flex; - align-items: center; - justify-content: space-between; - padding: 0 1rem; -`; - -export const LabelContainer = styled.div` - display: flex; - justify-content: space-between; - align-items: center; -`; - -export const HeaderTitle = styled.div` - ${({ theme }) => theme.v4.typography.title}; -`; - -export const StyledSecondaryButton = styled(SecondaryButton)` - color: ${({ theme }) => theme.v4.colors.primary.default}; -`; - -export const RemoveIcon = styled(Trash2Icon)` - width: 1.5rem; - height: 1.5rem; - fill: ${({ theme }) => theme.v4.colors.alert.default}; -`; - -export const RemoveLinkButton = styled(SecondaryButton)` - ${({ theme }) => theme.v4.typography.body}; - display: flex; - justify-content: flex-start; - align-items: center; - gap: 0.5rem; - color: ${({ theme }) => theme.v4.colors.alert.default}; - border-radius: 0; -`; - -export const Divider = styled.div` - width: 100%; - height: 0.0625rem; - align-self: stretch; - background-color: ${({ theme }) => theme.v4.colors.base.shade4}; -`; diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.module.css b/src/v4/social/pages/DraftsPage/DraftsPage.module.css index fddc35208..97ea83386 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.module.css +++ b/src/v4/social/pages/DraftsPage/DraftsPage.module.css @@ -57,4 +57,15 @@ padding: var(--asc-spacing-m1); background-color: var(--asc-color-black); width: 100%; +} + +.hyperLinkContainer { + position: absolute; + background-color: transparent; + padding: 1rem; + display: flex; + justify-content: center; + bottom: 6rem; + left: 0; + right: 0; } \ No newline at end of file diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index fc55bd598..84499e35e 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -17,7 +17,6 @@ import { StoryVideoPreview } from './styles'; import { StoryRepository } from '@amityco/ts-sdk'; import { HyperLink } from '../../elements/HyperLink'; -import { HyperlinkFormContainer } from '../../components/HyperLinkConfig/styles'; import { HyperLinkConfig } from '../../components'; import { useNavigation } from '~/social/providers/NavigationProvider'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; @@ -228,7 +227,7 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor /> ) : null} {hyperLink[0]?.data?.url && ( - +
{hyperLink[0]?.data?.customText || hyperLink[0].data.url} - +
)} Date: Thu, 25 Apr 2024 21:19:40 +0700 Subject: [PATCH 045/300] feat: ASC-21665 - darkmode config (#279) * fix: type and optional field on config * fix: remove undefined checking * fix: use config from customization provider * feat: handle switch light/dark mode * feat: add configuration live chat page * feat: add config on livechat header * feat: add theme configuration message list * feat: add configuration theme on message composer * fix: modal styling * feat: add configuration for livechat notification * fix: copy message to use livechat notification * fix: pr review * fix: pr * fix: user preferred theme --- .storybook/decorators/UiKitV4Decorator.tsx | 3 +- amity-uikit.config.json | 37 ++++- .../AmityLiveChatHeader/styles.module.css | 15 +- .../AmityLiveChatMessageComposeBar/index.tsx | 1 + .../livechatMessageComposeBar.stories.tsx | 8 +- .../styles.module.css | 30 +++- .../styles.module.css | 3 +- .../HomeIndicator/styles.module.css | 2 +- .../MessageAction/index.tsx | 4 +- .../MessageAction/styles.module.css | 50 ++++-- .../MessageBubble/styles.module.css | 41 ++++- .../MessageBubbleContainer/styles.module.css | 5 +- .../MessageTextWithMention/index.tsx | 22 +-- .../MessageTextWithMention/styles.module.css | 12 +- .../LiveChatMessageContent/styles.module.css | 15 +- .../components/LiveChatNotification/index.tsx | 2 +- .../LiveChatNotification/styles.module.css | 10 +- .../ChatContainer/styles.module.css | 31 ++-- .../livechatPage.stories.tsx | 7 +- .../pages/AmityLiveChatPage/styles.module.css | 8 +- .../LiveChatNotificationProvider.module.css | 2 +- .../core/components/Button/Button.module.css | 108 ++++++------- src/v4/core/components/ConfirmModal/index.tsx | 2 +- .../components/ConfirmModal/styles.module.css | 7 +- .../core/components/HyperlinkText/index.tsx | 10 +- .../HyperlinkText/styles.module.css | 2 +- .../components/InputText/InsideInputText.tsx | 4 +- src/v4/core/components/InputText/index.tsx | 1 + .../components/InputText/styles.module.css | 4 +- src/v4/core/components/Modal/index.tsx | 2 +- .../core/components/Modal/styles.module.css | 9 +- src/v4/core/components/Notification/index.tsx | 2 +- .../SocialMentionItem/styles.module.css | 4 +- src/v4/core/hooks.ts | 6 +- .../core/providers/CustomizationProvider.tsx | 6 +- src/v4/core/providers/ThemeProvider.tsx | 145 ++++++++++++++---- src/v4/icons/CheckCircle.tsx | 16 +- src/v4/icons/ExclamationCircle.tsx | 6 +- src/v4/icons/Kebub.tsx | 16 +- .../StoryTab/StoryTabItem.module.css | 98 ++++++------ src/v4/styles/global.css | 2 +- 41 files changed, 461 insertions(+), 297 deletions(-) diff --git a/.storybook/decorators/UiKitV4Decorator.tsx b/.storybook/decorators/UiKitV4Decorator.tsx index 0e47d469e..afb8dde46 100644 --- a/.storybook/decorators/UiKitV4Decorator.tsx +++ b/.storybook/decorators/UiKitV4Decorator.tsx @@ -2,6 +2,7 @@ import React, { useCallback } from 'react'; import { AmityUIKitProvider } from '../../src/v4/core/providers'; import { Preview } from '@storybook/react'; import amityConfig from '../../amity-uikit.config.json'; +import { Config } from '../../src/v4/core/providers/CustomizationProvider'; const GLOBAL_NAME = 'user'; const global = { @@ -73,7 +74,7 @@ const decorator: NonNullable[number] = ( onConnectionStatusChange={handleConnectionStatusChange} onConnected={handleConnected} onDisconnected={handleDisconnected} - configs={amityConfig} + configs={amityConfig as Config} > diff --git a/amity-uikit.config.json b/amity-uikit.config.json index adc377baf..789219ae1 100644 --- a/amity-uikit.config.json +++ b/amity-uikit.config.json @@ -10,18 +10,20 @@ "base_shade3_color": "#a5a9b5", "base_shade4_color": "#ebecef", "alert_color": "#FA4D30", - "background_color": "#FFFFFF" + "background_color": "#FFFFFF", + "base_inverse_color": "#000000" }, "dark": { "primary_color": "#1054DE", - "secondary_color": "#292B32", + "secondary_color": "#ebecef", "base_color": "#ebecef", "base_shade1_color": "#a5a9b5", "base_shade2_color": "#6e7487", "base_shade3_color": "#40434e", "base_shade4_color": "#292b32", "alert_color": "#FA4D30", - "background_color": "#191919" + "background_color": "#191919", + "base_inverse_color": "#FFFFFF" } }, "excludes": [], @@ -165,7 +167,34 @@ "theme": {}, "close_icon": "close.png" }, - "live_chat/*/*": {}, + "live_chat/*/*": { + "theme": { + "light": { + "primary_color": "#1054DE", + "secondary_color": "#292B32", + "base_color": "#292b32", + "base_shade1_color": "#636878", + "base_shade2_color": "#898e9e", + "base_shade3_color": "#a5a9b5", + "base_shade4_color": "#ebecef", + "alert_color": "#FA4D30", + "background_color": "#FFFFFF", + "base_inverse_color": "#000000" + }, + "dark": { + "primary_color": "#1054DE", + "secondary_color": "#ebecef", + "base_color": "#ebecef", + "base_shade1_color": "#a5a9b5", + "base_shade2_color": "#6e7487", + "base_shade3_color": "#40434e", + "base_shade4_color": "#292b32", + "alert_color": "#FA4D30", + "background_color": "#191919", + "base_inverse_color": "#FFFFFF" + } + } + }, "live_chat/chat_header/*": {}, "live_chat/message_list/*": {}, "live_chat/message_composer/*": { diff --git a/src/v4/chat/components/AmityLiveChatHeader/styles.module.css b/src/v4/chat/components/AmityLiveChatHeader/styles.module.css index 09580e17d..d1d81ab14 100644 --- a/src/v4/chat/components/AmityLiveChatHeader/styles.module.css +++ b/src/v4/chat/components/AmityLiveChatHeader/styles.module.css @@ -7,11 +7,17 @@ } .displayName { - color: var(--asc-color-base-inverse); + color: var( + --live-chat-chat-header-asc-color-base-inverse, + var(--live-chat-asc-color-base-inverse, var(--asc-color-base-inverse)) + ); } .memberCount { - color: var(--asc-color-base-default); + color: var( + --live-chat-chat-header-asc-color-base-default, + var(--live-chat-asc-color-base-default, var(--asc-color-base-default)) + ); display: flex; align-items: center; } @@ -20,5 +26,8 @@ width: 0.75rem; height: 0.75rem; margin-right: var(--asc-spacing-xxs2); - fill: var(--asc-color-base-default); + fill: var( + --live-chat-chat-header-asc-color-base-default, + var(--live-chat-asc-color-base-default, var(--asc-color-base-default)) + ); } diff --git a/src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx b/src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx index 90877b8df..25314d9dc 100644 --- a/src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx +++ b/src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx @@ -142,6 +142,7 @@ export const AmityLiveChatMessageComposeBar = ({ }} value={markup} queryMentionees={queryMentionees} + mentionColor={styles.mentionText} />
diff --git a/src/v4/chat/components/AmityLiveChatMessageComposeBar/livechatMessageComposeBar.stories.tsx b/src/v4/chat/components/AmityLiveChatMessageComposeBar/livechatMessageComposeBar.stories.tsx index 29b22195b..693604f08 100644 --- a/src/v4/chat/components/AmityLiveChatMessageComposeBar/livechatMessageComposeBar.stories.tsx +++ b/src/v4/chat/components/AmityLiveChatMessageComposeBar/livechatMessageComposeBar.stories.tsx @@ -19,13 +19,7 @@ const SampleLiveChatHeader = () => { return
No channels
; return (
- {}} - mentionsMessage={undefined} - clearMention={() => {}} - /> +
); }; diff --git a/src/v4/chat/components/AmityLiveChatMessageComposeBar/styles.module.css b/src/v4/chat/components/AmityLiveChatMessageComposeBar/styles.module.css index 2c3aef85f..d6f78cbef 100644 --- a/src/v4/chat/components/AmityLiveChatMessageComposeBar/styles.module.css +++ b/src/v4/chat/components/AmityLiveChatMessageComposeBar/styles.module.css @@ -2,7 +2,10 @@ border-radius: var(--asc-border-radius-xxl); width: 1rem; height: 1rem; - background-color: var(--asc-color-primary); + background-color: var( + --live-chat-message-composer-asc-color-primary-default, + var(--live-chat-asc-color-primary-default, var(--asc-color-primary-default)) + ); cursor: pointer; padding: var(--asc-spacing-xxs2); fill: white; @@ -34,7 +37,7 @@ gap: var(--asc-spacing-s2); padding: var(--asc-spacing-s1) var(--asc-spacing-s2); background-color: inherit; - border-top: 1px solid var(--asc-color-base-shade4); + box-shadow: 0px -1px 0px 0px var(--live-chat-message-composer-asc-color-base-shade4, var(--live-chat-asc-color-base-shade4, var(--asc-color-base-shade4))); } .textInputContainer { @@ -44,8 +47,10 @@ & > div { width: 100%; border-radius: var(--asc-border-radius-xxl); - border: 1px solid var(--asc-color-base-background); - background: var(--asc-color-secondary-shade4); + background: var( + --live-chat-message-composer-asc-color-secondary-shade4, + var(--live-chat-asc-color-secondary-shade4, var(--asc-color-secondary-shade4)) + ); textarea { padding: 0.563rem 1rem; @@ -60,8 +65,14 @@ padding: var(--asc-spacing-s1) var(--asc-spacing-s2); border-radius: var(--asc-border-radius-sm); border: var(--asc-border-radius-none); - background-color: var(--asc-color-secondary-default); - color: var(--asc-color-base-inverse); + background-color: var( + --live-chat-message-composer-asc-color-secondary, + var(--live-chat-asc-color-secondary, var(--asc-color-secondary-default)) + ); + color: var( + --live-chat-message-composer-asc-color-base-inverse, + var(--live-chat-asc-color-base-inverse, var(--asc-color-base-inverse)) + ); display: flex; align-items: center; @@ -70,3 +81,10 @@ font-weight: var(--asc-text-font-weight-light); } } + +.mentionText { + color: var( + --live-chat-message-composer-asc-color-primary-default, + var(--live-chat-asc-color-primary-default, var(--asc-color-primary-default)) + ) !important; +} diff --git a/src/v4/chat/components/AmityLiveChatMessageList/styles.module.css b/src/v4/chat/components/AmityLiveChatMessageList/styles.module.css index bda68abe2..0a394e098 100644 --- a/src/v4/chat/components/AmityLiveChatMessageList/styles.module.css +++ b/src/v4/chat/components/AmityLiveChatMessageList/styles.module.css @@ -29,7 +29,8 @@ width: 100%; height: 100%; align-items: center; - color: var(--asc-color-secondary-shade2); + color: var(--live-chat-message-list-asc-color-secondary-shade2), + var(--live-chat-asc-color-secondary-shade2, var(--asc-color-secondary-shade2)); font-weight: var(--asc-text-font-weight-light); font-size: var(--asc-text-font-size-md); } diff --git a/src/v4/chat/components/HomeIndicator/styles.module.css b/src/v4/chat/components/HomeIndicator/styles.module.css index c8fc4e832..7cc527f79 100644 --- a/src/v4/chat/components/HomeIndicator/styles.module.css +++ b/src/v4/chat/components/HomeIndicator/styles.module.css @@ -8,5 +8,5 @@ width: 8.375rem; height: 0.3125rem; border-radius: var(--asc-border-radius-full); - background-color: var(--asc-color-base-shade1); + background-color: var(--live-chat-asc-color-base-shade1, var(--asc-color-base-shade1)); } diff --git a/src/v4/chat/components/LiveChatMessageContent/MessageAction/index.tsx b/src/v4/chat/components/LiveChatMessageContent/MessageAction/index.tsx index b51e184c1..5f96e94b0 100644 --- a/src/v4/chat/components/LiveChatMessageContent/MessageAction/index.tsx +++ b/src/v4/chat/components/LiveChatMessageContent/MessageAction/index.tsx @@ -137,13 +137,13 @@ const MessageAction = ({ } >
{ setIsPopoverOpen(!isPopoverOpen); }} > - +
{/* */} diff --git a/src/v4/chat/components/LiveChatMessageContent/MessageAction/styles.module.css b/src/v4/chat/components/LiveChatMessageContent/MessageAction/styles.module.css index 544605d7e..2a2559ccf 100644 --- a/src/v4/chat/components/LiveChatMessageContent/MessageAction/styles.module.css +++ b/src/v4/chat/components/LiveChatMessageContent/MessageAction/styles.module.css @@ -1,14 +1,24 @@ -.optionIcon { - width: auto; - height: 1.25rem; - +.optionButton { + display: flex; :hover { cursor: pointer; - background-color: var(--asc-color-secondary-shade4); + background-color: var( + --live-chat-message-list-asc-color-secondary-shade4, + var(--live-chat-asc-color-secondary-shade4, var(--asc-color-secondary-shade4)) + ); border-radius: var(--asc-border-radius-sm); } } +.optionIcon { + fill: var( + --live-chat-message-list-asc-color-secondary-shade2, + var(--live-chat-asc-color-secondary-shade2, var(--asc-color-secondary-shade2)) + ); + width: auto; + height: 1.25rem; +} + .reactionIcon { width: 1.25rem; height: 1.25rem; @@ -16,7 +26,10 @@ .timestamp { font-family: var(--asc-text-global-font-family); - color: var(--asc-color-base-shade2); + color: var( + --live-chat-message-list-asc-color-base-shade2, + var(--live-chat-asc-color-base-shade2, var(--asc-color-base-shade2)) + ); font-size: 0.5rem; line-height: 0.75rem; margin-bottom: var(--asc-spacing-s1); @@ -30,25 +43,40 @@ } .messageActionButtonText { - color: var(--asc-color-base-inverse); + color: var( + --live-chat-message-list-asc-color-base-inverse, + var(--live-chat-asc-color-base-inverse, var(--asc-color-base-inverse)) + ); } .messageDangerActionButtonText { - color: var(--asc-color-alert); + color: var( + --live-chat-message-list-asc-color-alert, + var(--live-chat-asc-color-alert, var(--asc-color-alert)) + ); } .copyIcon { - fill: var(--asc-color-base-inverse); + fill: var( + --live-chat-message-list-asc-color-base-inverse, + var(--live-chat-asc-color-base-inverse, var(--asc-color-base-inverse)) + ); } .replyIcon { - fill: var(--asc-color-base-inverse); + fill: var( + --live-chat-message-list-asc-color-base-inverse, + var(--live-chat-asc-color-base-inverse, var(--asc-color-base-inverse)) + ); } .binIcon { width: 1.25rem; height: 1.25rem; - fill: var(--asc-color-alert); + fill: var( + --live-chat-message-list-asc-color-alert, + var(--live-chat-asc-color-alert, var(--asc-color-alert)) + ); } .mentionIcon { diff --git a/src/v4/chat/components/LiveChatMessageContent/MessageBubble/styles.module.css b/src/v4/chat/components/LiveChatMessageContent/MessageBubble/styles.module.css index a90cce7ad..99e0ceae5 100644 --- a/src/v4/chat/components/LiveChatMessageContent/MessageBubble/styles.module.css +++ b/src/v4/chat/components/LiveChatMessageContent/MessageBubble/styles.module.css @@ -2,8 +2,14 @@ padding: var(--asc-spacing-s1) 0.625rem; border-radius: var(--asc-border-radius-lg); border-top-left-radius: var(--asc-border-radius-none); - color: var(--asc-color-base-inverse); - background-color: var(--asc-color-base-shade4); + color: var( + --live-chat-message-list-asc-color-base-inverse, + var(--live-chat-asc-color-base-inverse, var(--asc-color-base-inverse)) + ); + background-color: var( + --live-chat-message-list-asc-color-base-shade4, + var(--live-chat-asc-color-base-shade4, var(--asc-color-base-shade4)) + ); max-width: 7.5rem; word-break: break-word; overflow-wrap: break-word; @@ -12,7 +18,11 @@ } .messageBubble[data-mentioned='true'] { - border: 1px solid var(--asc-color-primary-dark, var(--asc-color-primary)); + border: 1px solid + var( + --live-chat-message-list-asc-color-primary-default, + var(--live-chat-asc-color-primary-default, var(--asc-color-primary-default)) + ); } .messageRepliedBubble { @@ -22,15 +32,24 @@ .messageParentContainer { padding: var(--asc-spacing-s2); border-radius: var(--asc-border-radius-lg) var(--asc-border-radius-lg) 0 0; - background-color: var(--asc-color-base-shade3); + background-color: var( + --live-chat-message-list-asc-color-base-shade3, + var(--live-chat-asc-color-base-shade3, var(--asc-color-base-shade3)) + ); } .messageParentDisplayName { - color: var(--asc-color-base-inverse); + color: var( + --live-chat-message-list-asc-color-base-inverse, + var(--live-chat-asc-color-base-inverse, var(--asc-color-base-inverse)) + ); } .messageParentText { - color: var(--asc-color-base-default); + color: var( + --live-chat-message-list-asc-color-base-default, + var(--live-chat-asc-color-base-default, var(--asc-color-base-default)) + ); text-overflow: ellipsis; overflow: hidden; @@ -39,8 +58,14 @@ .messageChildContainer { border-radius: 0 0 var(--asc-border-radius-lg) var(--asc-border-radius-lg); - background-color: var(--asc-color-base-shade4); - color: var(--asc-color-base-inverse); + background-color: var( + --live-chat-message-list-asc-color-base-shade4, + var(--live-chat-asc-color-base-shade4, var(--asc-color-base-shade4)) + ); + color: var( + --live-chat-message-list-asc-color-base-inverse, + var(--live-chat-asc-color-base-inverse, var(--asc-color-base-inverse)) + ); padding: var(--asc-spacing-s1) var(--asc-spacing-s2); } diff --git a/src/v4/chat/components/LiveChatMessageContent/MessageBubbleContainer/styles.module.css b/src/v4/chat/components/LiveChatMessageContent/MessageBubbleContainer/styles.module.css index 1300170cc..240434c91 100644 --- a/src/v4/chat/components/LiveChatMessageContent/MessageBubbleContainer/styles.module.css +++ b/src/v4/chat/components/LiveChatMessageContent/MessageBubbleContainer/styles.module.css @@ -8,7 +8,10 @@ display: flex; gap: var(--asc-spacing-xxs2); align-items: center; - color: var(--asc-color-secondary-shade1); + color: var( + --live-chat-message-list-asc-color-secondary-shade1, + var(--live-chat-asc-color-secondary-shade1, var(--asc-color-secondary-shade1)) + ); margin-bottom: var(--asc-spacing-xxs2); } diff --git a/src/v4/chat/components/LiveChatMessageContent/MessageTextWithMention/index.tsx b/src/v4/chat/components/LiveChatMessageContent/MessageTextWithMention/index.tsx index 1a4851463..4cd103569 100644 --- a/src/v4/chat/components/LiveChatMessageContent/MessageTextWithMention/index.tsx +++ b/src/v4/chat/components/LiveChatMessageContent/MessageTextWithMention/index.tsx @@ -1,7 +1,6 @@ import React from 'react'; import styles from './styles.module.css'; import { Typography } from '~/v4/core/components'; -import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; import HyperLinkText from '~/v4/core/components/HyperlinkText/index'; interface MessageTextWithMentionProps { @@ -10,12 +9,6 @@ interface MessageTextWithMentionProps { } const MessageTextWithMention = ({ message, className }: MessageTextWithMentionProps) => { - const { getConfig } = useCustomization(); - - // TODO: uncomment this when full configuration on the component is supported - // const pageConfig = getConfig('live_chat/*/*'); - // const componentConfig = getConfig('live_chat/message_list/*'); - const mentionList = message.metadata?.mentioned as { index: number; userId: string; @@ -53,21 +46,12 @@ const MessageTextWithMention = ({ message, className }: MessageTextWithMentionPr {segments.map((segment, index) => segment.isMention ? ( - + {segment.text} ) : ( - {segment.text} + {segment.text} ), )} @@ -77,7 +61,7 @@ const MessageTextWithMention = ({ message, className }: MessageTextWithMentionPr return ( - {message.data?.text} + {message.data?.text} ); }; diff --git a/src/v4/chat/components/LiveChatMessageContent/MessageTextWithMention/styles.module.css b/src/v4/chat/components/LiveChatMessageContent/MessageTextWithMention/styles.module.css index cb4c68018..b49054e75 100644 --- a/src/v4/chat/components/LiveChatMessageContent/MessageTextWithMention/styles.module.css +++ b/src/v4/chat/components/LiveChatMessageContent/MessageTextWithMention/styles.module.css @@ -1,3 +1,13 @@ .mentionText { - color: var(--asc-color-primary-dark, var(--asc-color-primary)); + color: var( + --live-chat-message-list-asc-color-primary-default, + var(--live-chat-asc-color-primary-default, var(--asc-color-primary-default)) + ); +} + +.hyperlink { + color: var( + --live-chat-message-list-asc-color-primary-default, + var(--live-chat-asc-color-primary-default, var(--asc-color-primary-default)) + ); } diff --git a/src/v4/chat/components/LiveChatMessageContent/styles.module.css b/src/v4/chat/components/LiveChatMessageContent/styles.module.css index bf4fe0237..26529261c 100644 --- a/src/v4/chat/components/LiveChatMessageContent/styles.module.css +++ b/src/v4/chat/components/LiveChatMessageContent/styles.module.css @@ -18,7 +18,10 @@ background-color: var(--asc-color-base-shade4); gap: var(--asc-spacing-xxs2); border-radius: var(--asc-border-radius-lg); - color: var(--asc-color-base-inverse); + color: var( + --live-chat-message-list-asc-color-base-inverse, + var(--live-chat-asc-color-base-inverse, var(--asc-color-base-inverse)) + ); } .messageOptionsWrap { @@ -30,7 +33,10 @@ .binIcon { height: 1rem; width: 1rem; - fill: var(--asc-color-base-inverse); + fill: var( + --live-chat-message-list-asc-color-base-inverse, + var(--live-chat-asc-color-base-inverse, var(--asc-color-base-inverse)) + ); } .optionIcon { @@ -49,7 +55,10 @@ .timestamp { font-family: var(--asc-text-global-font-family); - color: var(--asc-color-base-shade2); + color: var( + --live-chat-message-list-asc-color-base-shade2, + var(--live-chat-asc-color-base-shade2, var(--asc-color-base-shade2)) + ); margin-bottom: var(--asc-spacing-s1); /* This value is not available in global css */ diff --git a/src/v4/chat/components/LiveChatNotification/index.tsx b/src/v4/chat/components/LiveChatNotification/index.tsx index 7fdb99d57..5b33fde89 100644 --- a/src/v4/chat/components/LiveChatNotification/index.tsx +++ b/src/v4/chat/components/LiveChatNotification/index.tsx @@ -11,7 +11,7 @@ interface NotificationProps { } const LiveChatNotification = ({ className, content, icon }: NotificationProps) => ( -
+
{icon} {content}
diff --git a/src/v4/chat/components/LiveChatNotification/styles.module.css b/src/v4/chat/components/LiveChatNotification/styles.module.css index beea9f782..f0e3857b5 100644 --- a/src/v4/chat/components/LiveChatNotification/styles.module.css +++ b/src/v4/chat/components/LiveChatNotification/styles.module.css @@ -1,10 +1,4 @@ /* styles.module.css */ -.icon { - width: 1.5rem; - height: 1.5rem; - fill: var(--asc-color-base-inverse); - margin-right: var(--asc-spacing-s2); -} .notifications { position: relative; @@ -21,13 +15,13 @@ padding: 1.125rem var(--asc-spacing-m1); display: flex; align-items: center; - color: var(--asc-color-base-inverse); + color: var(--live-chat-asc-color-base-inverse, var(--asc-color-base-inverse)); border-radius: var(--asc-border-radius-md); animation-duration: 0.3s; animation-name: appear; animation-fill-mode: forwards; pointer-events: auto; - background-color: var(--asc-color-base-shade4); + background-color: var(--live-chat-asc-color-base-shade4, var(--asc-color-base-shade4)); } @keyframes appear { diff --git a/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/styles.module.css b/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/styles.module.css index 44b37ffe8..7644a5b91 100644 --- a/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/styles.module.css +++ b/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/styles.module.css @@ -10,7 +10,7 @@ gap: var(--asc-spacing-s2); padding: var(--asc-spacing-s1) var(--asc-spacing-s2); background-color: inherit; - border-top: 1px solid var(--asc-color-base-shade2); + border-top: 1px solid var(--live-chat-asc-color-base-shade2, var(--asc-color-base-shade2)); } .textInputContainer { @@ -20,8 +20,8 @@ & > div { width: 100%; border-radius: var(--asc-border-radius-xxl); - border: 1px solid var(--asc-color-base-background); - background: var(--asc-color-secondary-shade4); + border: 1px solid var(--live-chat-asc-color-base-background, var(--asc-color-base-background)); + background: var(--live-chat-asc-color-secondary-shade4, var(--asc-color-secondary-shade4)); textarea { padding: 0.563rem 1rem; @@ -36,8 +36,11 @@ padding: var(--asc-spacing-s1) var(--asc-spacing-s2); border-radius: var(--asc-border-radius-sm); border: var(--asc-border-radius-none); - background-color: var(--asc-color-secondary-default); - color: var(--asc-color-base-inverse); + background-color: var( + --live-chat-asc-color-secondary-default, + var(--asc-color-secondary-default) + ); + color: var(--live-chat-asc-color-base-inverse, var(--asc-color-base-inverse)); display: flex; align-items: center; @@ -50,8 +53,8 @@ .replyPlaceholderContainer { display: flex; font-family: var(--asc-text-global-font-family); - background-color: var(--asc-color-base-shade4); - color: var(--asc-color-white); + background-color: var(--live-chat-asc-color-base-shade4, var(--asc-color-base-shade4)); + color: var(--live-chat-asc-color-base-inverse, var(--asc-color-base-inverse)); max-width: 100%; padding: var(--asc-spacing-s2); padding-left: var(--asc-spacing-m1); @@ -66,7 +69,7 @@ .replyProfile { display: flex; flex-direction: column; - color: var(--asc-color-white); + color: var(--live-chat-asc-color-base-inverse, var(--asc-color-base-inverse)); font-size: var(--asc-text-font-size-sm); line-height: var(--asc-line-height-sm); flex: 1; @@ -103,8 +106,8 @@ .mutedChannelContainer { display: flex; font-family: var(--asc-text-global-font-family); - background-color: var(--asc-color-base-shade4); - color: var(--asc-color-base-shade1); + background-color: var(--live-chat-asc-color-base-shade4, var(--asc-color-base-shade4)); + color: var(--live-chat-asc-color-base-shade1, var(--asc-color-base-shade1)); max-width: 100%; padding: var(--asc-spacing-s2); padding-left: var(--asc-spacing-m1); @@ -112,12 +115,12 @@ align-items: center; } -.mutedIcon { - color: var(--asc-color-base-shade1); +.mutedChannelIcon { + color: var(--live-chat-asc-color-base-shade3, var(--asc-color-base-shade3)); } .commentAltIcon { - color: var(--asc-color-base-shade2); + color: var(--live-chat-asc-color-base-shade2, var(--asc-color-base-shade2)); } .banStatePanel { @@ -129,5 +132,5 @@ text-align: center; gap: var(--asc-spacing-s2); padding: var(--asc-spacing-m1); - color: var(--asc-color-base-shade2); + color: var(--live-chat-asc-color-base-shade2, var(--asc-color-base-shade2)); } diff --git a/src/v4/chat/pages/AmityLiveChatPage/livechatPage.stories.tsx b/src/v4/chat/pages/AmityLiveChatPage/livechatPage.stories.tsx index bf2e2ca43..7c63683bf 100644 --- a/src/v4/chat/pages/AmityLiveChatPage/livechatPage.stories.tsx +++ b/src/v4/chat/pages/AmityLiveChatPage/livechatPage.stories.tsx @@ -63,12 +63,7 @@ const LiveChatList = () => { ) : null} - setOpen(false)} - className={styles.messageListSheet} - data-theme="dark" - > + setOpen(false)} className={styles.messageListSheet}> diff --git a/src/v4/chat/pages/AmityLiveChatPage/styles.module.css b/src/v4/chat/pages/AmityLiveChatPage/styles.module.css index 4679b53b0..451ec7536 100644 --- a/src/v4/chat/pages/AmityLiveChatPage/styles.module.css +++ b/src/v4/chat/pages/AmityLiveChatPage/styles.module.css @@ -15,16 +15,10 @@ box-shadow: var(--asc-box-shadow-01); } -.chatIcon { - width: 2rem; - height: 2rem; - fill: var(--asc-color-white); -} - .amtiyLivechatPage { display: flex; flex-direction: column; - background-color: var(--asc-color-base-background); + background-color: var(--live-chat-asc-color-base-background, var(--asc-color-base-background)); height: 100%; overflow-y: hidden; } diff --git a/src/v4/chat/providers/LiveChatNotificationProvider.module.css b/src/v4/chat/providers/LiveChatNotificationProvider.module.css index f46b11921..4eea2808d 100644 --- a/src/v4/chat/providers/LiveChatNotificationProvider.module.css +++ b/src/v4/chat/providers/LiveChatNotificationProvider.module.css @@ -1,6 +1,6 @@ .icon { width: 1.5rem; height: 1.5rem; - fill: var(--asc-color-base-inverse); + fill: var(--live-chat-asc-color-base-inverse, var(--asc-color-base-inverse)); margin-right: var(--asc-spacing-s2); } diff --git a/src/v4/core/components/Button/Button.module.css b/src/v4/core/components/Button/Button.module.css index bb32938c6..adb2c9e8b 100644 --- a/src/v4/core/components/Button/Button.module.css +++ b/src/v4/core/components/Button/Button.module.css @@ -1,55 +1,55 @@ .button { - display: inline-flex; - align-items: center; - justify-content: center; - padding: var(--asc-spacing-s1) var(--asc-spacing-m1); - font-size: var(--asc-text-font-size-md); - font-weight: var(--asc-text-font-weight-bold); - border-radius: var(--asc-border-radius-sm); - cursor: pointer; - transition: background-color 0.3s ease; - } - - .disabled { - opacity: 0.6; - cursor: not-allowed; - } - - .primary { - background-color: var(--asc-color-primary); - color: var(--asc-color-white); - border: none; - } - - .primary:hover:not(.disabled) { - background-color: var(--asc-color-primary-50); - } - - .secondary { - background-color: var(--asc-color-white); - color: var(--asc-color-primary); - border: 1px solid var(--asc-color-primary); - } - - .secondary:hover:not(.disabled) { - background-color: var(--asc-color-base-shade4); - } - - .small { - font-size: var(--asc-text-font-size-sm); - padding: var(--asc-spacing-xxs2) var(--asc-spacing-s1); - } - - .medium { - font-size: var(--asc-text-font-size-md); - padding: var(--asc-spacing-s1) var(--asc-spacing-m1); - } - - .large { - font-size: var(--asc-text-font-size-xl); - padding: var(--asc-spacing-s2) var(--asc-spacing-m2); - } - - .icon { - margin-right: var(--asc-spacing-s1); - } \ No newline at end of file + display: inline-flex; + align-items: center; + justify-content: center; + padding: var(--asc-spacing-s1) var(--asc-spacing-m1); + font-size: var(--asc-text-font-size-md); + font-weight: var(--asc-text-font-weight-bold); + border-radius: var(--asc-border-radius-sm); + cursor: pointer; + transition: background-color 0.3s ease; +} + +.disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.primary { + background-color: var(--asc-color-primary-default); + color: var(--asc-color-white); + border: none; +} + +.primary:hover:not(.disabled) { + background-color: var(--asc-color-primary-50); +} + +.secondary { + background-color: var(--asc-color-white); + color: var(--asc-color-primary-default); + border: 1px solid var(--asc-color-primary-default); +} + +.secondary:hover:not(.disabled) { + background-color: var(--asc-color-base-shade4); +} + +.small { + font-size: var(--asc-text-font-size-sm); + padding: var(--asc-spacing-xxs2) var(--asc-spacing-s1); +} + +.medium { + font-size: var(--asc-text-font-size-md); + padding: var(--asc-spacing-s1) var(--asc-spacing-m1); +} + +.large { + font-size: var(--asc-text-font-size-xl); + padding: var(--asc-spacing-s2) var(--asc-spacing-m2); +} + +.icon { + margin-right: var(--asc-spacing-s1); +} diff --git a/src/v4/core/components/ConfirmModal/index.tsx b/src/v4/core/components/ConfirmModal/index.tsx index 7bb1f5804..077f257af 100644 --- a/src/v4/core/components/ConfirmModal/index.tsx +++ b/src/v4/core/components/ConfirmModal/index.tsx @@ -42,7 +42,7 @@ const Confirm = ({ } onCancel={onCancel} > -
{content}
+ {content} ); diff --git a/src/v4/core/components/ConfirmModal/styles.module.css b/src/v4/core/components/ConfirmModal/styles.module.css index 901d3eeef..75d341c64 100644 --- a/src/v4/core/components/ConfirmModal/styles.module.css +++ b/src/v4/core/components/ConfirmModal/styles.module.css @@ -2,24 +2,19 @@ max-width: 22.5rem !important; } -.confirmModalContent { - padding: var(--asc-spacing-m1) var(--asc-spacing-m1) var(--asc-spacing-s2) var(--asc-spacing-m1); -} - .footer { display: flex; justify-content: flex-end; } .okButton { - color: var(--asc-color-secondary-default); background: var(--asc-color-alert) !important; border: none; } .cancelButton { margin-right: var(--asc-spacing-s1); - background-color: var(--asc-color-base-background) !important; + background: transparent !important; color: var(--asc-color-secondary-default); border: 1px solid var(--asc-color-secondary-default) !important; } diff --git a/src/v4/core/components/HyperlinkText/index.tsx b/src/v4/core/components/HyperlinkText/index.tsx index 20400c55e..aacf9d147 100644 --- a/src/v4/core/components/HyperlinkText/index.tsx +++ b/src/v4/core/components/HyperlinkText/index.tsx @@ -3,14 +3,20 @@ import styles from './styles.module.css'; import Linkify from 'linkify-react'; -const HyperLinkText = ({ children }: { children: ReactNode }) => { +const HyperLinkText = ({ + linkClassName, + children, +}: { + linkClassName?: string; + children: ReactNode; +}) => { return ( { return ( - + {content} ); diff --git a/src/v4/core/components/HyperlinkText/styles.module.css b/src/v4/core/components/HyperlinkText/styles.module.css index bc591e562..899aac484 100644 --- a/src/v4/core/components/HyperlinkText/styles.module.css +++ b/src/v4/core/components/HyperlinkText/styles.module.css @@ -1,3 +1,3 @@ .hyperlink { - color: var(--asc-color-primary-dark, var(--asc-color-primary)); + color: var(--asc-color-primary-dark, var(--asc-color-primary-default)); } diff --git a/src/v4/core/components/InputText/InsideInputText.tsx b/src/v4/core/components/InputText/InsideInputText.tsx index d0fdddbe9..02a42574b 100644 --- a/src/v4/core/components/InputText/InsideInputText.tsx +++ b/src/v4/core/components/InputText/InsideInputText.tsx @@ -49,6 +49,7 @@ interface InsideInputTextProps { onClear?: () => void; onClick?: () => void; suggestionRef?: RefObject; + mentionColor?: string; } const InsideInputText = forwardRef( @@ -76,6 +77,7 @@ const InsideInputText = forwardRef { @@ -147,7 +149,7 @@ const InsideInputText = forwardRef { if (!queryMentionees) return callback([]); queryMentionees(queryValue).then((result) => { diff --git a/src/v4/core/components/InputText/index.tsx b/src/v4/core/components/InputText/index.tsx index 48058b622..b1786c1d5 100644 --- a/src/v4/core/components/InputText/index.tsx +++ b/src/v4/core/components/InputText/index.tsx @@ -36,6 +36,7 @@ export interface InputTextProps { onClear?: () => void; onClick?: () => void; suggestionRef?: React.RefObject; + mentionColor?: string; } const InputText = forwardRef( diff --git a/src/v4/core/components/InputText/styles.module.css b/src/v4/core/components/InputText/styles.module.css index e9e7109d2..5814ee3f7 100644 --- a/src/v4/core/components/InputText/styles.module.css +++ b/src/v4/core/components/InputText/styles.module.css @@ -82,7 +82,7 @@ outline: none; font: inherit; resize: vertical; - color: var(--asc-color-white); + color: var(--asc-color-base-inverse); &::placeholder { font-weight: 400; @@ -113,7 +113,7 @@ .mentions_mention { position: relative; z-index: 1; - color: var(--asc-color-primary-dark, var(--asc-color-primary)); + color: var(--asc-color-primary-dark, var(--asc-color-primary-default)); pointer-events: none; background-color: var(--asc-color-base-shade4); } diff --git a/src/v4/core/components/Modal/index.tsx b/src/v4/core/components/Modal/index.tsx index 5b9db4beb..4fc246cbf 100644 --- a/src/v4/core/components/Modal/index.tsx +++ b/src/v4/core/components/Modal/index.tsx @@ -31,7 +31,7 @@ const Modal = ({ }, [modalRef?.current]); return ( -
+
( -
+
{icon} {content}
); diff --git a/src/v4/core/components/SocialMentionItem/styles.module.css b/src/v4/core/components/SocialMentionItem/styles.module.css index 1c78bda54..2bd4cfc65 100644 --- a/src/v4/core/components/SocialMentionItem/styles.module.css +++ b/src/v4/core/components/SocialMentionItem/styles.module.css @@ -20,7 +20,7 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - color: var(--asc-color-white); + color: var(--asc-color-base-inverse); } .mentionAllDescription { @@ -35,4 +35,4 @@ display: flex; align-items: center; } -} \ No newline at end of file +} diff --git a/src/v4/core/hooks.ts b/src/v4/core/hooks.ts index 7f62da8d1..6bbe3eca2 100644 --- a/src/v4/core/hooks.ts +++ b/src/v4/core/hooks.ts @@ -1,13 +1,13 @@ import { useIntl } from 'react-intl'; -import { useNotifications } from '~/v4/core/providers/NotificationProvider'; +import { useLiveChatNotifications } from '~/v4/chat/providers/LiveChatNotificationProvider'; export const useCopyMessage = () => { - const notification = useNotifications(); + const notification = useLiveChatNotifications(); const { formatMessage } = useIntl(); const copyMessage = async (message: string) => { await navigator.clipboard.writeText(message); - notification.show({ + notification.success({ content: formatMessage({ id: 'livechat.notification.copy.message' }), }); }; diff --git a/src/v4/core/providers/CustomizationProvider.tsx b/src/v4/core/providers/CustomizationProvider.tsx index c356aaa1c..52a385c4e 100644 --- a/src/v4/core/providers/CustomizationProvider.tsx +++ b/src/v4/core/providers/CustomizationProvider.tsx @@ -7,7 +7,7 @@ interface CustomizationContextValue { getConfig: (path: string) => Record; } -type Theme = { +export type Theme = { light: { primary_color: string; secondary_color: string; @@ -18,6 +18,7 @@ type Theme = { base_shade4_color: string; alert_color: string; background_color: string; + base_inverse_color: string; }; dark: { primary_color: string; @@ -29,6 +30,7 @@ type Theme = { base_shade4_color: string; alert_color: string; background_color: string; + base_inverse_color: string; }; }; @@ -269,6 +271,7 @@ export const defaultConfig: Config = { base_shade4_color: '#ebecef', alert_color: '#FA4D30', background_color: '#FFFFFF', + base_inverse_color: '#000000', }, dark: { primary_color: '#1054DE', @@ -280,6 +283,7 @@ export const defaultConfig: Config = { base_shade4_color: '#292b32', alert_color: '#FA4D30', background_color: '#191919', + base_inverse_color: '#FFFFFF', }, }, excludes: [], diff --git a/src/v4/core/providers/ThemeProvider.tsx b/src/v4/core/providers/ThemeProvider.tsx index ccf95d2f9..6e6a7489d 100644 --- a/src/v4/core/providers/ThemeProvider.tsx +++ b/src/v4/core/providers/ThemeProvider.tsx @@ -1,6 +1,6 @@ import React, { createContext, useEffect, useState } from 'react'; import { lighten, parseToHsl, darken, hslToColorString } from 'polished'; -import { defaultConfig, useCustomization } from './CustomizationProvider'; +import { useCustomization, Config, Theme } from './CustomizationProvider'; const SHADE_PERCENTAGES = [0.25, 0.4, 0.5, 0.75]; @@ -23,6 +23,94 @@ const generateShades = (hexColor: string, isDarkMode = false): string[] => { return shades; }; +const generatePalleteByConfig = ({ + themeConfig, + configId, + isDarkMode, +}: { + themeConfig: Theme['light'] | Theme['dark']; + configId?: string; + isDarkMode?: boolean; +}) => { + const primaryColorShades = generateShades(themeConfig.primary_color, isDarkMode); + const secondaryColorShades = generateShades(themeConfig.secondary_color, isDarkMode); + + const prefix = configId ? configId + '-' : ''; + + setCSSVariable(`--${prefix}asc-color-primary-default`, themeConfig.primary_color); + setCSSVariable(`--${prefix}asc-color-primary-shade1`, primaryColorShades[0]); + setCSSVariable(`--${prefix}asc-color-primary-shade2`, primaryColorShades[1]); + setCSSVariable(`--${prefix}asc-color-primary-shade3`, primaryColorShades[2]); + setCSSVariable(`--${prefix}asc-color-primary-shade4`, primaryColorShades[3]); + + setCSSVariable(`--${prefix}asc-color-secondary-default`, themeConfig.secondary_color); + setCSSVariable(`--${prefix}asc-color-secondary-shade1`, secondaryColorShades[0]); + setCSSVariable(`--${prefix}asc-color-secondary-shade2`, secondaryColorShades[1]); + setCSSVariable(`--${prefix}asc-color-secondary-shade3`, secondaryColorShades[2]); + setCSSVariable(`--${prefix}asc-color-secondary-shade4`, secondaryColorShades[3]); + + setCSSVariable(`--${prefix}asc-color-base-default`, themeConfig.base_color); + setCSSVariable(`--${prefix}asc-color-base-shade1`, themeConfig.base_shade1_color); + setCSSVariable(`--${prefix}asc-color-base-shade2`, themeConfig.base_shade2_color); + setCSSVariable(`--${prefix}asc-color-base-shade3`, themeConfig.base_shade3_color); + setCSSVariable(`--${prefix}asc-color-base-shade4`, themeConfig.base_shade4_color); + + setCSSVariable(`--${prefix}asc-color-alert`, themeConfig.alert_color); + setCSSVariable(`--${prefix}asc-color-base-background`, themeConfig.background_color); + setCSSVariable(`--${prefix}asc-color-base-inverse`, themeConfig.base_inverse_color); +}; + +const generateComponentPalette = (config: Config, currentTheme: 'light' | 'dark') => { + const configurables = [ + { + pageId: 'live_chat', + componentIds: ['chat_header', 'message_list', 'message_composer'], + }, + ]; + + configurables.forEach((configurable) => { + const pageConfig = (config.customizations as { [key: string]: { theme: Theme } })?.[ + `${configurable.pageId}/*/*` + ]?.theme; + + if (pageConfig) { + const themeToGenerate = currentTheme === 'light' ? pageConfig.light : pageConfig.dark; + const configId = configurable.pageId.replace('_', '-'); + + if (themeToGenerate) { + generatePalleteByConfig({ + themeConfig: themeToGenerate, + configId, + isDarkMode: currentTheme === 'dark', + }); + } + } + + if (configurable.componentIds.length === 0) return; + + configurable.componentIds.forEach((componentId) => { + const componentConfig = (config.customizations as { [key: string]: { theme: Theme } })?.[ + `${configurable.pageId}/${componentId}/*` + ].theme; + if (componentConfig) { + const themeToGenerate = + currentTheme === 'light' ? componentConfig.light : componentConfig.dark; + + const configId = + configurable.pageId.replace('_', '-') + '-' + componentId.replace('_', '-'); + + if (themeToGenerate) { + generatePalleteByConfig({ + themeConfig: themeToGenerate, + configId, + isDarkMode: currentTheme === 'dark', + }); + } + } + }); + }); +}; + export const ThemeContext = createContext<{ currentTheme: 'light' | 'dark'; toggleTheme: () => void; @@ -32,52 +120,43 @@ export const ThemeContext = createContext<{ }); export const ThemeProvider: React.FC = ({ children }) => { - const config = useCustomization().config || defaultConfig; - - const [currentTheme, setCurrentTheme] = useState<'light' | 'dark'>('light'); + const { config } = useCustomization(); + const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); + + const [currentTheme, setCurrentTheme] = useState<'light' | 'dark'>( + config?.preferred_theme && config?.preferred_theme !== 'default' + ? config.preferred_theme + : mediaQuery.matches + ? 'dark' + : 'light', + ); useEffect(() => { - if (config.theme?.light) { - const primaryColorShades = generateShades(config.theme.light.primary_color); - const secondaryColorShades = generateShades(config.theme.light.secondary_color); - - setCSSVariable('--asc-color-primary-default', config.theme.light.primary_color); - setCSSVariable('--asc-color-primary-shade1', primaryColorShades[0]); - setCSSVariable('--asc-color-primary-shade2', primaryColorShades[1]); - setCSSVariable('--asc-color-primary-shade3', primaryColorShades[2]); - setCSSVariable('--asc-color-primary-shade4', primaryColorShades[3]); - - setCSSVariable('--asc-color-secondary-default', config.theme.light.secondary_color); - setCSSVariable('--asc-color-secondary-shade1', secondaryColorShades[0]); - setCSSVariable('--asc-color-secondary-shade2', secondaryColorShades[1]); - setCSSVariable('--asc-color-secondary-shade3', secondaryColorShades[2]); - setCSSVariable('--asc-color-secondary-shade4', secondaryColorShades[3]); - - setCSSVariable('--asc-color-base-default', config.theme.light.base_color); - setCSSVariable('--asc-color-base-shade1', config.theme.light.base_shade1_color); - setCSSVariable('--asc-color-base-shade2', config.theme.light.base_shade2_color); - setCSSVariable('--asc-color-base-shade3', config.theme.light.base_shade3_color); - setCSSVariable('--asc-color-base-shade4', config.theme.light.base_shade4_color); - - setCSSVariable('--asc-color-alert', config.theme.light.alert_color); - setCSSVariable('--asc-color-background', config.theme.light.background_color); + if (!config) return; + const themeToGenerate = currentTheme === 'light' ? config.theme?.light : config.theme?.dark; + + if (themeToGenerate) { + generatePalleteByConfig({ + themeConfig: themeToGenerate, + isDarkMode: currentTheme === 'dark', + }); } - if (config.theme?.dark) { - setCSSVariable('--asc-color-primary-dark', config.theme.dark.primary_color); - } + generateComponentPalette(config, currentTheme); }, [currentTheme, config]); useEffect(() => { + if (!config) return; + if (config.preferred_theme === 'default') { - const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); const handleChange = (e: MediaQueryListEvent) => { setCurrentTheme(e.matches ? 'dark' : 'light'); }; + mediaQuery.addEventListener('change', handleChange); return () => mediaQuery.removeEventListener('change', handleChange); } - }, [config.preferred_theme]); + }, [config?.preferred_theme]); const toggleTheme = () => { setCurrentTheme((prevTheme) => (prevTheme === 'light' ? 'dark' : 'light')); diff --git a/src/v4/icons/CheckCircle.tsx b/src/v4/icons/CheckCircle.tsx index 4baef5430..c9843dde7 100644 --- a/src/v4/icons/CheckCircle.tsx +++ b/src/v4/icons/CheckCircle.tsx @@ -1,18 +1,8 @@ import React from 'react'; -const CheckCircle = ({ fill = '#FFFFFF', ...rest }: React.SVGProps) => ( - - +const CheckCircle = ({ ...props }: React.SVGProps) => ( + + ); diff --git a/src/v4/icons/ExclamationCircle.tsx b/src/v4/icons/ExclamationCircle.tsx index 059892654..4154b25a9 100644 --- a/src/v4/icons/ExclamationCircle.tsx +++ b/src/v4/icons/ExclamationCircle.tsx @@ -1,13 +1,12 @@ import React from 'react'; -const ExclamationCircle = ({ fill = '#FFFFFF', ...rest }: React.SVGProps) => ( +const ExclamationCircle = ({ ...props }: React.SVGProps) => ( ); diff --git a/src/v4/icons/Kebub.tsx b/src/v4/icons/Kebub.tsx index beb0b627c..a470a080a 100644 --- a/src/v4/icons/Kebub.tsx +++ b/src/v4/icons/Kebub.tsx @@ -1,18 +1,8 @@ import React from 'react'; -const Kebub = ({ fill = '#A5A9B5', ...props }: React.SVGProps) => ( - - +const Kebub = ({ ...props }: React.SVGProps) => ( + + ); diff --git a/src/v4/social/components/StoryTab/StoryTabItem.module.css b/src/v4/social/components/StoryTab/StoryTabItem.module.css index 63b3aed9b..1418faff6 100644 --- a/src/v4/social/components/StoryTab/StoryTabItem.module.css +++ b/src/v4/social/components/StoryTab/StoryTabItem.module.css @@ -1,50 +1,50 @@ .container { - display: flex; - flex-direction: column; - align-items: center; - cursor: pointer; - } - - .avatarContainer { - position: relative; - width: 4rem; - height: 4rem; - margin-bottom: 0.5rem; - } - - .verifiedIcon { - position: absolute; - bottom: 0; - right: 0; - z-index: 2; - fill: var(--asc-color-primary); - } - - .avatarBackground { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 3.5rem; - height: 3.5rem; - border-radius: 50%; - overflow: hidden; - background-image: url("data:image/svg+xml,%3Csvg width='100%25' height='100%25' viewBox='0 0 40 40' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='40' height='40' rx='20' fill='%23D9E5FC'/%3E%3Cpath d='M19.8462 12C20.7625 12 21.6413 12.356 22.2893 12.9898C22.9373 13.6235 23.3013 14.4831 23.3013 15.3793C23.3013 16.2756 22.9373 17.1351 22.2893 17.7688C21.6413 18.4026 20.7625 18.7586 19.8462 18.7586C18.9298 18.7586 18.051 18.4026 17.403 17.7688C16.755 17.1351 16.391 16.2756 16.391 15.3793C16.391 14.4831 16.755 13.6235 17.403 12.9898C18.051 12.356 18.9298 12 19.8462 12ZM12.9359 14.4138C13.4887 14.4138 14.0021 14.5586 14.4463 14.8193C14.2982 16.2 14.7128 17.571 15.5618 18.6428C15.0682 19.5697 14.081 20.2069 12.9359 20.2069C12.1504 20.2069 11.3972 19.9017 10.8418 19.3585C10.2864 18.8153 9.97436 18.0786 9.97436 17.3103C9.97436 16.5421 10.2864 15.8054 10.8418 15.2622C11.3972 14.719 12.1504 14.4138 12.9359 14.4138ZM26.7564 14.4138C27.5419 14.4138 28.2951 14.719 28.8505 15.2622C29.4059 15.8054 29.7179 16.5421 29.7179 17.3103C29.7179 18.0786 29.4059 18.8153 28.8505 19.3585C28.2951 19.9017 27.5419 20.2069 26.7564 20.2069C25.6113 20.2069 24.6241 19.5697 24.1305 18.6428C24.9795 17.571 25.3941 16.2 25.246 14.8193C25.6903 14.5586 26.2036 14.4138 26.7564 14.4138ZM13.4295 24.3103C13.4295 22.3117 16.3022 20.6897 19.8462 20.6897C23.3901 20.6897 26.2628 22.3117 26.2628 24.3103V26H13.4295V24.3103ZM8 26V24.5517C8 23.2097 9.86577 22.08 12.3929 21.7517C11.8105 22.4083 11.4551 23.3159 11.4551 24.3103V26H8ZM31.6923 26H28.2372V24.3103C28.2372 23.3159 27.8818 22.4083 27.2994 21.7517C29.8265 22.08 31.6923 23.2097 31.6923 24.5517V26Z' fill='white'/%3E%3C/svg%3E%0A"); - } - - .avatar { - width: 100%; - height: 100%; - object-fit: cover; - border-radius: 50%; - } - - .displayName { - margin: 0; - color: var(--asc-color-base-default); - text-align: center; - max-width: 4.5rem; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } \ No newline at end of file + display: flex; + flex-direction: column; + align-items: center; + cursor: pointer; +} + +.avatarContainer { + position: relative; + width: 4rem; + height: 4rem; + margin-bottom: 0.5rem; +} + +.verifiedIcon { + position: absolute; + bottom: 0; + right: 0; + z-index: 2; + fill: var(--asc-color-primary-default); +} + +.avatarBackground { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 3.5rem; + height: 3.5rem; + border-radius: 50%; + overflow: hidden; + background-image: url("data:image/svg+xml,%3Csvg width='100%25' height='100%25' viewBox='0 0 40 40' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='40' height='40' rx='20' fill='%23D9E5FC'/%3E%3Cpath d='M19.8462 12C20.7625 12 21.6413 12.356 22.2893 12.9898C22.9373 13.6235 23.3013 14.4831 23.3013 15.3793C23.3013 16.2756 22.9373 17.1351 22.2893 17.7688C21.6413 18.4026 20.7625 18.7586 19.8462 18.7586C18.9298 18.7586 18.051 18.4026 17.403 17.7688C16.755 17.1351 16.391 16.2756 16.391 15.3793C16.391 14.4831 16.755 13.6235 17.403 12.9898C18.051 12.356 18.9298 12 19.8462 12ZM12.9359 14.4138C13.4887 14.4138 14.0021 14.5586 14.4463 14.8193C14.2982 16.2 14.7128 17.571 15.5618 18.6428C15.0682 19.5697 14.081 20.2069 12.9359 20.2069C12.1504 20.2069 11.3972 19.9017 10.8418 19.3585C10.2864 18.8153 9.97436 18.0786 9.97436 17.3103C9.97436 16.5421 10.2864 15.8054 10.8418 15.2622C11.3972 14.719 12.1504 14.4138 12.9359 14.4138ZM26.7564 14.4138C27.5419 14.4138 28.2951 14.719 28.8505 15.2622C29.4059 15.8054 29.7179 16.5421 29.7179 17.3103C29.7179 18.0786 29.4059 18.8153 28.8505 19.3585C28.2951 19.9017 27.5419 20.2069 26.7564 20.2069C25.6113 20.2069 24.6241 19.5697 24.1305 18.6428C24.9795 17.571 25.3941 16.2 25.246 14.8193C25.6903 14.5586 26.2036 14.4138 26.7564 14.4138ZM13.4295 24.3103C13.4295 22.3117 16.3022 20.6897 19.8462 20.6897C23.3901 20.6897 26.2628 22.3117 26.2628 24.3103V26H13.4295V24.3103ZM8 26V24.5517C8 23.2097 9.86577 22.08 12.3929 21.7517C11.8105 22.4083 11.4551 23.3159 11.4551 24.3103V26H8ZM31.6923 26H28.2372V24.3103C28.2372 23.3159 27.8818 22.4083 27.2994 21.7517C29.8265 22.08 31.6923 23.2097 31.6923 24.5517V26Z' fill='white'/%3E%3C/svg%3E%0A"); +} + +.avatar { + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 50%; +} + +.displayName { + margin: 0; + color: var(--asc-color-base-default); + text-align: center; + max-width: 4.5rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/src/v4/styles/global.css b/src/v4/styles/global.css index 479bdf456..b83778659 100644 --- a/src/v4/styles/global.css +++ b/src/v4/styles/global.css @@ -29,7 +29,7 @@ --asc-color-black: #000000; --asc-color-white: #ffffff; - --asc-color-primary: #1054de; + --asc-color-primary-default: #1054de; --asc-color-primary-shade1: #4a82f2; --asc-color-primary-shade2: #a0bdf8; --asc-color-primary-shade3: #d9e5fc; From 108a81c5349f9e93b78ba095e4caa70b6f05c987 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Thu, 25 Apr 2024 21:26:41 +0700 Subject: [PATCH 046/300] fix: ASC-21980 - fix remove link button style (#300) * fix: use v4 * fix: hyperlink position and bg color * fix: remove styled component * fix: button --- .../HyperLinkConfig/HyperLinkConfig.module.css | 15 +++++++++------ .../HyperLinkConfig/HyperLinkConfig.tsx | 9 +++++++-- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css index b31fe412c..93810290c 100644 --- a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css +++ b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css @@ -25,8 +25,8 @@ } .input.hasError { - border-bottom-color: var(--asc-color-alert-default); - color: var(--asc-color-alert-default); + border-bottom-color: var(--asc-color-alert); + color: var(--asc-color-alert); } .label { @@ -35,7 +35,7 @@ .label::after { content: none; - color: var(--asc-color-alert-default); + color: var(--asc-color-alert); } .label.required::after { @@ -47,7 +47,7 @@ } .errorText { - color: var(--asc-color-alert-default); + color: var(--asc-color-alert); } .characterCount { @@ -80,7 +80,7 @@ .removeIcon { width: 1.5rem; height: 1.5rem; - fill: var(--asc-color-alert-default); + fill: var(--asc-color-alert); } .removeLinkButton { @@ -88,8 +88,11 @@ justify-content: flex-start; align-items: center; gap: var(--asc-spacing-s1); - color: var(--asc-color-alert-default); + color: var(--asc-color-alert) ; border-radius: 0; + background-color: transparent; + transition: color 0.3s ease; + border: none; } .divider { diff --git a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx index f44cb3af2..1995c17fa 100644 --- a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx +++ b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx @@ -17,6 +17,7 @@ import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; import { Trash2Icon } from '~/icons'; import styles from './HyperLinkConfig.module.css'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; +import Button from '~/v4/core/components/Button/Button'; interface HyperLinkConfigProps { pageId: '*'; @@ -201,10 +202,14 @@ export const HyperLinkConfig = ({
{isHaveHyperLink && (
- +
)} From fc7745eb6ded30577720906a7fb18a6e624bcdb8 Mon Sep 17 00:00:00 2001 From: Pitchaya T <33589608+ptchayap@users.noreply.github.com> Date: Fri, 26 Apr 2024 01:08:47 +0700 Subject: [PATCH 047/300] fix: character limit (#302) --- amity-uikit.config.json | 1 + .../components/AmityLiveChatMessageComposeBar/index.tsx | 8 ++++++-- .../AmityLiveChatPage/ChatContainer/ChatReadyState.tsx | 1 + .../AmityLiveChatPage/ChatContainer/styles.module.css | 2 +- src/v4/core/components/Modal/styles.module.css | 2 +- 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/amity-uikit.config.json b/amity-uikit.config.json index 789219ae1..f25286441 100644 --- a/amity-uikit.config.json +++ b/amity-uikit.config.json @@ -198,6 +198,7 @@ "live_chat/chat_header/*": {}, "live_chat/message_list/*": {}, "live_chat/message_composer/*": { + "message_limit": 200, "placeholder_text": "Write a message" } } diff --git a/src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx b/src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx index 25314d9dc..b5ae3ec51 100644 --- a/src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx +++ b/src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx @@ -25,6 +25,7 @@ interface AmityLiveChatMessageComposeBarProps { composeAction: ComposeActionTypes; suggestionRef?: RefObject; disabled?: boolean; + pageId?: string; } type ComposeBarMention = { @@ -36,6 +37,7 @@ type ComposeBarMention = { }; export const AmityLiveChatMessageComposeBar = ({ + pageId = 'live_chat', channel, suggestionRef, composeAction: { replyMessage, mentionMessage, clearReplyMessage, clearMention }, @@ -44,11 +46,13 @@ export const AmityLiveChatMessageComposeBar = ({ const [mentionList, setMentionList] = useState<{ [key: ComposeBarMention['id']]: ComposeBarMention; }>({}); + const componentId = 'message_composer'; + const { confirm } = useConfirmContext(); const notification = useLiveChatNotifications(); const { getConfig } = useCustomization(); - const componentConfig = getConfig('live_chat/message_composer/*'); + const componentConfig = getConfig(`${pageId}/${componentId}/*`); const commentInputRef = useRef(null); const { queryMentionees, mentionees, onChange, markup, metadata, text } = useMention({ @@ -64,7 +68,7 @@ export const AmityLiveChatMessageComposeBar = ({ if (!channel) return; if (text?.trim().length === 0) return; - if (text.trim().length > COMPOSEBAR_MAX_CHARACTER_LIMIT) { + if (text.trim().length > (componentConfig?.message_limit || COMPOSEBAR_MAX_CHARACTER_LIMIT)) { confirm({ title: formatMessage({ id: 'livechat.error.tooLongMessage.title' }), content: formatMessage({ id: 'livechat.error.tooLongMessage.description' }), diff --git a/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ChatReadyState.tsx b/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ChatReadyState.tsx index 8d414bbe4..012c6b0ba 100644 --- a/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ChatReadyState.tsx +++ b/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ChatReadyState.tsx @@ -98,6 +98,7 @@ const ChatReadyState = ({ channel }: { channel: Amity.Channel }) => {
Date: Fri, 26 Apr 2024 09:51:51 +0700 Subject: [PATCH 048/300] fix: change permission to check if user is a moderator (#304) --- src/v4/chat/components/LiveChatMessageContent/index.tsx | 2 +- src/v4/chat/hooks/useChannelPermission.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/v4/chat/components/LiveChatMessageContent/index.tsx b/src/v4/chat/components/LiveChatMessageContent/index.tsx index 0e10856e3..40aa71f56 100644 --- a/src/v4/chat/components/LiveChatMessageContent/index.tsx +++ b/src/v4/chat/components/LiveChatMessageContent/index.tsx @@ -7,7 +7,7 @@ import { FormattedTime, useIntl } from 'react-intl'; import Bin from '~/v4/icons/Bin'; import useSDK from '~/core/hooks/useSDK'; import MessageBubble from './MessageBubble'; -import useChannelPermission from '../../hooks/useChannelPermission'; +import useChannelPermission from '~/v4/chat/hooks/useChannelPermission'; import Flag from '~/v4/icons/Flag'; interface MessageItemProps { diff --git a/src/v4/chat/hooks/useChannelPermission.ts b/src/v4/chat/hooks/useChannelPermission.ts index fcab98b61..1d42662c1 100644 --- a/src/v4/chat/hooks/useChannelPermission.ts +++ b/src/v4/chat/hooks/useChannelPermission.ts @@ -6,9 +6,9 @@ const useChannelPermission = (subChannelId: Amity.SubChannel['subChannelId']) => const [isModerator, setIsModerator] = useState(false); useEffect(() => { - const currentUser = client?.hasPermission('EDIT_CHANNEL_USER').currentUser() || false; + const currentUser = client?.hasPermission('MUTE_CHANNEL').currentUser() || false; const currentUserInChannel = - client?.hasPermission('EDIT_CHANNEL_USER').channel(subChannelId) || false; + client?.hasPermission('MUTE_CHANNEL').channel(subChannelId) || false; setIsModerator(currentUser || currentUserInChannel); }, [subChannelId]); From d79d39ac6ad1a437a28c9cf18a29575eb8b3d7ae Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Fri, 26 Apr 2024 10:01:08 +0700 Subject: [PATCH 049/300] fix: ASC-22060 - remove onBack() on draft page (#303) * fix: use v4 * fix: hyperlink position and bg color * fix: remove styled component * fix: button * fix: hyperlink style * fix: style * fix: story tab item font color * fix: css var * fix: remove onBack() * fix: css * fix: remove unused * fix: remove unused line --- .../core/components/Modal/styles.module.css | 1 + .../HyperLinkConfig.module.css | 3 +- .../HyperLinkConfig/HyperLinkConfig.tsx | 28 +++++++++++-------- .../StoryTab/StoryTabItem.module.css | 1 - src/v4/social/pages/DraftsPage/DraftsPage.tsx | 3 -- src/v4/styles/global.css | 2 +- 6 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/v4/core/components/Modal/styles.module.css b/src/v4/core/components/Modal/styles.module.css index 36293845c..3d3ea9430 100644 --- a/src/v4/core/components/Modal/styles.module.css +++ b/src/v4/core/components/Modal/styles.module.css @@ -36,6 +36,7 @@ animation-name: appear; margin-top: 0 !important; } + .modalWindow { margin: auto; background-color: var(--asc-color-base-background); diff --git a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css index 93810290c..a4ab26807 100644 --- a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css +++ b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css @@ -22,11 +22,11 @@ border-bottom: 1px solid var(--asc-color-base-shade4); outline: none; color: inherit; + background-color: transparent; } .input.hasError { border-bottom-color: var(--asc-color-alert); - color: var(--asc-color-alert); } .label { @@ -93,6 +93,7 @@ background-color: transparent; transition: color 0.3s ease; border: none; + font-weight: var(--asc-text-font-weight-normal); } .divider { diff --git a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx index 1995c17fa..19df8ccf9 100644 --- a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx +++ b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx @@ -6,7 +6,7 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { SecondaryButton } from '~/core/components/Button'; import useSDK from '~/core/hooks/useSDK'; -import { BottomSheet } from '~/v4/core/components'; +import { BottomSheet, Typography } from '~/v4/core/components'; import { MobileSheet, MobileSheetContainer, @@ -128,9 +128,9 @@ export const HyperLinkConfig = ({ )} -
+ {formatMessage({ id: 'storyCreation.hyperlink.bottomSheet.title' })} -
+
- + + +
- + + +
{watch('customText')?.length} / {MAX_LENGTH}
diff --git a/src/v4/social/components/StoryTab/StoryTabItem.module.css b/src/v4/social/components/StoryTab/StoryTabItem.module.css index 1418faff6..ad1e132a8 100644 --- a/src/v4/social/components/StoryTab/StoryTabItem.module.css +++ b/src/v4/social/components/StoryTab/StoryTabItem.module.css @@ -41,7 +41,6 @@ .displayName { margin: 0; - color: var(--asc-color-base-default); text-align: center; max-width: 4.5rem; white-space: nowrap; diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index 84499e35e..2b509ae4b 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -36,8 +36,6 @@ type HyperLinkFormInputs = { }; const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStoryPageProps) => { - // TODO: Change to usePageBehavior() to align with v4 - const { onBack } = useNavigation(); const { file, setFile } = useStoryContext(); const { navigationBehavior } = usePageBehavior(); const [isHyperLinkBottomSheetOpen, setIsHyperLinkBottomSheetOpen] = useState(false); @@ -78,7 +76,6 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor items?: Amity.StoryItem[], ) => { if (!file) return; - onBack(); const formData = new FormData(); formData.append('files', file); setFile(null); diff --git a/src/v4/styles/global.css b/src/v4/styles/global.css index b83778659..32b8d9b2e 100644 --- a/src/v4/styles/global.css +++ b/src/v4/styles/global.css @@ -166,4 +166,4 @@ --asc-color-secondary-shade3: #40434e; --asc-color-secondary-shade4: #292b32; --asc-color-secondary-shade5: #191919; -} +} \ No newline at end of file From 663520d69b361b059aeeac9c6bc3d12aaf50e514 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Fri, 26 Apr 2024 10:17:55 +0700 Subject: [PATCH 050/300] fix: ASC-21980 - hyperlink style (#301) * fix: use v4 * fix: hyperlink position and bg color * fix: remove styled component * fix: button * fix: hyperlink style * fix: style * fix: story tab item font color * fix: css var * fix: css * fix: remove unused * fix: remove line From 8c0ad0ccd1e644df88fa5d39043d67fe35c1210f Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Fri, 26 Apr 2024 11:02:41 +0700 Subject: [PATCH 051/300] fix: ASC-22063 - fix story global feed pagination (#305) * fix: use v4 * fix: hyperlink position and bg color * fix: remove styled component * fix: button * fix: hyperlink style * fix: style * fix: story tab item font color * fix: css var * fix: remove onBack() * fix: story global feed pagination * fix: add comment * fix: remove new line * fix: remove console.log * fix: logic * fix: story style * chore: Update src/v4/styles/global.css * fix: remove console.log --------- Co-authored-by: Bonn --- .../core/components/Modal/styles.module.css | 2 +- .../HyperLinkConfig.module.css | 2 +- .../StoryTab/StoryTabGlobalFeed.module.css | 1 + .../StoryTab/StoryTabGlobalFeed.tsx | 74 ++++++++++++------- ...yTargets.tsx => useGlobalStoryTargets.tsx} | 2 +- 5 files changed, 50 insertions(+), 31 deletions(-) rename src/v4/social/hooks/collections/{useGetGlobalStoryTargets.tsx => useGlobalStoryTargets.tsx} (92%) diff --git a/src/v4/core/components/Modal/styles.module.css b/src/v4/core/components/Modal/styles.module.css index 3d3ea9430..90ffcdc37 100644 --- a/src/v4/core/components/Modal/styles.module.css +++ b/src/v4/core/components/Modal/styles.module.css @@ -71,4 +71,4 @@ .footer { padding: var(--asc-spacing-m2) var(--asc-spacing-s2); padding-top: var(--asc-spacing-xxs2); -} +} \ No newline at end of file diff --git a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css index a4ab26807..dca8251d6 100644 --- a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css +++ b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css @@ -101,4 +101,4 @@ height: 0.0625rem; align-self: stretch; background-color: var(--asc-color-base-shade4); -} +} \ No newline at end of file diff --git a/src/v4/social/components/StoryTab/StoryTabGlobalFeed.module.css b/src/v4/social/components/StoryTab/StoryTabGlobalFeed.module.css index 19dafcdaa..3bf905ab4 100644 --- a/src/v4/social/components/StoryTab/StoryTabGlobalFeed.module.css +++ b/src/v4/social/components/StoryTab/StoryTabGlobalFeed.module.css @@ -4,6 +4,7 @@ padding: 0.625rem; background-color: var(--asc-color-white); border-bottom: 0.0625rem solid #e6e6e6; + gap: var(--asc-spacing-s1) } .storyTab { diff --git a/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx b/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx index 47630fb9c..6dcb48a51 100644 --- a/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx +++ b/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx @@ -1,19 +1,47 @@ -import React, { useRef } from 'react'; +import React, { useRef, useEffect } from 'react'; import styles from './StoryTabGlobalFeed.module.css'; import { useNavigation } from '~/social/providers/NavigationProvider'; -import { useGetGlobalStoryTargets } from '../../hooks/collections/useGetGlobalStoryTargets'; import { StoryTabItem } from './StoryTabItem'; -import InfiniteScroll from 'react-infinite-scroll-component'; +import { useGlobalStoryTargets } from '~/v4/social/hooks/collections/useGlobalStoryTargets'; -interface StoryTabGlobalFeedProps {} +const STORIES_PER_PAGE = 10; -export const StoryTabGlobalFeed: React.FC = () => { - const { stories, isLoading, hasMore, loadMoreStories } = useGetGlobalStoryTargets({ +export const StoryTabGlobalFeed: React.FC = () => { + const { stories, isLoading, hasMore, loadMoreStories } = useGlobalStoryTargets({ seenState: 'smart' as Amity.StorySeenQuery, - limit: 10, + limit: STORIES_PER_PAGE, }); const { onClickStory } = useNavigation(); const containerRef = useRef(null); + const observerRef = useRef(null); + + useEffect(() => { + if (containerRef.current) { + observerRef.current = new IntersectionObserver( + (entries) => { + const lastStory = entries[0]; + + if (lastStory.isIntersecting && hasMore) { + loadMoreStories(); + } + }, + { root: containerRef.current, rootMargin: '0px', threshold: 0.9 }, + ); + + if (stories.length > 0) { + const lastStoryElement = containerRef.current.children[stories.length - 1]; + if (lastStoryElement) { + observerRef.current.observe(lastStoryElement); + } + } + } + + return () => { + if (observerRef.current) { + observerRef.current.disconnect(); + } + }; + }, [stories, hasMore, loadMoreStories]); if (isLoading) { return ( @@ -32,27 +60,17 @@ export const StoryTabGlobalFeed: React.FC = () => { return (
- Loading...} - style={{ display: 'flex', overflowX: 'auto', gap: '0.5rem' }} - scrollThreshold={0.9} - scrollableTarget="containerRef" - > - {stories.map((story) => { - return ( - onClickStory(story.targetId)} - size={64} - /> - ); - })} - + {stories.map((story) => { + return ( + onClickStory(story.targetId)} + size={64} + /> + ); + })}
); }; diff --git a/src/v4/social/hooks/collections/useGetGlobalStoryTargets.tsx b/src/v4/social/hooks/collections/useGlobalStoryTargets.tsx similarity index 92% rename from src/v4/social/hooks/collections/useGetGlobalStoryTargets.tsx rename to src/v4/social/hooks/collections/useGlobalStoryTargets.tsx index a75bd4fd4..127de2c3f 100644 --- a/src/v4/social/hooks/collections/useGetGlobalStoryTargets.tsx +++ b/src/v4/social/hooks/collections/useGlobalStoryTargets.tsx @@ -1,7 +1,7 @@ import { StoryRepository } from '@amityco/ts-sdk'; import useLiveCollection from '~/core/hooks/useLiveCollection'; -export const useGetGlobalStoryTargets = ( +export const useGlobalStoryTargets = ( params: Amity.LiveCollectionParams, ) => { const { items, hasMore, loadMore, ...rest } = useLiveCollection({ From 7ac39658db8018240bb00c29eccef04def52b304 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Fri, 26 Apr 2024 11:49:14 +0700 Subject: [PATCH 052/300] fix: official condition (#306) --- .../StoryViewer/Renderers/Wrappers/Header/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/index.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/index.tsx index 61d763ec7..174cd9af9 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/index.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/index.tsx @@ -65,7 +65,7 @@ const Header: React.FC< {heading}
- {!isOfficial && } + {isOfficial && }
{subheading}
From 04e1a61a664f1cfa72139da7702b7ee1489e3bb2 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Fri, 26 Apr 2024 12:08:09 +0700 Subject: [PATCH 053/300] fix: ASC-19646 - notification error when failed create story (#307) * fix: official condition * fix: add error --- src/v4/social/pages/DraftsPage/DraftsPage.tsx | 65 ++++++++++--------- 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index 2b509ae4b..07121ec41 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -18,7 +18,6 @@ import { StoryRepository } from '@amityco/ts-sdk'; import { HyperLink } from '../../elements/HyperLink'; import { HyperLinkConfig } from '../../components'; -import { useNavigation } from '~/social/providers/NavigationProvider'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; import { useNotifications } from '~/v4/core/providers/NotificationProvider'; @@ -76,36 +75,42 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor items?: Amity.StoryItem[], ) => { if (!file) return; - const formData = new FormData(); - formData.append('files', file); - setFile(null); - if (mediaType?.type === 'image') { - const { data: imageData } = await StoryRepository.createImageStory( - targetType, - targetId, - formData, - metadata, - imageMode, - items, - ); - if (imageData) { - notification.success({ - content: formatMessage({ id: 'storyViewer.notification.success' }), - }); - } - } else if (mediaType?.type === 'video') { - const { data: videoData } = await StoryRepository.createVideoStory( - targetType, - targetId, - formData, - metadata, - items, - ); - if (videoData) { - notification.success({ - content: formatMessage({ id: 'storyViewer.notification.success' }), - }); + try { + const formData = new FormData(); + formData.append('files', file); + setFile(null); + if (mediaType?.type === 'image') { + const { data: imageData } = await StoryRepository.createImageStory( + targetType, + targetId, + formData, + metadata, + imageMode, + items, + ); + if (imageData) { + notification.success({ + content: formatMessage({ id: 'storyViewer.notification.success' }), + }); + } + } else if (mediaType?.type === 'video') { + const { data: videoData } = await StoryRepository.createVideoStory( + targetType, + targetId, + formData, + metadata, + items, + ); + if (videoData) { + notification.success({ + content: formatMessage({ id: 'storyViewer.notification.success' }), + }); + } } + } catch (error) { + notification.error({ + content: formatMessage({ id: 'storyViewer.notification.error' }), + }); } }; From 72b7abfe5b74d0a9e58573310380167a0d59d747 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Fri, 26 Apr 2024 12:15:40 +0700 Subject: [PATCH 054/300] fix: ASC-22060 - navigate to community when create story (#308) * fix: official condition * fix: add error * fix: navigate --- src/v4/social/pages/DraftsPage/DraftsPage.tsx | 3 + .../pages/StoryPage/CommunityFeedStory.tsx | 65 ++++++++++--------- 2 files changed, 38 insertions(+), 30 deletions(-) diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index 07121ec41..4ae7d2982 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -20,6 +20,7 @@ import { HyperLink } from '../../elements/HyperLink'; import { HyperLinkConfig } from '../../components'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; import { useNotifications } from '~/v4/core/providers/NotificationProvider'; +import { useNavigation } from '~/social/providers/NavigationProvider'; type AmityStoryMediaType = { type: 'image'; url: string } | { type: 'video'; url: string }; @@ -35,6 +36,7 @@ type HyperLinkFormInputs = { }; const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStoryPageProps) => { + const { onClickCommunity } = useNavigation(); const { file, setFile } = useStoryContext(); const { navigationBehavior } = usePageBehavior(); const [isHyperLinkBottomSheetOpen, setIsHyperLinkBottomSheetOpen] = useState(false); @@ -76,6 +78,7 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor ) => { if (!file) return; try { + onClickCommunity(targetId); const formData = new FormData(); formData.append('files', file); setFile(null); diff --git a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx index 67f6e9d65..f5addb5f4 100644 --- a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx @@ -116,37 +116,42 @@ export const CommunityFeedStory = ({ communityId }: CommunityFeedStoryProps) => metadata?: Amity.Metadata, items?: Amity.StoryItem[], ) => { - onBack(); - const formData = new FormData(); - formData.append('files', file); - setFile(null); - if (file?.type.includes('image')) { - const { data: imageData } = await StoryRepository.createImageStory( - 'community', - communityId, - formData, - metadata, - imageMode, - items, - ); - if (imageData) { - notification.success({ - content: formatMessage({ id: 'storyViewer.notification.success' }), - }); - } - } else { - const { data: videoData } = await StoryRepository.createVideoStory( - 'community', - communityId, - formData, - metadata, - items, - ); - if (videoData) { - notification.success({ - content: formatMessage({ id: 'storyViewer.notification.success' }), - }); + try { + const formData = new FormData(); + formData.append('files', file); + setFile(null); + if (file?.type.includes('image')) { + const { data: imageData } = await StoryRepository.createImageStory( + 'community', + communityId, + formData, + metadata, + imageMode, + items, + ); + if (imageData) { + notification.success({ + content: formatMessage({ id: 'storyViewer.notification.success' }), + }); + } + } else { + const { data: videoData } = await StoryRepository.createVideoStory( + 'community', + communityId, + formData, + metadata, + items, + ); + if (videoData) { + notification.success({ + content: formatMessage({ id: 'storyViewer.notification.success' }), + }); + } } + } catch (error) { + notification.error({ + content: formatMessage({ id: 'storyViewer.notification.error' }), + }); } }; From ebf39f0718d1460e2096f4612f411c7564b4f583 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Fri, 26 Apr 2024 15:49:31 +0700 Subject: [PATCH 055/300] fix: ASC-21985 - navigate story target for global feed story (#309) * fix: official condition * fix: add error * fix: navigate * fix: story global feed navigate * fix: onClose * fix: ASC-22060 - create story on view story page (#310) * fix: story global feed upload * fix: remove console.log * fix: navigate --- src/social/components/CommunityInfo/index.tsx | 2 +- src/social/pages/Application/index.tsx | 7 - src/social/providers/NavigationProvider.tsx | 27 +++- .../components/StoryTab/StoryTabCommunity.tsx | 2 +- .../StoryTab/StoryTabGlobalFeed.tsx | 8 +- .../social/components/ViewStoryPage/index.tsx | 2 +- .../StoryViewer/Renderers/Image.tsx | 11 +- .../StoryViewer/Renderers/Video.tsx | 22 ++- src/v4/social/pages/Application/index.tsx | 4 +- src/v4/social/pages/DraftsPage/DraftsPage.tsx | 17 ++- .../pages/StoryPage/GlobalFeedStory.tsx | 139 +++++++++++------- .../social/pages/StoryPage/ViewStoryPage.tsx | 6 + 12 files changed, 158 insertions(+), 89 deletions(-) diff --git a/src/social/components/CommunityInfo/index.tsx b/src/social/components/CommunityInfo/index.tsx index 8adfd8fbe..fa96b3be5 100644 --- a/src/social/components/CommunityInfo/index.tsx +++ b/src/social/components/CommunityInfo/index.tsx @@ -41,7 +41,7 @@ const CommunityInfo = ({ communityId, stories }: CommunityInfoProps) => { canReviewCommunityPosts, } = useCommunityInfo(communityId); - const { info, confirm } = useConfirmContext(); + const { confirm } = useConfirmContext(); const categoryNames = (communityCategories || []).map((category) => category.name); diff --git a/src/social/pages/Application/index.tsx b/src/social/pages/Application/index.tsx index fd6c685e2..c06c3b3ba 100644 --- a/src/social/pages/Application/index.tsx +++ b/src/social/pages/Application/index.tsx @@ -16,7 +16,6 @@ import CommunityEditPage from '~/social/pages/CommunityEdit'; import ProfileSettings from '~/social/components/ProfileSettings'; import { useNavigation } from '~/social/providers/NavigationProvider'; import useSDK from '~/core/hooks/useSDK'; -import { AmityViewStoryPage } from '~/v4/social/pages/StoryPage'; import { StoryProvider } from '~/v4/social/providers/StoryProvider'; import CommunityFeed from '../CommunityFeed'; @@ -88,12 +87,6 @@ const Community = () => { /> )} - {page.type === PageTypes.ViewStory && ( - - - - )} - {page.type === PageTypes.CommunityEdit && ( )} diff --git a/src/social/providers/NavigationProvider.tsx b/src/social/providers/NavigationProvider.tsx index 435b56b3e..271a51e25 100644 --- a/src/social/providers/NavigationProvider.tsx +++ b/src/social/providers/NavigationProvider.tsx @@ -34,6 +34,8 @@ type Page = storyId: string; targetId?: string; communityId?: string; + targetIds?: string[]; + storyType?: 'communityFeed' | 'globalFeed'; }; type ContextValue = { @@ -42,7 +44,11 @@ type ContextValue = { onClickCategory: (categoryId: string) => void; onClickCommunity: (communityId: string) => void; onClickUser: (userId: string, pageType?: string) => void; - onClickStory: (storyId: string) => void; + onClickStory: ( + storyId: string, + storyType: 'communityFeed' | 'globalFeed', + targetId?: string[], + ) => void; onCommunityCreated: (communityId: string) => void; onEditCommunity: (communityId: string, tab?: string) => void; onEditUser: (userId: string) => void; @@ -66,7 +72,11 @@ let defaultValue: ContextValue = { onClickCategory: (categoryId: string) => {}, onClickCommunity: (communityId: string) => {}, onClickUser: (userId: string) => {}, - onClickStory: (storyId: string) => {}, + onClickStory: ( + storyId: string, + storyType: 'communityFeed' | 'globalFeed', + targetId?: string[], + ) => {}, onCommunityCreated: (communityId: string) => {}, onEditCommunity: (communityId: string) => {}, onEditUser: (userId: string) => {}, @@ -90,7 +100,8 @@ if (process.env.NODE_ENV !== 'production') { onClickCommunity: (communityId) => console.log(`NavigationContext onClickCommunity(${communityId})`), onClickUser: (userId) => console.log(`NavigationContext onClickUser(${userId})`), - onClickStory: (storyId) => console.log(`NavigationContext onClickStory(${storyId})`), + onClickStory: (storyId, storyType, targetIds) => + console.log(`NavigationContext onClickStory(${storyId}, ${storyType}, ${targetIds})`), onCommunityCreated: (communityId) => console.log(`NavigationContext onCommunityCreated(${communityId})`), onEditCommunity: (communityId) => @@ -118,7 +129,11 @@ interface NavigationProviderProps { onClickCategory?: (categoryId: string) => void; onClickCommunity?: (communityId: string) => void; onClickUser?: (userId: string) => void; - onClickStory?: (storyId: string) => void; + onClickStory?: ( + storyId: string, + storyType: 'communityFeed' | 'globalFeed', + targetId?: string[], + ) => void; onCommunityCreated?: (communityId: string) => void; onEditCommunity?: (communityId: string, options?: { tab?: string }) => void; onEditUser?: (userId: string) => void; @@ -313,10 +328,12 @@ export default function NavigationProvider({ ); const handleClickStory = useCallback( - (targetId) => { + (targetId, storyType, targetIds) => { const next = { type: PageTypes.ViewStory, targetId, + storyType, + targetIds, }; if (onChangePage) return onChangePage(next); diff --git a/src/v4/social/components/StoryTab/StoryTabCommunity.tsx b/src/v4/social/components/StoryTab/StoryTabCommunity.tsx index 9c8ee3785..dfdc0ad2b 100644 --- a/src/v4/social/components/StoryTab/StoryTabCommunity.tsx +++ b/src/v4/social/components/StoryTab/StoryTabCommunity.tsx @@ -50,7 +50,7 @@ export const StoryTabCommunityFeed: React.FC = ({ co const handleOnClick = () => { if (Array.isArray(stories) && stories.length === 0) return; - onClickStory(communityId); + onClickStory(communityId, 'communityFeed'); }; const { client } = useSDK(); diff --git a/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx b/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx index 6dcb48a51..1d36be6dc 100644 --- a/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx +++ b/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx @@ -66,7 +66,13 @@ export const StoryTabGlobalFeed: React.FC = () => { key={story.targetId} targetId={story.targetId} hasUnseen={story.hasUnseen} - onClick={() => onClickStory(story.targetId)} + onClick={() => + onClickStory( + story.targetId, + 'globalFeed', + stories.map((s) => s.targetId), + ) + } size={64} /> ); diff --git a/src/v4/social/components/ViewStoryPage/index.tsx b/src/v4/social/components/ViewStoryPage/index.tsx index bb81e37c0..ba3ed181c 100644 --- a/src/v4/social/components/ViewStoryPage/index.tsx +++ b/src/v4/social/components/ViewStoryPage/index.tsx @@ -253,7 +253,7 @@ const StoryViewer = ({ pageId, targetId, duration = 5000, onClose }: StoryViewer } }, [stories, file, currentIndex]); - if (isDraft && file) { + if (file) { return ( { const { formatMessage } = useIntl(); - const { onBack, onClickCommunity } = useNavigation(); + const { page, onChangePage, onClickCommunity } = useNavigation(); const [loaded, setLoaded] = useState(false); const [isOpenBottomSheet, setIsOpenBottomSheet] = useState(false); const [isOpenCommentSheet, setIsOpenCommentSheet] = useState(false); @@ -143,7 +144,13 @@ export const renderer: CustomRenderer = ({ story, action, config }) => { onAction={openBottomSheet} onAddStory={handleAddIconClick} onClickCommunity={() => onClickCommunity(community?.communityId as string)} - onClose={onBack} + onClose={() => { + if (page.type === PageTypes.ViewStory && page.storyType === 'globalFeed') { + onChangePage(PageTypes.NewsFeed); + return; + } + onClickCommunity(community?.communityId as string); + }} addStoryButton={addStoryButton} /> { const { formatMessage } = useIntl(); - const { onBack, onClickCommunity } = useNavigation(); + const { page, onClickCommunity, onChangePage } = useNavigation(); const [loaded, setLoaded] = useState(false); const [muted, setMuted] = useState(false); const [isPaused, setIsPaused] = useState(false); @@ -36,13 +37,6 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler const [isOpenCommentSheet, setIsOpenCommentSheet] = useState(false); const { width, height, loader, storyStyles } = config; const { client } = useSDK(); - const [isReplying, setIsReplying] = useState(false); - const [replyTo, setReplyTo] = useState(undefined); - const [selectedComment, setSelectedComment] = useState<{ - referenceType?: string; - referenceId?: string; - commentId?: string; - } | null>(null); const isLiked = !!(story && story.myReactions && story.myReactions.includes(LIKE_REACTION_KEY)); const totalLikes = story?.reactions[LIKE_REACTION_KEY] || 0; @@ -187,7 +181,13 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler onAction={openBottomSheet} onAddStory={handleAddIconClick} onClickCommunity={() => onClickCommunity(community?.communityId as string)} - onClose={onBack} + onClose={() => { + if (page.type === PageTypes.ViewStory && page.storyType === 'globalFeed') { + onChangePage(PageTypes.NewsFeed); + return; + } + onClickCommunity(community?.communityId as string); + }} addStoryButton={addStoryButton} /> { toggleOpen={toggleOpen} /> )} - {page.type === PageTypes.ViewStory && ( + {page.type === PageTypes.ViewStory && page.storyType && (
- +
)} {page.type === PageTypes.CommunityEdit && ( diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index 4ae7d2982..55fbdbf86 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -21,11 +21,12 @@ import { HyperLinkConfig } from '../../components'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; import { useNotifications } from '~/v4/core/providers/NotificationProvider'; import { useNavigation } from '~/social/providers/NavigationProvider'; +import { PageTypes } from '~/social/constants'; type AmityStoryMediaType = { type: 'image'; url: string } | { type: 'video'; url: string }; type AmityDraftStoryPageProps = { - targetId: string; + targetId?: string; targetType: Amity.StoryTargetType; mediaType?: AmityStoryMediaType; }; @@ -36,7 +37,7 @@ type HyperLinkFormInputs = { }; const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStoryPageProps) => { - const { onClickCommunity } = useNavigation(); + const { page, onChangePage, onClickCommunity } = useNavigation(); const { file, setFile } = useStoryContext(); const { navigationBehavior } = usePageBehavior(); const [isHyperLinkBottomSheetOpen, setIsHyperLinkBottomSheetOpen] = useState(false); @@ -78,11 +79,17 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor ) => { if (!file) return; try { - onClickCommunity(targetId); const formData = new FormData(); formData.append('files', file); setFile(null); - if (mediaType?.type === 'image') { + if (page.type === PageTypes.ViewStory && page.storyType === 'globalFeed') { + onChangePage(PageTypes.NewsFeed); + } else { + if (page.communityId) { + onClickCommunity(page.communityId); + } + } + if (mediaType?.type === 'image' && targetId) { const { data: imageData } = await StoryRepository.createImageStory( targetType, targetId, @@ -96,7 +103,7 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor content: formatMessage({ id: 'storyViewer.notification.success' }), }); } - } else if (mediaType?.type === 'video') { + } else if (mediaType?.type === 'video' && targetId) { const { data: videoData } = await StoryRepository.createVideoStory( targetType, targetId, diff --git a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx index 59b99b11d..01d93e801 100644 --- a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx @@ -1,21 +1,15 @@ import React, { useEffect, useRef, useState } from 'react'; - import useStories from '~/social/hooks/useStories'; - import useSDK from '~/core/hooks/useSDK'; - import { useMedia } from 'react-use'; import { useIntl } from 'react-intl'; - import { FinalColor } from 'extract-colors/lib/types/Color'; import { StoryRepository } from '@amityco/ts-sdk'; import { CreateStoryButton } from '../../elements'; import { Trash2Icon } from '~/icons'; -import { isNonNullable } from '~/helpers/utils'; +import { isNonNullable } from '~/v4/helpers/utils'; import { extractColors } from 'extract-colors'; - import { useNavigation } from '~/social/providers/NavigationProvider'; - import { HiddenInput, StoryArrowLeftButton, @@ -32,21 +26,26 @@ import { checkStoryPermission } from '~/utils'; import { useStoryContext } from '../../providers/StoryProvider'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; import { useNotifications } from '~/v4/core/providers/NotificationProvider'; - -interface CommunityFeedStoryProps { - communityId: string; -} +import { PageTypes } from '~/social/constants'; const DURATION = 5000; -export const GlobalFeedStory = ({ communityId }: CommunityFeedStoryProps) => { - const { onBack } = useNavigation(); +interface GlobalFeedStoryProps { + targetId: string; +} + +export const GlobalFeedStory: React.FC = () => { + const { page } = useNavigation(); + const { onClickStory, onChangePage } = useNavigation(); const { confirm } = useConfirmContext(); const notification = useNotifications(); const { stories } = useStories({ - targetId: communityId, targetType: 'community', + targetId: + page.type === PageTypes.ViewStory && page.storyType === 'globalFeed' && page.targetId + ? page.targetId + : '', options: { orderBy: 'asc', sortBy: 'createdAt', @@ -81,7 +80,7 @@ export const GlobalFeedStory = ({ communityId }: CommunityFeedStoryProps) => { const [colors, setColors] = useState([]); const isStoryCreator = stories[currentIndex]?.creator?.userId === currentUserId; - const haveStoryPermission = checkStoryPermission(client, communityId); + const haveStoryPermission = checkStoryPermission(client, stories[currentIndex]?.targetId); const confirmDeleteStory = (storyId: string) => { const isLastStory = currentIndex === 0; @@ -92,7 +91,7 @@ export const GlobalFeedStory = ({ communityId }: CommunityFeedStoryProps) => { onOk: async () => { previousStory(); if (isLastStory) { - onBack(); + onChangePage(PageTypes.NewsFeed); } await StoryRepository.softDeleteStory(storyId); notification.success({ @@ -116,37 +115,44 @@ export const GlobalFeedStory = ({ communityId }: CommunityFeedStoryProps) => { metadata?: Amity.Metadata, items?: Amity.StoryItem[], ) => { - onBack(); - const formData = new FormData(); - formData.append('files', file); - setFile(null); - if (file?.type.includes('image')) { - const { data: imageData } = await StoryRepository.createImageStory( - 'community', - communityId, - formData, - metadata, - imageMode, - items, - ); - if (imageData) { - notification.success({ - content: formatMessage({ id: 'storyViewer.notification.success' }), - }); - } - } else { - const { data: videoData } = await StoryRepository.createVideoStory( - 'community', - communityId, - formData, - metadata, - items, - ); - if (videoData) { - notification.success({ - content: formatMessage({ id: 'storyViewer.notification.success' }), - }); + try { + const formData = new FormData(); + formData.append('files', file); + setFile(null); + if (file?.type.includes('image') && currentUserId) { + const { data: imageData } = await StoryRepository.createImageStory( + 'user', + currentUserId, + formData, + metadata, + imageMode, + items, + ); + if (imageData) { + notification.success({ + content: formatMessage({ id: 'storyViewer.notification.success' }), + }); + } + } else { + if (currentUserId) { + const { data: videoData } = await StoryRepository.createVideoStory( + 'user', + currentUserId, + formData, + metadata, + items, + ); + if (videoData) { + notification.success({ + content: formatMessage({ id: 'storyViewer.notification.success' }), + }); + } + } } + } catch (error) { + notification.error({ + content: formatMessage({ id: 'storyViewer.notification.error' }), + }); } }; @@ -184,15 +190,44 @@ export const GlobalFeedStory = ({ communityId }: CommunityFeedStoryProps) => { }); const nextStory = () => { - if (currentIndex === stories.length - 1) { - onBack(); + if ( + page.type === PageTypes.ViewStory && + page.targetIds && + page.targetId && + currentIndex === formattedStories?.length - 1 + ) { + const currentTargetIndex = page.targetIds.indexOf(page.targetId); + const nextTargetIndex = currentTargetIndex + 1; + + if (nextTargetIndex < page.targetIds.length) { + const nextTargetId = page.targetIds[nextTargetIndex]; + onClickStory(nextTargetId, 'globalFeed', page.targetIds); + } else { + onChangePage(PageTypes.NewsFeed); + } return; } setCurrentIndex(currentIndex + 1); }; const previousStory = () => { - if (currentIndex === 0) return; + if ( + page.type === PageTypes.ViewStory && + page.targetIds && + page.targetId && + currentIndex === 0 + ) { + const currentTargetIndex = page.targetIds.indexOf(page.targetId); + const previousTargetIndex = currentTargetIndex - 1; + + if (previousTargetIndex >= 0) { + const previousTargetId = page.targetIds[previousTargetIndex]; + onClickStory(previousTargetId, 'globalFeed', page.targetIds); + } else { + onChangePage(PageTypes.NewsFeed); + } + return; + } setCurrentIndex(currentIndex - 1); }; @@ -242,7 +277,7 @@ export const GlobalFeedStory = ({ communityId }: CommunityFeedStoryProps) => { } }, [stories, file, currentIndex]); - if (file) { + if (file && page.type === PageTypes.ViewStory && page.storyType === 'globalFeed') { return ( { ? { type: 'image', url: URL.createObjectURL(file) } : { type: 'video', url: URL.createObjectURL(file) } } - targetId={communityId} + targetId={page.targetId} targetType="community" /> ); @@ -287,7 +322,7 @@ export const GlobalFeedStory = ({ communityId }: CommunityFeedStoryProps) => { onStoryEnd={increaseIndex} onNext={nextStory} onPrevious={previousStory} - onAllStoriesEnd={onBack} + onAllStoriesEnd={() => onChangePage(PageTypes.NewsFeed)} /> ) : null} diff --git a/src/v4/social/pages/StoryPage/ViewStoryPage.tsx b/src/v4/social/pages/StoryPage/ViewStoryPage.tsx index 521826696..2a452ef3c 100644 --- a/src/v4/social/pages/StoryPage/ViewStoryPage.tsx +++ b/src/v4/social/pages/StoryPage/ViewStoryPage.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { CommunityFeedStory } from './CommunityFeedStory'; import { useNavigation } from '~/social/providers/NavigationProvider'; import { PageTypes } from '~/social/constants'; +import { GlobalFeedStory } from './GlobalFeedStory'; type AmityViewStoryPageType = 'communityFeed' | 'globalFeed'; @@ -19,6 +20,11 @@ const AmityViewStoryPage: React.FC = ({ type }) => { return ; } return null; + case 'globalFeed': + if (page.type === PageTypes.ViewStory && page.targetId) { + return ; + } + return null; default: return null; } From 6120a05cc11d66e8ad15db11867d75be6a3730b2 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Fri, 26 Apr 2024 16:24:07 +0700 Subject: [PATCH 056/300] fix: ASC-21970 - story uploading state (#297) * fix: font * fix: story tab gap * fix: use css var * fix: modal * fix: can't delete story * fix: v4 component * fix: hyperlink config * fix: shouldAllowCreation * fix: type * fix: uploading state * fix: remove console.log * fix: use css var * fix: modal * fix: v4 component * fix: hyperlink config * fix: shouldAllowCreation * fix: type * fix: uploading state * fix: remove console.log * fix: storyRing state * fix: remove fill in verified icon * fix: story ring * fix: icon * fix: story --------- Co-authored-by: Bonn --- src/social/components/CommunityInfo/index.tsx | 23 -- src/v4/core/components/ConfirmModal/index.tsx | 2 +- src/v4/icons/Verified.tsx | 21 ++ .../HyperLinkConfig.module.css | 6 +- .../HyperLinkConfig/HyperLinkConfig.tsx | 1 + .../components/StoryTab/StoryRing.module.css | 21 +- .../social/components/StoryTab/StoryRing.tsx | 249 +++++++++++++----- .../components/StoryTab/StoryTabCommunity.tsx | 8 +- .../StoryTab/StoryTabGlobalFeed.module.css | 2 +- .../components/StoryTab/StoryTabItem.tsx | 8 +- src/v4/social/icons/verified.tsx | 5 +- .../Wrappers/Header/Header.module.css | 2 +- .../Renderers/Wrappers/Header/index.tsx | 2 +- .../social/pages/Application/sdk.stories.tsx | 2 +- .../pages/DraftsPage/DraftsPage.module.css | 2 +- src/v4/social/pages/DraftsPage/DraftsPage.tsx | 10 +- .../pages/StoryPage/GlobalFeedStory.tsx | 11 +- 17 files changed, 253 insertions(+), 122 deletions(-) create mode 100644 src/v4/icons/Verified.tsx diff --git a/src/social/components/CommunityInfo/index.tsx b/src/social/components/CommunityInfo/index.tsx index fa96b3be5..3ed33b2f6 100644 --- a/src/social/components/CommunityInfo/index.tsx +++ b/src/social/components/CommunityInfo/index.tsx @@ -3,12 +3,7 @@ import { CommunityPostSettings } from '@amityco/ts-sdk'; import UICommunityInfo from './UICommunityInfo'; import { useCommunityInfo } from './hooks'; -import { useNavigation } from '~/social/providers/NavigationProvider'; -import useSDK from '~/core/hooks/useSDK'; - -import { useStoryContext } from '~/v4/social/providers/StoryProvider'; -import { checkStoryPermission } from '~/utils'; import { FormattedMessage } from 'react-intl'; import { useConfirmContext } from '~/core/providers/ConfirmProvider'; @@ -18,17 +13,6 @@ interface CommunityInfoProps { } const CommunityInfo = ({ communityId, stories }: CommunityInfoProps) => { - const { setFile } = useStoryContext(); - const haveStories = stories?.length > 0; - const isStorySyncing = haveStories && stories.some((story) => story?.syncState === 'syncing'); - const isStoryErrored = haveStories && stories.some((story) => story?.syncState === 'error'); - const isSeen = haveStories && stories.every((story) => story?.isSeen); - - const { onClickStory } = useNavigation(); - - const { client } = useSDK(); - const haveStoryPermission = checkStoryPermission(client, communityId); - const { community, communityCategories, @@ -83,13 +67,6 @@ const CommunityInfo = ({ communityId, stories }: CommunityInfoProps) => { onOk: () => leaveCommunity(), }) } - setStoryFile={(storyFile) => setFile(storyFile)} - haveStories={haveStories || false} - haveStoryPermission={haveStoryPermission} - isStorySyncing={isStorySyncing || false} - isStoryErrored={isStoryErrored || false} - isSeen={isSeen || false} - onClickStory={onClickStory} /> ); }; diff --git a/src/v4/core/components/ConfirmModal/index.tsx b/src/v4/core/components/ConfirmModal/index.tsx index 077f257af..7bb1f5804 100644 --- a/src/v4/core/components/ConfirmModal/index.tsx +++ b/src/v4/core/components/ConfirmModal/index.tsx @@ -42,7 +42,7 @@ const Confirm = ({ } onCancel={onCancel} > - {content} +
{content}
); diff --git a/src/v4/icons/Verified.tsx b/src/v4/icons/Verified.tsx new file mode 100644 index 000000000..7fd75ef2b --- /dev/null +++ b/src/v4/icons/Verified.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +const Svg = (props: React.SVGProps) => ( + + + +); + +export default Svg; diff --git a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css index dca8251d6..a1bb228d7 100644 --- a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css +++ b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css @@ -21,12 +21,13 @@ border: none; border-bottom: 1px solid var(--asc-color-base-shade4); outline: none; - color: inherit; + color: var(--asc-color-base-default); background-color: transparent; } .input.hasError { border-bottom-color: var(--asc-color-alert); + color: var(--asc-color-alert); } .label { @@ -40,6 +41,7 @@ .label.required::after { content: '*'; + color: var(--asc-color-alert); } .description { @@ -88,7 +90,7 @@ justify-content: flex-start; align-items: center; gap: var(--asc-spacing-s1); - color: var(--asc-color-alert) ; + color: var(--asc-color-alert); border-radius: 0; background-color: transparent; transition: color 0.3s ease; diff --git a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx index 19df8ccf9..dad3c7fac 100644 --- a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx +++ b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx @@ -170,6 +170,7 @@ export const HyperLinkConfig = ({ {formatMessage({ id: 'storyCreation.hyperlink.form.urlLabel' })} + { - pageId: '*'; - componentId: 'story_tab_component'; + pageId?: '*'; + componentId?: 'story_tab_component'; hasUnseen?: boolean; uploading?: boolean; isErrored?: boolean; size?: number; } +const EmptyStateRingSvg = ({ + pageId, + componentId, + elementId, + size, +}: { + pageId: string; + componentId: string; + elementId: string; + size: number; +}) => { + const { getConfig } = useCustomization(); + const elementConfig = getConfig(`${pageId}/${componentId}/${elementId}`); + return ( + + + + + + + + + + ); +}; + +const HasSeenRingSvg = ({ + pageId, + componentId, + elementId, + size, +}: { + pageId: string; + componentId: string; + elementId: string; + size: number; +}) => { + const { getConfig } = useCustomization(); + const elementConfig = getConfig(`${pageId}/${componentId}/${elementId}`); + return ( + + + + + + + + + + ); +}; + +const UploadingRingSvg = ({ + pageId, + componentId, + elementId, + size, +}: { + pageId: string; + componentId: string; + elementId: string; + size: number; +}) => { + const { getConfig } = useCustomization(); + const elementConfig = getConfig(`${pageId}/${componentId}/${elementId}`); + return ( + + + + + + + + + + ); +}; + const StoryRing = ({ pageId = '*', componentId = 'story_tab_component', @@ -22,21 +170,10 @@ const StoryRing = ({ ...props }: StoryRingProps) => { const elementId = 'story_ring'; - const { getConfig, isExcluded } = useCustomization(); - const elementConfig = getConfig(`${pageId}/${componentId}/${elementId}`); + const { isExcluded } = useCustomization(); const isElementExcluded = isExcluded(`${pageId}/${componentId}/${elementId}`); - const scaleFactor = size / 64; // Assuming the default size is 64 - const ringSize = 48 * scaleFactor; // Adjust the ring size based on the scale factor - const viewBox = `0 0 ${ringSize} ${ringSize}`; - const ringRadius = 23 * scaleFactor; - const strokeWidth = 2 * scaleFactor; - - const strokeDasharray = 339 * scaleFactor; - const strokeDashoffset = (339 / 2) * scaleFactor; - if (isElementExcluded) return null; - if (isErrored) { return ( + strokeWidth="2" + > ); } - return ( - - - - - - - - - {hasUnseen && ( - - )} - + ); + } + + if (hasUnseen) { + return ( + + ); + } + + return ( + ); }; diff --git a/src/v4/social/components/StoryTab/StoryTabCommunity.tsx b/src/v4/social/components/StoryTab/StoryTabCommunity.tsx index dfdc0ad2b..d804de851 100644 --- a/src/v4/social/components/StoryTab/StoryTabCommunity.tsx +++ b/src/v4/social/components/StoryTab/StoryTabCommunity.tsx @@ -1,7 +1,7 @@ import React, { useRef } from 'react'; import Truncate from 'react-truncate-markup'; import { backgroundImage as CommunityImage } from '~/icons/Community'; -import styles from './StoryTabCommunity.module.css'; + import StoryRing from './StoryRing'; import useStories from '~/social/hooks/useStories'; import useSDK from '~/core/hooks/useSDK'; @@ -9,7 +9,7 @@ import { checkStoryPermission } from '~/utils'; import { useStoryContext } from '~/v4/social/providers/StoryProvider'; import { useCommunityInfo } from '~/social/components/CommunityInfo/hooks'; import { useNavigation } from '~/social/providers/NavigationProvider'; -import { StoryTabItem } from './StoryTabItem'; + import { AddStoryButton, ErrorButton, @@ -56,7 +56,7 @@ export const StoryTabCommunityFeed: React.FC = ({ co const { client } = useSDK(); const hasStoryPermission = checkStoryPermission(client, communityId); const hasStoryRing = stories?.length > 0; - const isSeen = stories.every((story) => story?.isSeen); + const hasUnSeen = stories.some((story) => !story?.isSeen); const uploading = stories.some((story) => story?.syncState === 'syncing'); const isErrored = stories.some((story) => story?.syncState === 'error'); @@ -67,7 +67,7 @@ export const StoryTabCommunityFeed: React.FC = ({ co = ({ targetId, hasUnseen, onC
- {community?.isOfficial && ( - - )} + {community?.isOfficial && }
{communityAvatar && ( {community?.displayName} diff --git a/src/v4/social/icons/verified.tsx b/src/v4/social/icons/verified.tsx index 9bb8d4066..f2dac3f5d 100644 --- a/src/v4/social/icons/verified.tsx +++ b/src/v4/social/icons/verified.tsx @@ -10,10 +10,7 @@ function Verified(props: React.SVGProps) { fill="none" {...props} > - + ); } diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/Header.module.css b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/Header.module.css index 189a9df01..8294adf3d 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/Header.module.css +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/Header.module.css @@ -70,7 +70,7 @@ } .verifiedBadge { - color: var(--asc-color-white); + fill: var(--asc-color-white); } .dotsButton { diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/index.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/index.tsx index 174cd9af9..0237b22cf 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/index.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/index.tsx @@ -4,7 +4,7 @@ import Truncate from 'react-truncate-markup'; import Avatar from '~/core/components/Avatar'; import { backgroundImage as communityBackgroundImage } from '~/icons/Community'; -import { PauseIcon, PlayIcon, VerifiedIcon } from '~/icons'; +import { PauseIcon, PlayIcon } from '~/icons'; import { CloseButton, OverflowMenuButton } from '~/v4/social/elements'; import Verified from '~/v4/social/icons/verified'; diff --git a/src/v4/social/pages/Application/sdk.stories.tsx b/src/v4/social/pages/Application/sdk.stories.tsx index e6875f829..24fd1f577 100644 --- a/src/v4/social/pages/Application/sdk.stories.tsx +++ b/src/v4/social/pages/Application/sdk.stories.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import UiKitApp from './index'; +import UiKitApp from '.'; export default { title: 'V4/Social', diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.module.css b/src/v4/social/pages/DraftsPage/DraftsPage.module.css index 97ea83386..72f273564 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.module.css +++ b/src/v4/social/pages/DraftsPage/DraftsPage.module.css @@ -68,4 +68,4 @@ bottom: 6rem; left: 0; right: 0; -} \ No newline at end of file +} diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index 55fbdbf86..9173ef398 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -152,7 +152,15 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor }; const onRemoveHyperLink = () => { - setHyperLink([]); + setHyperLink([ + { + data: { + url: '', + customText: '', + }, + type: 'hyperlink' as Amity.StoryItemType, + }, + ]); }; useEffect(() => { diff --git a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx index 01d93e801..c55e9cda3 100644 --- a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx @@ -177,7 +177,13 @@ export const GlobalFeedStory: React.FC = () => { ? { name: 'delete', action: () => deleteStory(story?.storyId as string), - icon: , + icon: ( + + ), } : null, ].filter(isNonNullable), @@ -205,6 +211,7 @@ export const GlobalFeedStory: React.FC = () => { } else { onChangePage(PageTypes.NewsFeed); } + setCurrentIndex(0); return; } setCurrentIndex(currentIndex + 1); @@ -226,6 +233,7 @@ export const GlobalFeedStory: React.FC = () => { } else { onChangePage(PageTypes.NewsFeed); } + setCurrentIndex(0); return; } setCurrentIndex(currentIndex - 1); @@ -322,7 +330,6 @@ export const GlobalFeedStory: React.FC = () => { onStoryEnd={increaseIndex} onNext={nextStory} onPrevious={previousStory} - onAllStoriesEnd={() => onChangePage(PageTypes.NewsFeed)} /> ) : null} From e1e17e9dd9dd380a4e4600d8c11c6769a20e803c Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Fri, 26 Apr 2024 17:13:44 +0700 Subject: [PATCH 057/300] fix: notification style (#311) --- src/v4/core/components/Notification/styles.module.css | 4 ++-- src/v4/core/providers/NotificationProvider.module.css | 2 +- src/v4/core/providers/NotificationProvider.tsx | 5 +++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/v4/core/components/Notification/styles.module.css b/src/v4/core/components/Notification/styles.module.css index ab5a7aafa..36dd28e5c 100644 --- a/src/v4/core/components/Notification/styles.module.css +++ b/src/v4/core/components/Notification/styles.module.css @@ -24,13 +24,13 @@ display: flex; justify-content: center; align-items: center; - color: white; + color: var(--asc-color-white); border-radius: 4px; margin-bottom: 10px; animation-duration: 0.3s; animation-name: appear; pointer-events: auto; - background-color: var(--asc-color-base-shade4); + background-color: var(--asc-color-base-default); } @keyframes appear { diff --git a/src/v4/core/providers/NotificationProvider.module.css b/src/v4/core/providers/NotificationProvider.module.css index ab5a7aafa..0f8bf6de4 100644 --- a/src/v4/core/providers/NotificationProvider.module.css +++ b/src/v4/core/providers/NotificationProvider.module.css @@ -1,8 +1,8 @@ -/* styles.module.css */ .icon { width: 1.125rem; height: 1.125rem; margin-right: 8px; + fill: var(--asc-color-white); } .notifications { diff --git a/src/v4/core/providers/NotificationProvider.tsx b/src/v4/core/providers/NotificationProvider.tsx index 97a4c8d85..1fd6846f3 100644 --- a/src/v4/core/providers/NotificationProvider.tsx +++ b/src/v4/core/providers/NotificationProvider.tsx @@ -1,5 +1,6 @@ import React, { createContext, ReactNode, useContext, useState } from 'react'; -import Check from '~/v4/icons/Check'; + +import CheckCircle from '~/v4/icons/CheckCircle'; import ExclamationCircle from '~/v4/icons/ExclamationCircle'; import Remove from '~/v4/icons/Remove'; import styles from './NotificationProvider.module.css'; @@ -65,7 +66,7 @@ export const NotificationProvider: React.FC = ({ children }) => { notifications, notificationFunction: { success: (data: Omit) => - addNotifications({ ...data, icon: }), + addNotifications({ ...data, icon: }), info: (data: Omit) => addNotifications({ ...data, icon: }), error: (data: Omit) => From 2b05e243dcf3ccba3f9e3453ea5ff12578d8f49f Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Fri, 26 Apr 2024 18:01:03 +0700 Subject: [PATCH 058/300] fix: add onAllStoriesEnd (#312) --- src/v4/social/pages/StoryPage/GlobalFeedStory.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx index c55e9cda3..1a22193ba 100644 --- a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx @@ -330,6 +330,7 @@ export const GlobalFeedStory: React.FC = () => { onStoryEnd={increaseIndex} onNext={nextStory} onPrevious={previousStory} + onAllStoriesEnd={nextStory} /> ) : null} From bfe560ce6357b44d8e8f65a20d670f76d23d347c Mon Sep 17 00:00:00 2001 From: Pitchaya T <33589608+ptchayap@users.noreply.github.com> Date: Fri, 26 Apr 2024 21:23:26 +0700 Subject: [PATCH 059/300] Release/v4.0.0 beta.3 (#314) * chore: upgrade dependencies * fix: cherry-pick bugs from v3 * chore(release): 4.0.0-beta.3 --------- Co-authored-by: bmo-amity-bot --- CHANGELOG.md | 6 +++++ package.json | 4 ++-- pnpm-lock.yaml | 8 +++---- src/core/providers/UiKitProvider/index.tsx | 25 +++++++++++---------- src/social/hooks/useFeed.ts | 5 +++++ src/social/providers/NavigationProvider.tsx | 11 ++++++++- 6 files changed, 40 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c87e2f89..8191dcf49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## 4.0.0-beta.3 (2024-04-26) + +### Bug Fixes + +- cherry-pick bugs from v3 ([7b01a95](https://github.com/EkoCommunications/AmityUiKitWeb/commit/7b01a956db5a7c019164ee4ffaf781a55c588714)) + ## 4.0.0-beta.2 (2024-04-12) ### Bug Fixes diff --git a/package.json b/package.json index 74a0d0fd9..d6cac5f43 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@amityco/ui-kit", - "version": "4.0.0-beta.2", + "version": "4.0.0-beta.3", "engines": { "node": ">=16", "pnpm": ">=8" @@ -39,7 +39,7 @@ "react-dom": ">=17.0.2" }, "devDependencies": { - "@amityco/ts-sdk": "^6.21.0", + "@amityco/ts-sdk": "^6.23.0", "@fortawesome/pro-light-svg-icons": "^5.15.4", "@fortawesome/pro-regular-svg-icons": "^5.15.4", "@fortawesome/pro-solid-svg-icons": "^5.15.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0de01765d..8f77de438 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -101,8 +101,8 @@ dependencies: devDependencies: '@amityco/ts-sdk': - specifier: ^6.21.0 - version: 6.22.0 + specifier: ^6.23.0 + version: 6.23.0 '@fortawesome/pro-light-svg-icons': specifier: ^5.15.4 version: 5.15.4 @@ -274,8 +274,8 @@ packages: resolution: {integrity: sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==} dev: true - /@amityco/ts-sdk@6.22.0: - resolution: {integrity: sha512-JWJ1c0+5ipEwBwXzVNjNWRkk2MCsNkSGjunfXWEoOqfZEjxnjAVW9eoS5ev1vB/j9HdfVSumuwhYqbxEcHQu1g==} + /@amityco/ts-sdk@6.23.0: + resolution: {integrity: sha512-0wLdz4h8hojNcM7WFpgk/sGCbe3ir+lCHu9+yxmSckMoK18/vOq7jSZNHCkElM99smqtU8R5+EoLurQTSW45Gw==} engines: {node: '>=12', npm: '>=6'} dependencies: agentkeepalive: 4.5.0 diff --git a/src/core/providers/UiKitProvider/index.tsx b/src/core/providers/UiKitProvider/index.tsx index ca56d683e..3dad3d09c 100644 --- a/src/core/providers/UiKitProvider/index.tsx +++ b/src/core/providers/UiKitProvider/index.tsx @@ -46,6 +46,7 @@ interface UiKitProviderProps { onEditCommunity?: (communityId: string, options?: { tab?: string }) => void; onEditUser?: (userId: string) => void; onMessageUser?: (userId: string) => void; + onBack?: () => void; }; socialCommunityCreationButtonVisible?: boolean; onConnectionStatusChange?: (state: Amity.SessionStates) => void; @@ -98,20 +99,20 @@ const UiKitProvider = ({ setClient(ascClient); } -await ASCClient.login( - { userId, displayName, authToken }, - { - sessionWillRenewAccessToken(renewal) { - // secure mode - if (authToken) { - renewal.renewWithAuthToken(authToken); - return; - } + await ASCClient.login( + { userId, displayName, authToken }, + { + sessionWillRenewAccessToken(renewal) { + // secure mode + if (authToken) { + renewal.renewWithAuthToken(authToken); + return; + } - renewal.renew(); - }, + renewal.renew(); }, - ); + }, + ); setIsConnected(true); if (stateChangeRef.current == null) { diff --git a/src/social/hooks/useFeed.ts b/src/social/hooks/useFeed.ts index f58089db9..3a22d9e92 100644 --- a/src/social/hooks/useFeed.ts +++ b/src/social/hooks/useFeed.ts @@ -22,6 +22,11 @@ const useFeed = () => { useEffect(() => { fetchMore(); + + return () => { + setItems([]); + setQueryToken(null); + }; }, []); const prependItem = (post: Amity.Post) => { diff --git a/src/social/providers/NavigationProvider.tsx b/src/social/providers/NavigationProvider.tsx index 271a51e25..e2bf1d385 100644 --- a/src/social/providers/NavigationProvider.tsx +++ b/src/social/providers/NavigationProvider.tsx @@ -138,6 +138,7 @@ interface NavigationProviderProps { onEditCommunity?: (communityId: string, options?: { tab?: string }) => void; onEditUser?: (userId: string) => void; onMessageUser?: (userId: string) => void; + onBack?: () => void; } export default function NavigationProvider({ @@ -151,6 +152,7 @@ export default function NavigationProvider({ onEditCommunity, onEditUser, onMessageUser, + onBack, }: NavigationProviderProps) { const [pages, setPages] = useState([ { type: PageTypes.NewsFeed, communityId: undefined }, @@ -327,6 +329,13 @@ export default function NavigationProvider({ [onChangePage, onMessageUser], ); + const handleBack = useCallback(() => { + if (onBack) { + onBack(); + } + popPage(); + }, [onChangePage, onBack, popPage]); + const handleClickStory = useCallback( (targetId, storyType, targetIds) => { const next = { @@ -356,7 +365,7 @@ export default function NavigationProvider({ onEditCommunity: handleEditCommunity, onEditUser: handleEditUser, onMessageUser: handleMessageUser, - onBack: popPage, + onBack: handleBack, setNavigationBlocker, }} > From acd2075614cf4af9826ce598308a1b1f45fa4b4b Mon Sep 17 00:00:00 2001 From: Bonn Date: Mon, 29 Apr 2024 18:19:35 +0700 Subject: [PATCH 060/300] chore(sdk): ASC-00000 - custom build ci (#316) * chore: custom build ci * chore: remove unused libs * chore: fix ci * chore: fix ci * fix: revert ci * fix: remove pnpm cache step * fix: pnpm install step * fix: ci * fix: add NPM_TOKEN * fix: update .npmrc * fix: update pnpm-lock.yaml * fix: path * chore: add cache step --- .github/workflows/dev.yaml | 25 + .npmrc | 3 +- package.json | 3 - pnpm-lock.yaml | 4697 +++++++++-------- .../ChatContainer/ChatReadyState.tsx | 4 +- 5 files changed, 2441 insertions(+), 2291 deletions(-) diff --git a/.github/workflows/dev.yaml b/.github/workflows/dev.yaml index dc41ee555..a361edb96 100644 --- a/.github/workflows/dev.yaml +++ b/.github/workflows/dev.yaml @@ -3,6 +3,9 @@ name: Dev release pipeline on: pull_request: +env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + jobs: sync_dev: runs-on: ubuntu-latest @@ -53,6 +56,28 @@ jobs: - name: build storybook run: pnpm run storybook:build + - name: get version + id: version + run: echo "current_version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT + + - name: increase version (pre-release) + run: pnpm standard-version --release-as ${{ steps.version.outputs.current_version }} --prerelease alpha.$(git rev-parse --short HEAD) --skip.changelog --skip.commit --skip.tag + + - name: unpublish old version + if: "contains(github.event.pull_request.title, '(sdk):')" + continue-on-error: true + run: | + prev=$(npm view @amityco/ui-kit dist-tags.dev/${{ github.event.pull_request.number }}) + [[ ! -z $prev ]] && npm unpublish @amityco/ui-kit@$prev + + - name: build + run: pnpm run build + + - name: publish on npm with dev dist-tag + if: "contains(github.event.pull_request.title, '(sdk):')" + continue-on-error: true + run: npm publish --tag dev/${{ github.event.pull_request.number }} + - name: publish on s3 uses: jakejarvis/s3-sync-action@master with: diff --git a/.npmrc b/.npmrc index 1a03e44e8..bd3327ab5 100644 --- a/.npmrc +++ b/.npmrc @@ -1,2 +1 @@ -@fortawesome:registry=https://npm.fontawesome.com/ -//npm.fontawesome.com/:_authToken=${NPM_FONT_AWESOME_TOKEN} +//registry.npmjs.org/:_authToken=${NPM_TOKEN} \ No newline at end of file diff --git a/package.json b/package.json index d6cac5f43..df1e21b43 100644 --- a/package.json +++ b/package.json @@ -40,9 +40,6 @@ }, "devDependencies": { "@amityco/ts-sdk": "^6.23.0", - "@fortawesome/pro-light-svg-icons": "^5.15.4", - "@fortawesome/pro-regular-svg-icons": "^5.15.4", - "@fortawesome/pro-solid-svg-icons": "^5.15.4", "@storybook/addon-a11y": "^7.6.7", "@storybook/addon-actions": "^7.6.7", "@storybook/addon-backgrounds": "^7.6.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8f77de438..c8f28b455 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,37 +7,37 @@ settings: dependencies: '@fortawesome/fontawesome-svg-core': specifier: ^1.2.36 - version: 1.2.36 + version: 1.3.0 '@fortawesome/free-solid-svg-icons': specifier: ^5.15.4 version: 5.15.4 '@fortawesome/react-fontawesome': specifier: ^0.2.0 - version: 0.2.0(@fortawesome/fontawesome-svg-core@1.2.36)(react@18.2.0) + version: 0.2.0(@fortawesome/fontawesome-svg-core@1.3.0)(react@18.3.1) '@hookform/error-message': specifier: ^2.0.1 - version: 2.0.1(react-dom@18.2.0)(react-hook-form@7.49.2)(react@18.2.0) + version: 2.0.1(react-dom@18.3.1)(react-hook-form@7.51.3)(react@18.3.1) '@hookform/resolvers': specifier: ^3.3.4 - version: 3.3.4(react-hook-form@7.49.2) + version: 3.3.4(react-hook-form@7.51.3) '@tanstack/react-query': specifier: ^5.28.14 - version: 5.28.14(react@18.2.0) + version: 5.32.0(react@18.3.1) clsx: specifier: ^2.1.0 - version: 2.1.0 + version: 2.1.1 extract-colors: specifier: ^4.0.2 - version: 4.0.2 + version: 4.0.4 filesize: specifier: ^9.0.11 version: 9.0.11 hls.js: specifier: ^1.4.14 - version: 1.4.14 + version: 1.5.8 linkify-react: specifier: ^4.1.3 - version: 4.1.3(linkifyjs@4.1.3)(react@18.2.0) + version: 4.1.3(linkifyjs@4.1.3)(react@18.3.1) linkifyjs: specifier: ^4.1.3 version: 4.1.3 @@ -52,114 +52,105 @@ dependencies: version: 4.3.1 react-hook-form: specifier: ^7.49.2 - version: 7.49.2(react@18.2.0) + version: 7.51.3(react@18.3.1) react-infinite-scroll-component: specifier: ^6.1.0 - version: 6.1.0(react@18.2.0) + version: 6.1.0(react@18.3.1) react-insta-stories: specifier: ^2.6.2 - version: 2.6.2(react@18.2.0) + version: 2.6.2(react@18.3.1) react-intl: specifier: ^6.5.5 - version: 6.5.5(react@18.2.0)(typescript@4.9.5) + version: 6.6.5(react@18.3.1)(typescript@4.9.5) react-loading-skeleton: specifier: ^3.3.1 - version: 3.3.1(react@18.2.0) + version: 3.4.0(react@18.3.1) react-mentions: specifier: ^4.4.10 - version: 4.4.10(react-dom@18.2.0)(react@18.2.0) + version: 4.4.10(react-dom@18.3.1)(react@18.3.1) react-modal-sheet: specifier: ^2.2.0 - version: 2.2.0(framer-motion@10.18.0)(react@18.2.0) + version: 2.2.0(framer-motion@11.1.7)(react@18.3.1) react-sizeme: specifier: ^3.0.2 version: 3.0.2 react-textarea-autosize: specifier: ^8.5.3 - version: 8.5.3(@types/react@17.0.74)(react@18.2.0) + version: 8.5.3(@types/react@17.0.80)(react@18.3.1) react-timeago: specifier: ^7.2.0 - version: 7.2.0(react@18.2.0) + version: 7.2.0(react@18.3.1) react-tiny-popover: specifier: ^7.2.4 - version: 7.2.4(react-dom@18.2.0)(react@18.2.0) + version: 7.2.4(react-dom@18.3.1)(react@18.3.1) react-truncate-markup: specifier: ^5.1.2 - version: 5.1.2(react@18.2.0) + version: 5.1.2(react@18.3.1) react-use: specifier: ^17.4.2 - version: 17.4.2(react-dom@18.2.0)(react@18.2.0) + version: 17.5.0(react-dom@18.3.1)(react@18.3.1) stylis: specifier: ^4.3.1 - version: 4.3.1 + version: 4.3.2 uuid: specifier: ^8.3.2 version: 8.3.2 zod: specifier: ^3.22.4 - version: 3.22.4 + version: 3.23.4 devDependencies: '@amityco/ts-sdk': specifier: ^6.23.0 version: 6.23.0 - '@fortawesome/pro-light-svg-icons': - specifier: ^5.15.4 - version: 5.15.4 - '@fortawesome/pro-regular-svg-icons': - specifier: ^5.15.4 - version: 5.15.4 - '@fortawesome/pro-solid-svg-icons': - specifier: ^5.15.4 - version: 5.15.4 '@storybook/addon-a11y': specifier: ^7.6.7 - version: 7.6.7 + version: 7.6.18 '@storybook/addon-actions': specifier: ^7.6.7 - version: 7.6.7 + version: 7.6.18 '@storybook/addon-backgrounds': specifier: ^7.6.7 - version: 7.6.7 + version: 7.6.18 '@storybook/addon-controls': specifier: ^7.6.7 - version: 7.6.7(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + version: 7.6.18(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) '@storybook/addon-essentials': specifier: ^7.6.7 - version: 7.6.7(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + version: 7.6.18(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) '@storybook/addon-toolbars': specifier: ^7.6.7 - version: 7.6.7 + version: 7.6.18 '@storybook/addon-viewport': specifier: ^7.6.7 - version: 7.6.7 + version: 7.6.18 '@storybook/client-api': specifier: ^7.6.7 - version: 7.6.7 + version: 7.6.17 '@storybook/preview-api': specifier: ^7.6.7 - version: 7.6.7 + version: 7.6.18 '@storybook/react': specifier: ^7.6.7 - version: 7.6.7(react-dom@18.2.0)(react@18.2.0)(typescript@4.9.5) + version: 7.6.18(react-dom@18.3.1)(react@18.3.1)(typescript@4.9.5) '@storybook/react-vite': specifier: ^7.6.7 - version: 7.6.7(react-dom@18.2.0)(react@18.2.0)(typescript@4.9.5)(vite@4.5.1) + version: 7.6.18(react-dom@18.3.1)(react@18.3.1)(typescript@4.9.5)(vite@4.5.3) '@storybook/theming': specifier: ^7.6.7 - version: 7.6.7(react-dom@18.2.0)(react@18.2.0) + version: 7.6.18(react-dom@18.3.1)(react@18.3.1) '@types/jest': specifier: ^29.5.11 - version: 29.5.11 + version: 29.5.12 '@types/lodash': specifier: ^4.14.202 - version: 4.14.202 + version: 4.17.0 '@types/prop-types': specifier: ^15.7.11 - version: 15.7.11 + version: 15.7.12 '@types/react': specifier: ^17.0.74 - version: 17.0.74 + version: 17.0.80 '@types/react-helmet': specifier: ^6.1.11 version: 6.1.11 @@ -177,37 +168,37 @@ devDependencies: version: 5.1.34 '@types/uuid': specifier: ^9.0.7 - version: 9.0.7 + version: 9.0.8 '@typescript-eslint/eslint-plugin': specifier: ^6.18.0 - version: 6.18.0(@typescript-eslint/parser@6.18.0)(eslint@8.56.0)(typescript@4.9.5) + version: 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.0)(typescript@4.9.5) '@typescript-eslint/parser': specifier: ^6.18.0 - version: 6.18.0(eslint@8.56.0)(typescript@4.9.5) + version: 6.21.0(eslint@8.57.0)(typescript@4.9.5) '@vitejs/plugin-react': specifier: ^4.2.1 - version: 4.2.1(vite@4.5.1) + version: 4.2.1(vite@4.5.3) autoprefixer: specifier: ^10.4.16 - version: 10.4.16(postcss@8.4.33) + version: 10.4.19(postcss@8.4.38) esbuild-plugin-replace: specifier: ^1.4.0 version: 1.4.0 eslint: specifier: ^8.56.0 - version: 8.56.0 + version: 8.57.0 eslint-config-prettier: specifier: ^9.1.0 - version: 9.1.0(eslint@8.56.0) + version: 9.1.0(eslint@8.57.0) eslint-import-resolver-typescript: specifier: ^3.6.1 - version: 3.6.1(@typescript-eslint/parser@6.18.0)(eslint-plugin-import@2.29.1)(eslint@8.56.0) + version: 3.6.1(@typescript-eslint/parser@6.21.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0) eslint-plugin-import: specifier: ^2.29.1 - version: 2.29.1(@typescript-eslint/parser@6.18.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) + version: 2.29.1(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) eslint-plugin-jest: specifier: ^27.6.1 - version: 27.6.1(@typescript-eslint/eslint-plugin@6.18.0)(eslint@8.56.0)(jest@29.7.0)(typescript@4.9.5) + version: 27.9.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint@8.57.0)(jest@29.7.0)(typescript@4.9.5) husky: specifier: ^8.0.3 version: 8.0.3 @@ -219,37 +210,37 @@ devDependencies: version: 13.3.0 postcss: specifier: ^8.4.33 - version: 8.4.33 + version: 8.4.38 prettier: specifier: 2.4.0 version: 2.4.0 react: specifier: ^18.2.0 - version: 18.2.0 + version: 18.3.1 react-dom: specifier: ^18.2.0 - version: 18.2.0(react@18.2.0) + version: 18.3.1(react@18.3.1) react-router-dom: specifier: ^6.21.1 - version: 6.21.1(react-dom@18.2.0)(react@18.2.0) + version: 6.23.0(react-dom@18.3.1)(react@18.3.1) standard-version: specifier: ^9.5.0 version: 9.5.0 storybook: specifier: ^7.6.7 - version: 7.6.7 + version: 7.6.18 styled-components: specifier: ^6.1.6 - version: 6.1.6(react-dom@18.2.0)(react@18.2.0) + version: 6.1.8(react-dom@18.3.1)(react@18.3.1) svg-url-loader: specifier: ^7.1.1 - version: 7.1.1(webpack@5.89.0) + version: 7.1.1(webpack@5.91.0) ts-jest: specifier: ^29.1.1 - version: 29.1.1(@babel/core@7.23.7)(esbuild@0.19.11)(jest@29.7.0)(typescript@4.9.5) + version: 29.1.2(@babel/core@7.24.4)(esbuild@0.19.12)(jest@29.7.0)(typescript@4.9.5) tsup: specifier: ^7.3.0 - version: 7.3.0(postcss@8.4.33)(typescript@4.9.5) + version: 7.3.0(postcss@8.4.38)(typescript@4.9.5) typescript: specifier: ^4.9.5 version: 4.9.5 @@ -258,18 +249,13 @@ devDependencies: version: 5.1.0(typescript@4.9.5) vite: specifier: ^4.5.1 - version: 4.5.1 + version: 4.5.3 vite-tsconfig-paths: specifier: ^4.2.3 - version: 4.2.3(typescript@4.9.5)(vite@4.5.1) + version: 4.3.2(typescript@4.9.5)(vite@4.5.3) packages: - /@aashutoshrathi/word-wrap@1.2.6: - resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} - engines: {node: '>=0.10.0'} - dev: true - /@adobe/css-tools@4.3.3: resolution: {integrity: sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==} dev: true @@ -279,14 +265,14 @@ packages: engines: {node: '>=12', npm: '>=6'} dependencies: agentkeepalive: 4.5.0 - axios: 1.6.5(debug@4.3.4) + axios: 1.6.8(debug@4.3.4) debug: 4.3.4 - hls.js: 1.4.14 - js-base64: 3.7.5 + hls.js: 1.5.8 + js-base64: 3.7.7 mitt: 3.0.1 mqtt: 4.3.8 object-hash: 3.0.0 - react-native-uuid: 2.0.1 + react-native-uuid: 2.0.2 socket.io-client: 2.2.0 transitivePeerDependencies: - bufferutil @@ -294,12 +280,12 @@ packages: - utf-8-validate dev: true - /@ampproject/remapping@2.2.1: - resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} + /@ampproject/remapping@2.3.0: + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} dependencies: - '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.20 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 dev: true /@aw-web-design/x-default-browser@1.4.126: @@ -309,33 +295,33 @@ packages: default-browser-id: 3.0.0 dev: true - /@babel/code-frame@7.23.5: - resolution: {integrity: sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==} + /@babel/code-frame@7.24.2: + resolution: {integrity: sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/highlight': 7.23.4 - chalk: 2.4.2 + '@babel/highlight': 7.24.2 + picocolors: 1.0.0 dev: true - /@babel/compat-data@7.23.5: - resolution: {integrity: sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==} + /@babel/compat-data@7.24.4: + resolution: {integrity: sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==} engines: {node: '>=6.9.0'} dev: true - /@babel/core@7.23.7: - resolution: {integrity: sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw==} + /@babel/core@7.24.4: + resolution: {integrity: sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==} engines: {node: '>=6.9.0'} dependencies: - '@ampproject/remapping': 2.2.1 - '@babel/code-frame': 7.23.5 - '@babel/generator': 7.23.6 + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.24.2 + '@babel/generator': 7.24.4 '@babel/helper-compilation-targets': 7.23.6 - '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.7) - '@babel/helpers': 7.23.7 - '@babel/parser': 7.23.6 - '@babel/template': 7.22.15 - '@babel/traverse': 7.23.7 - '@babel/types': 7.23.6 + '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.4) + '@babel/helpers': 7.24.4 + '@babel/parser': 7.24.4 + '@babel/template': 7.24.0 + '@babel/traverse': 7.24.1 + '@babel/types': 7.24.0 convert-source-map: 2.0.0 debug: 4.3.4 gensync: 1.0.0-beta.2 @@ -345,13 +331,13 @@ packages: - supports-color dev: true - /@babel/generator@7.23.6: - resolution: {integrity: sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==} + /@babel/generator@7.24.4: + resolution: {integrity: sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.6 - '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.20 + '@babel/types': 7.24.0 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 jsesc: 2.5.2 dev: true @@ -359,65 +345,65 @@ packages: resolution: {integrity: sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.6 + '@babel/types': 7.24.0 dev: true /@babel/helper-builder-binary-assignment-operator-visitor@7.22.15: resolution: {integrity: sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.6 + '@babel/types': 7.24.0 dev: true /@babel/helper-compilation-targets@7.23.6: resolution: {integrity: sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/compat-data': 7.23.5 + '@babel/compat-data': 7.24.4 '@babel/helper-validator-option': 7.23.5 - browserslist: 4.22.2 + browserslist: 4.23.0 lru-cache: 5.1.1 semver: 6.3.1 dev: true - /@babel/helper-create-class-features-plugin@7.23.7(@babel/core@7.23.7): - resolution: {integrity: sha512-xCoqR/8+BoNnXOY7RVSgv6X+o7pmT5q1d+gGcRlXYkI+9B31glE4jeejhKVpA04O1AtzOt7OSQ6VYKP5FcRl9g==} + /@babel/helper-create-class-features-plugin@7.24.4(@babel/core@7.24.4): + resolution: {integrity: sha512-lG75yeuUSVu0pIcbhiYMXBXANHrpUPaOfu7ryAzskCgKUHuAxRQI5ssrtmF0X9UXldPlvT0XM/A4F44OXRt6iQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.23.7 + '@babel/core': 7.24.4 '@babel/helper-annotate-as-pure': 7.22.5 '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-function-name': 7.23.0 '@babel/helper-member-expression-to-functions': 7.23.0 '@babel/helper-optimise-call-expression': 7.22.5 - '@babel/helper-replace-supers': 7.22.20(@babel/core@7.23.7) + '@babel/helper-replace-supers': 7.24.1(@babel/core@7.24.4) '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 '@babel/helper-split-export-declaration': 7.22.6 semver: 6.3.1 dev: true - /@babel/helper-create-regexp-features-plugin@7.22.15(@babel/core@7.23.7): + /@babel/helper-create-regexp-features-plugin@7.22.15(@babel/core@7.24.4): resolution: {integrity: sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.23.7 + '@babel/core': 7.24.4 '@babel/helper-annotate-as-pure': 7.22.5 regexpu-core: 5.3.2 semver: 6.3.1 dev: true - /@babel/helper-define-polyfill-provider@0.4.4(@babel/core@7.23.7): - resolution: {integrity: sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA==} + /@babel/helper-define-polyfill-provider@0.6.2(@babel/core@7.24.4): + resolution: {integrity: sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 dependencies: - '@babel/core': 7.23.7 + '@babel/core': 7.24.4 '@babel/helper-compilation-targets': 7.23.6 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-plugin-utils': 7.24.0 debug: 4.3.4 lodash.debounce: 4.0.8 resolve: 1.22.8 @@ -434,40 +420,40 @@ packages: resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/template': 7.22.15 - '@babel/types': 7.23.6 + '@babel/template': 7.24.0 + '@babel/types': 7.24.0 dev: true /@babel/helper-hoist-variables@7.22.5: resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.6 + '@babel/types': 7.24.0 dev: true /@babel/helper-member-expression-to-functions@7.23.0: resolution: {integrity: sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.6 + '@babel/types': 7.24.0 dev: true - /@babel/helper-module-imports@7.22.15: - resolution: {integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==} + /@babel/helper-module-imports@7.24.3: + resolution: {integrity: sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.6 + '@babel/types': 7.24.0 dev: true - /@babel/helper-module-transforms@7.23.3(@babel/core@7.23.7): + /@babel/helper-module-transforms@7.23.3(@babel/core@7.24.4): resolution: {integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.23.7 + '@babel/core': 7.24.4 '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-module-imports': 7.22.15 + '@babel/helper-module-imports': 7.24.3 '@babel/helper-simple-access': 7.22.5 '@babel/helper-split-export-declaration': 7.22.6 '@babel/helper-validator-identifier': 7.22.20 @@ -477,33 +463,33 @@ packages: resolution: {integrity: sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.6 + '@babel/types': 7.24.0 dev: true - /@babel/helper-plugin-utils@7.22.5: - resolution: {integrity: sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==} + /@babel/helper-plugin-utils@7.24.0: + resolution: {integrity: sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==} engines: {node: '>=6.9.0'} dev: true - /@babel/helper-remap-async-to-generator@7.22.20(@babel/core@7.23.7): + /@babel/helper-remap-async-to-generator@7.22.20(@babel/core@7.24.4): resolution: {integrity: sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.23.7 + '@babel/core': 7.24.4 '@babel/helper-annotate-as-pure': 7.22.5 '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-wrap-function': 7.22.20 dev: true - /@babel/helper-replace-supers@7.22.20(@babel/core@7.23.7): - resolution: {integrity: sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==} + /@babel/helper-replace-supers@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.23.7 + '@babel/core': 7.24.4 '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-member-expression-to-functions': 7.23.0 '@babel/helper-optimise-call-expression': 7.22.5 @@ -513,25 +499,25 @@ packages: resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.6 + '@babel/types': 7.24.0 dev: true /@babel/helper-skip-transparent-expression-wrappers@7.22.5: resolution: {integrity: sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.6 + '@babel/types': 7.24.0 dev: true /@babel/helper-split-export-declaration@7.22.6: resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.6 + '@babel/types': 7.24.0 dev: true - /@babel/helper-string-parser@7.23.4: - resolution: {integrity: sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==} + /@babel/helper-string-parser@7.24.1: + resolution: {integrity: sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==} engines: {node: '>=6.9.0'} dev: true @@ -550,1000 +536,1011 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/helper-function-name': 7.23.0 - '@babel/template': 7.22.15 - '@babel/types': 7.23.6 + '@babel/template': 7.24.0 + '@babel/types': 7.24.0 dev: true - /@babel/helpers@7.23.7: - resolution: {integrity: sha512-6AMnjCoC8wjqBzDHkuqpa7jAKwvMo4dC+lr/TFBz+ucfulO1XMpDnwWPGBNwClOKZ8h6xn5N81W/R5OrcKtCbQ==} + /@babel/helpers@7.24.4: + resolution: {integrity: sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/template': 7.22.15 - '@babel/traverse': 7.23.7 - '@babel/types': 7.23.6 + '@babel/template': 7.24.0 + '@babel/traverse': 7.24.1 + '@babel/types': 7.24.0 transitivePeerDependencies: - supports-color dev: true - /@babel/highlight@7.23.4: - resolution: {integrity: sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==} + /@babel/highlight@7.24.2: + resolution: {integrity: sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==} engines: {node: '>=6.9.0'} dependencies: '@babel/helper-validator-identifier': 7.22.20 chalk: 2.4.2 js-tokens: 4.0.0 + picocolors: 1.0.0 dev: true - /@babel/parser@7.23.6: - resolution: {integrity: sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==} + /@babel/parser@7.24.4: + resolution: {integrity: sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==} engines: {node: '>=6.0.0'} hasBin: true dependencies: - '@babel/types': 7.23.6 + '@babel/types': 7.24.0 + dev: true + + /@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.24.4(@babel/core@7.24.4): + resolution: {integrity: sha512-qpl6vOOEEzTLLcsuqYYo8yDtrTocmu2xkGvgNebvPjT9DTtfFYGmgDqY+rBYXNlqL4s9qLDn6xkrJv4RxAPiTA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==} + /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-y4HqEnkelJIOQGd+3g1bTeKsA5c6qM7eOn7VggGVbBc0y8MLSKHacwcIE2PplNlQSj0PqS9rrXL/nkPVK+kUNg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==} + /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-Hj791Ii4ci8HqnaKHAlLNs+zaLXb0EzSDhiAWp5VNlyvCNymYfacs64pxTxbH1znW/NcArSmwpmG9IKE/TUVVQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.13.0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 - '@babel/plugin-transform-optional-chaining': 7.23.4(@babel/core@7.23.7) + '@babel/plugin-transform-optional-chaining': 7.24.1(@babel/core@7.24.4) dev: true - /@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.23.7(@babel/core@7.23.7): - resolution: {integrity: sha512-LlRT7HgaifEpQA1ZgLVOIJZZFVPWN5iReq/7/JixwBtwcoeVGDBD53ZV28rrsLYOZs1Y/EHhA8N/Z6aazHR8cw==} + /@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-m9m/fXsXLiHfwdgydIFnpk+7jlVbnvlK5B2EKiPdLUb6WX654ZaaEWJUjk8TftRbZpK0XibovlLWX4KIZhV6jw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.23.7 + '@babel/core': 7.24.4 '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.23.7): + /@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.24.4): resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 + '@babel/core': 7.24.4 dev: true - /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.23.7): + /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.24.4): resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.23.7): + /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.24.4): resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.23.7): + /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.24.4): resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.23.7): + /@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.24.4): resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.23.7): + /@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.24.4): resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.23.7): + /@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.24.4): resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-syntax-flow@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-YZiAIpkJAwQXBJLIQbRFayR5c+gJ35Vcz3bg954k7cd73zqjvhacJuL9RbrzPz8qPmZdgqP6EUKwy0PCNhaaPA==} + /@babel/plugin-syntax-flow@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-sxi2kLTI5DeW5vDtMUsk4mTPwvlUDbjOnoWayhynCwrw4QXRld4QEYwqzY8JmQXaJUtgUuCIurtSRH5sn4c7mA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-syntax-import-assertions@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==} + /@babel/plugin-syntax-import-assertions@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-IuwnI5XnuF189t91XbxmXeCDz3qs6iDRO7GJ++wcfgeXNs/8FmIlKcpDSXNVyuLQxlwvskmI3Ct73wUODkJBlQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-syntax-import-attributes@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==} + /@babel/plugin-syntax-import-attributes@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-zhQTMH0X2nVLnb04tz+s7AMuasX8U0FnpE+nHTOhSOINjWMnopoZTxtIKsd45n4GQ/HIZLyfIpoul8e2m0DnRA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.23.7): + /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.24.4): resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.23.7): + /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.24.4): resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-syntax-jsx@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==} + /@babel/plugin-syntax-jsx@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.23.7): + /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.24.4): resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.23.7): + /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.24.4): resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.23.7): + /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.24.4): resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.23.7): + /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.24.4): resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.23.7): + /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.24.4): resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.23.7): + /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.24.4): resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.23.7): + /@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.24.4): resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.23.7): + /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.24.4): resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-syntax-typescript@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==} + /@babel/plugin-syntax-typescript@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.23.7): + /@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.24.4): resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.7) - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.4) + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-transform-arrow-functions@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==} + /@babel/plugin-transform-arrow-functions@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-ngT/3NkRhsaep9ck9uj2Xhv9+xB1zShY3tM3g6om4xxCELwCDN4g4Aq5dRn48+0hasAql7s2hdBOysCfNpr4fw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-transform-async-generator-functions@7.23.7(@babel/core@7.23.7): - resolution: {integrity: sha512-PdxEpL71bJp1byMG0va5gwQcXHxuEYC/BgI/e88mGTtohbZN28O5Yit0Plkkm/dBzCF/BxmbNcses1RH1T+urA==} + /@babel/plugin-transform-async-generator-functions@7.24.3(@babel/core@7.24.4): + resolution: {integrity: sha512-Qe26CMYVjpQxJ8zxM1340JFNjZaF+ISWpr1Kt/jGo+ZTUzKkfw/pphEWbRCb+lmSM6k/TOgfYLvmbHkUQ0asIg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 + '@babel/core': 7.24.4 '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.23.7) - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.23.7) + '@babel/helper-plugin-utils': 7.24.0 + '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.24.4) + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.4) dev: true - /@babel/plugin-transform-async-to-generator@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==} + /@babel/plugin-transform-async-to-generator@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-AawPptitRXp1y0n4ilKcGbRYWfbbzFWz2NqNu7dacYDtFtz0CMjG64b3LQsb3KIgnf4/obcUL78hfaOS7iCUfw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-module-imports': 7.22.15 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.23.7) + '@babel/core': 7.24.4 + '@babel/helper-module-imports': 7.24.3 + '@babel/helper-plugin-utils': 7.24.0 + '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.24.4) dev: true - /@babel/plugin-transform-block-scoped-functions@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==} + /@babel/plugin-transform-block-scoped-functions@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-TWWC18OShZutrv9C6mye1xwtam+uNi2bnTOCBUd5sZxyHOiWbU6ztSROofIMrK84uweEZC219POICK/sTYwfgg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-transform-block-scoping@7.23.4(@babel/core@7.23.7): - resolution: {integrity: sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==} + /@babel/plugin-transform-block-scoping@7.24.4(@babel/core@7.24.4): + resolution: {integrity: sha512-nIFUZIpGKDf9O9ttyRXpHFpKC+X3Y5mtshZONuEUYBomAKoM4y029Jr+uB1bHGPhNmK8YXHevDtKDOLmtRrp6g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-transform-class-properties@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==} + /@babel/plugin-transform-class-properties@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-OMLCXi0NqvJfORTaPQBwqLXHhb93wkBKZ4aNwMl6WtehO7ar+cmp+89iPEQPqxAnxsOKTaMcs3POz3rKayJ72g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-create-class-features-plugin': 7.23.7(@babel/core@7.23.7) - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-create-class-features-plugin': 7.24.4(@babel/core@7.24.4) + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-transform-class-static-block@7.23.4(@babel/core@7.23.7): - resolution: {integrity: sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ==} + /@babel/plugin-transform-class-static-block@7.24.4(@babel/core@7.24.4): + resolution: {integrity: sha512-B8q7Pz870Hz/q9UgP8InNpY01CSLDSCyqX7zcRuv3FcPl87A2G17lASroHWaCtbdIcbYzOZ7kWmXFKbijMSmFg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.12.0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-create-class-features-plugin': 7.23.7(@babel/core@7.23.7) - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.23.7) + '@babel/core': 7.24.4 + '@babel/helper-create-class-features-plugin': 7.24.4(@babel/core@7.24.4) + '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.24.4) dev: true - /@babel/plugin-transform-classes@7.23.5(@babel/core@7.23.7): - resolution: {integrity: sha512-jvOTR4nicqYC9yzOHIhXG5emiFEOpappSJAl73SDSEDcybD+Puuze8Tnpb9p9qEyYup24tq891gkaygIFvWDqg==} + /@babel/plugin-transform-classes@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-ZTIe3W7UejJd3/3R4p7ScyyOoafetUShSf4kCqV0O7F/RiHxVj/wRaRnQlrGwflvcehNA8M42HkAiEDYZu2F1Q==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 + '@babel/core': 7.24.4 '@babel/helper-annotate-as-pure': 7.22.5 '@babel/helper-compilation-targets': 7.23.6 '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-function-name': 7.23.0 - '@babel/helper-optimise-call-expression': 7.22.5 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-replace-supers': 7.22.20(@babel/core@7.23.7) + '@babel/helper-plugin-utils': 7.24.0 + '@babel/helper-replace-supers': 7.24.1(@babel/core@7.24.4) '@babel/helper-split-export-declaration': 7.22.6 globals: 11.12.0 dev: true - /@babel/plugin-transform-computed-properties@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==} + /@babel/plugin-transform-computed-properties@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-5pJGVIUfJpOS+pAqBQd+QMaTD2vCL/HcePooON6pDpHgRp4gNRmzyHTPIkXntwKsq3ayUFVfJaIKPw2pOkOcTw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/template': 7.22.15 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 + '@babel/template': 7.24.0 dev: true - /@babel/plugin-transform-destructuring@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==} + /@babel/plugin-transform-destructuring@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-ow8jciWqNxR3RYbSNVuF4U2Jx130nwnBnhRw6N6h1bOejNkABmcI5X5oz29K4alWX7vf1C+o6gtKXikzRKkVdw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-transform-dotall-regex@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==} + /@babel/plugin-transform-dotall-regex@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-p7uUxgSoZwZ2lPNMzUkqCts3xlp8n+o05ikjy7gbtFJSt9gdU88jAmtfmOxHM14noQXBxfgzf2yRWECiNVhTCw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.7) - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.4) + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-transform-duplicate-keys@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==} + /@babel/plugin-transform-duplicate-keys@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-msyzuUnvsjsaSaocV6L7ErfNsa5nDWL1XKNnDePLgmz+WdU4w/J8+AxBMrWfi9m4IxfL5sZQKUPQKDQeeAT6lA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-transform-dynamic-import@7.23.4(@babel/core@7.23.7): - resolution: {integrity: sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ==} + /@babel/plugin-transform-dynamic-import@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-av2gdSTyXcJVdI+8aFZsCAtR29xJt0S5tas+Ef8NvBNmD1a+N/3ecMLeMBgfcK+xzsjdLDT6oHt+DFPyeqUbDA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.23.7) + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.24.4) dev: true - /@babel/plugin-transform-exponentiation-operator@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==} + /@babel/plugin-transform-exponentiation-operator@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-U1yX13dVBSwS23DEAqU+Z/PkwE9/m7QQy8Y9/+Tdb8UWYaGNDYwTLi19wqIAiROr8sXVum9A/rtiH5H0boUcTw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 + '@babel/core': 7.24.4 '@babel/helper-builder-binary-assignment-operator-visitor': 7.22.15 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-transform-export-namespace-from@7.23.4(@babel/core@7.23.7): - resolution: {integrity: sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ==} + /@babel/plugin-transform-export-namespace-from@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-Ft38m/KFOyzKw2UaJFkWG9QnHPG/Q/2SkOrRk4pNBPg5IPZ+dOxcmkK5IyuBcxiNPyyYowPGUReyBvrvZs7IlQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.23.7) + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.24.4) dev: true - /@babel/plugin-transform-flow-strip-types@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-26/pQTf9nQSNVJCrLB1IkHUKyPxR+lMrH2QDPG89+Znu9rAMbtrybdbWeE9bb7gzjmE5iXHEY+e0HUwM6Co93Q==} + /@babel/plugin-transform-flow-strip-types@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-iIYPIWt3dUmUKKE10s3W+jsQ3icFkw0JyRVyY1B7G4yK/nngAOHLVx8xlhA6b/Jzl/Y0nis8gjqhqKtRDQqHWQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-flow': 7.23.3(@babel/core@7.23.7) + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-flow': 7.24.1(@babel/core@7.24.4) dev: true - /@babel/plugin-transform-for-of@7.23.6(@babel/core@7.23.7): - resolution: {integrity: sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw==} + /@babel/plugin-transform-for-of@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-OxBdcnF04bpdQdR3i4giHZNZQn7cm8RQKcSwA17wAAqEELo1ZOwp5FFgeptWUQXFyT9kwHo10aqqauYkRZPCAg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 dev: true - /@babel/plugin-transform-function-name@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==} + /@babel/plugin-transform-function-name@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-BXmDZpPlh7jwicKArQASrj8n22/w6iymRnvHYYd2zO30DbE277JO20/7yXJT3QxDPtiQiOxQBbZH4TpivNXIxA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 + '@babel/core': 7.24.4 '@babel/helper-compilation-targets': 7.23.6 '@babel/helper-function-name': 7.23.0 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-transform-json-strings@7.23.4(@babel/core@7.23.7): - resolution: {integrity: sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg==} + /@babel/plugin-transform-json-strings@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-U7RMFmRvoasscrIFy5xA4gIp8iWnWubnKkKuUGJjsuOH7GfbMkB+XZzeslx2kLdEGdOJDamEmCqOks6e8nv8DQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.23.7) + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.4) dev: true - /@babel/plugin-transform-literals@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==} + /@babel/plugin-transform-literals@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-zn9pwz8U7nCqOYIiBaOxoQOtYmMODXTJnkxG4AtX8fPmnCRYWBOHD0qcpwS9e2VDSp1zNJYpdnFMIKb8jmwu6g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-transform-logical-assignment-operators@7.23.4(@babel/core@7.23.7): - resolution: {integrity: sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg==} + /@babel/plugin-transform-logical-assignment-operators@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-OhN6J4Bpz+hIBqItTeWJujDOfNP+unqv/NJgyhlpSqgBTPm37KkMmZV6SYcOj+pnDbdcl1qRGV/ZiIjX9Iy34w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.23.7) + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.4) dev: true - /@babel/plugin-transform-member-expression-literals@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==} + /@babel/plugin-transform-member-expression-literals@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-4ojai0KysTWXzHseJKa1XPNXKRbuUrhkOPY4rEGeR+7ChlJVKxFa3H3Bz+7tWaGKgJAXUWKOGmltN+u9B3+CVg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-transform-modules-amd@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==} + /@babel/plugin-transform-modules-amd@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-lAxNHi4HVtjnHd5Rxg3D5t99Xm6H7b04hUS7EHIXcUl2EV4yl1gWdqZrNzXnSrHveL9qMdbODlLF55mvgjAfaQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.7) - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.4) + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-transform-modules-commonjs@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==} + /@babel/plugin-transform-modules-commonjs@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.7) - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.4) + '@babel/helper-plugin-utils': 7.24.0 '@babel/helper-simple-access': 7.22.5 dev: true - /@babel/plugin-transform-modules-systemjs@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-ZxyKGTkF9xT9YJuKQRo19ewf3pXpopuYQd8cDXqNzc3mUNbOME0RKMoZxviQk74hwzfQsEe66dE92MaZbdHKNQ==} + /@babel/plugin-transform-modules-systemjs@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-mqQ3Zh9vFO1Tpmlt8QPnbwGHzNz3lpNEMxQb1kAemn/erstyqw1r9KeOlOfo3y6xAnFEcOv2tSyrXfmMk+/YZA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 + '@babel/core': 7.24.4 '@babel/helper-hoist-variables': 7.22.5 - '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.7) - '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.4) + '@babel/helper-plugin-utils': 7.24.0 '@babel/helper-validator-identifier': 7.22.20 dev: true - /@babel/plugin-transform-modules-umd@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==} + /@babel/plugin-transform-modules-umd@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-tuA3lpPj+5ITfcCluy6nWonSL7RvaG0AOTeAuvXqEKS34lnLzXpDb0dcP6K8jD0zWZFNDVly90AGFJPnm4fOYg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.7) - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.4) + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-transform-named-capturing-groups-regex@7.22.5(@babel/core@7.23.7): + /@babel/plugin-transform-named-capturing-groups-regex@7.22.5(@babel/core@7.24.4): resolution: {integrity: sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.7) - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.4) + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-transform-new-target@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==} + /@babel/plugin-transform-new-target@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-/rurytBM34hYy0HKZQyA0nHbQgQNFm4Q/BOc9Hflxi2X3twRof7NaE5W46j4kQitm7SvACVRXsa6N/tSZxvPug==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-transform-nullish-coalescing-operator@7.23.4(@babel/core@7.23.7): - resolution: {integrity: sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==} + /@babel/plugin-transform-nullish-coalescing-operator@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-iQ+caew8wRrhCikO5DrUYx0mrmdhkaELgFa+7baMcVuhxIkN7oxt06CZ51D65ugIb1UWRQ8oQe+HXAVM6qHFjw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.23.7) + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.4) dev: true - /@babel/plugin-transform-numeric-separator@7.23.4(@babel/core@7.23.7): - resolution: {integrity: sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q==} + /@babel/plugin-transform-numeric-separator@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-7GAsGlK4cNL2OExJH1DzmDeKnRv/LXq0eLUSvudrehVA5Rgg4bIrqEUW29FbKMBRT0ztSqisv7kjP+XIC4ZMNw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.23.7) + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.4) dev: true - /@babel/plugin-transform-object-rest-spread@7.23.4(@babel/core@7.23.7): - resolution: {integrity: sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==} + /@babel/plugin-transform-object-rest-spread@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-XjD5f0YqOtebto4HGISLNfiNMTTs6tbkFf2TOqJlYKYmbo+mN9Dnpl4SRoofiziuOWMIyq3sZEUqLo3hLITFEA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/compat-data': 7.23.5 - '@babel/core': 7.23.7 + '@babel/core': 7.24.4 '@babel/helper-compilation-targets': 7.23.6 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.23.7) - '@babel/plugin-transform-parameters': 7.23.3(@babel/core@7.23.7) + '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-transform-parameters': 7.24.1(@babel/core@7.24.4) dev: true - /@babel/plugin-transform-object-super@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==} + /@babel/plugin-transform-object-super@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-oKJqR3TeI5hSLRxudMjFQ9re9fBVUU0GICqM3J1mi8MqlhVr6hC/ZN4ttAyMuQR6EZZIY6h/exe5swqGNNIkWQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-replace-supers': 7.22.20(@babel/core@7.23.7) + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 + '@babel/helper-replace-supers': 7.24.1(@babel/core@7.24.4) dev: true - /@babel/plugin-transform-optional-catch-binding@7.23.4(@babel/core@7.23.7): - resolution: {integrity: sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A==} + /@babel/plugin-transform-optional-catch-binding@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-oBTH7oURV4Y+3EUrf6cWn1OHio3qG/PVwO5J03iSJmBg6m2EhKjkAu/xuaXaYwWW9miYtvbWv4LNf0AmR43LUA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.23.7) + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.4) dev: true - /@babel/plugin-transform-optional-chaining@7.23.4(@babel/core@7.23.7): - resolution: {integrity: sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==} + /@babel/plugin-transform-optional-chaining@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-n03wmDt+987qXwAgcBlnUUivrZBPZ8z1plL0YvgQalLm+ZE5BMhGm94jhxXtA1wzv1Cu2aaOv1BM9vbVttrzSg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.23.7) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.4) dev: true - /@babel/plugin-transform-parameters@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==} + /@babel/plugin-transform-parameters@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-8Jl6V24g+Uw5OGPeWNKrKqXPDw2YDjLc53ojwfMcKwlEoETKU9rU0mHUtcg9JntWI/QYzGAXNWEcVHZ+fR+XXg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-transform-private-methods@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==} + /@babel/plugin-transform-private-methods@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-tGvisebwBO5em4PaYNqt4fkw56K2VALsAbAakY0FjTYqJp7gfdrgr7YX76Or8/cpik0W6+tj3rZ0uHU9Oil4tw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-create-class-features-plugin': 7.23.7(@babel/core@7.23.7) - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-create-class-features-plugin': 7.24.4(@babel/core@7.24.4) + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-transform-private-property-in-object@7.23.4(@babel/core@7.23.7): - resolution: {integrity: sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A==} + /@babel/plugin-transform-private-property-in-object@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-pTHxDVa0BpUbvAgX3Gat+7cSciXqUcY9j2VZKTbSB6+VQGpNgNO9ailxTGHSXlqOnX1Hcx1Enme2+yv7VqP9bg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 + '@babel/core': 7.24.4 '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-create-class-features-plugin': 7.23.7(@babel/core@7.23.7) - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.23.7) + '@babel/helper-create-class-features-plugin': 7.24.4(@babel/core@7.24.4) + '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.24.4) dev: true - /@babel/plugin-transform-property-literals@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==} + /@babel/plugin-transform-property-literals@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-LetvD7CrHmEx0G442gOomRr66d7q8HzzGGr4PMHGr+5YIm6++Yke+jxj246rpvsbyhJwCLxcTn6zW1P1BSenqA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-transform-react-jsx-self@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-qXRvbeKDSfwnlJnanVRp0SfuWE5DQhwQr5xtLBzp56Wabyo+4CMosF6Kfp+eOD/4FYpql64XVJ2W0pVLlJZxOQ==} + /@babel/plugin-transform-react-jsx-self@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-kDJgnPujTmAZ/9q2CN4m2/lRsUUPDvsG3+tSHWUJIzMGTt5U/b/fwWd3RO3n+5mjLrsBrVa5eKFRVSQbi3dF1w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-transform-react-jsx-source@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-91RS0MDnAWDNvGC6Wio5XYkyWI39FMFO+JK9+4AlgaTH+yWwVTsw7/sn6LK0lH7c5F+TFkpv/3LfCJ1Ydwof/g==} + /@babel/plugin-transform-react-jsx-source@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-1v202n7aUq4uXAieRTKcwPzNyphlCuqHHDcdSNc+vdhoTEZcFMh+L5yZuCmGaIO7bs1nJUNfHB89TZyoL48xNA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-transform-regenerator@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==} + /@babel/plugin-transform-regenerator@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-sJwZBCzIBE4t+5Q4IGLaaun5ExVMRY0lYwos/jNecjMrVCygCdph3IKv0tkP5Fc87e/1+bebAmEAGBfnRD+cnw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 regenerator-transform: 0.15.2 dev: true - /@babel/plugin-transform-reserved-words@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==} + /@babel/plugin-transform-reserved-words@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-JAclqStUfIwKN15HrsQADFgeZt+wexNQ0uLhuqvqAUFoqPMjEcFCYZBhq0LUdz6dZK/mD+rErhW71fbx8RYElg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-transform-shorthand-properties@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==} + /@babel/plugin-transform-shorthand-properties@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-LyjVB1nsJ6gTTUKRjRWx9C1s9hE7dLfP/knKdrfeH9UPtAGjYGgxIbFfx7xyLIEWs7Xe1Gnf8EWiUqfjLhInZA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-transform-spread@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==} + /@babel/plugin-transform-spread@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-KjmcIM+fxgY+KxPVbjelJC6hrH1CgtPmTvdXAfn3/a9CnWGSTY7nH4zm5+cjmWJybdcPSsD0++QssDsjcpe47g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 dev: true - /@babel/plugin-transform-sticky-regex@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==} + /@babel/plugin-transform-sticky-regex@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-9v0f1bRXgPVcPrngOQvLXeGNNVLc8UjMVfebo9ka0WF3/7+aVUHmaJVT3sa0XCzEFioPfPHZiOcYG9qOsH63cw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-transform-template-literals@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==} + /@babel/plugin-transform-template-literals@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-WRkhROsNzriarqECASCNu/nojeXCDTE/F2HmRgOzi7NGvyfYGq1NEjKBK3ckLfRgGc6/lPAqP0vDOSw3YtG34g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-transform-typeof-symbol@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==} + /@babel/plugin-transform-typeof-symbol@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-CBfU4l/A+KruSUoW+vTQthwcAdwuqbpRNB8HQKlZABwHRhsdHZ9fezp4Sn18PeAlYxTNiLMlx4xUBV3AWfg1BA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-transform-typescript@7.23.6(@babel/core@7.23.7): - resolution: {integrity: sha512-6cBG5mBvUu4VUD04OHKnYzbuHNP8huDsD3EDqqpIpsswTDoqHCjLoHb6+QgsV1WsT2nipRqCPgxD3LXnEO7XfA==} + /@babel/plugin-transform-typescript@7.24.4(@babel/core@7.24.4): + resolution: {integrity: sha512-79t3CQ8+oBGk/80SQ8MN3Bs3obf83zJ0YZjDmDaEZN8MqhMI760apl5z6a20kFeMXBwJX99VpKT8CKxEBp5H1g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 + '@babel/core': 7.24.4 '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-create-class-features-plugin': 7.23.7(@babel/core@7.23.7) - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-typescript': 7.23.3(@babel/core@7.23.7) + '@babel/helper-create-class-features-plugin': 7.24.4(@babel/core@7.24.4) + '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-typescript': 7.24.1(@babel/core@7.24.4) dev: true - /@babel/plugin-transform-unicode-escapes@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==} + /@babel/plugin-transform-unicode-escapes@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-RlkVIcWT4TLI96zM660S877E7beKlQw7Ig+wqkKBiWfj0zH5Q4h50q6er4wzZKRNSYpfo6ILJ+hrJAGSX2qcNw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-transform-unicode-property-regex@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==} + /@babel/plugin-transform-unicode-property-regex@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-Ss4VvlfYV5huWApFsF8/Sq0oXnGO+jB+rijFEFugTd3cwSObUSnUi88djgR5528Csl0uKlrI331kRqe56Ov2Ng==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.7) - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.4) + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-transform-unicode-regex@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==} + /@babel/plugin-transform-unicode-regex@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-2A/94wgZgxfTsiLaQ2E36XAOdcZmGAaEEgVmxQWwZXWkGhvoHbaqXcKnU8zny4ycpu3vNqg0L/PcCiYtHtA13g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.7) - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.4) + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/plugin-transform-unicode-sets-regex@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==} + /@babel/plugin-transform-unicode-sets-regex@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-fqj4WuzzS+ukpgerpAoOnMfQXwUHFxXUZUE84oL2Kao2N8uSlvcpnAidKASgsNgzZHBsHWvcm8s9FPWUhAb8fA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.7) - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.4) + '@babel/helper-plugin-utils': 7.24.0 dev: true - /@babel/preset-env@7.23.7(@babel/core@7.23.7): - resolution: {integrity: sha512-SY27X/GtTz/L4UryMNJ6p4fH4nsgWbz84y9FE0bQeWJP6O5BhgVCt53CotQKHCOeXJel8VyhlhujhlltKms/CA==} + /@babel/preset-env@7.24.4(@babel/core@7.24.4): + resolution: {integrity: sha512-7Kl6cSmYkak0FK/FXjSEnLJ1N9T/WA2RkMhu17gZ/dsxKJUuTYNIylahPTzqpLyJN4WhDif8X0XK1R8Wsguo/A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/compat-data': 7.23.5 - '@babel/core': 7.23.7 + '@babel/compat-data': 7.24.4 + '@babel/core': 7.24.4 '@babel/helper-compilation-targets': 7.23.6 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-plugin-utils': 7.24.0 '@babel/helper-validator-option': 7.23.5 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.23.7(@babel/core@7.23.7) - '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.23.7) - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.23.7) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.23.7) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.23.7) - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.23.7) - '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.23.7) - '@babel/plugin-syntax-import-assertions': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-syntax-import-attributes': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.23.7) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.23.7) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.23.7) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.23.7) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.23.7) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.23.7) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.23.7) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.23.7) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.23.7) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.23.7) - '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.23.7) - '@babel/plugin-transform-arrow-functions': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-async-generator-functions': 7.23.7(@babel/core@7.23.7) - '@babel/plugin-transform-async-to-generator': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-block-scoped-functions': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-block-scoping': 7.23.4(@babel/core@7.23.7) - '@babel/plugin-transform-class-properties': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-class-static-block': 7.23.4(@babel/core@7.23.7) - '@babel/plugin-transform-classes': 7.23.5(@babel/core@7.23.7) - '@babel/plugin-transform-computed-properties': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-destructuring': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-dotall-regex': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-duplicate-keys': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-dynamic-import': 7.23.4(@babel/core@7.23.7) - '@babel/plugin-transform-exponentiation-operator': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-export-namespace-from': 7.23.4(@babel/core@7.23.7) - '@babel/plugin-transform-for-of': 7.23.6(@babel/core@7.23.7) - '@babel/plugin-transform-function-name': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-json-strings': 7.23.4(@babel/core@7.23.7) - '@babel/plugin-transform-literals': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-logical-assignment-operators': 7.23.4(@babel/core@7.23.7) - '@babel/plugin-transform-member-expression-literals': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-modules-amd': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-modules-commonjs': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-modules-systemjs': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-modules-umd': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-named-capturing-groups-regex': 7.22.5(@babel/core@7.23.7) - '@babel/plugin-transform-new-target': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-nullish-coalescing-operator': 7.23.4(@babel/core@7.23.7) - '@babel/plugin-transform-numeric-separator': 7.23.4(@babel/core@7.23.7) - '@babel/plugin-transform-object-rest-spread': 7.23.4(@babel/core@7.23.7) - '@babel/plugin-transform-object-super': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-optional-catch-binding': 7.23.4(@babel/core@7.23.7) - '@babel/plugin-transform-optional-chaining': 7.23.4(@babel/core@7.23.7) - '@babel/plugin-transform-parameters': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-private-methods': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-private-property-in-object': 7.23.4(@babel/core@7.23.7) - '@babel/plugin-transform-property-literals': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-regenerator': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-reserved-words': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-shorthand-properties': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-spread': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-sticky-regex': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-template-literals': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-typeof-symbol': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-unicode-escapes': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-unicode-property-regex': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-unicode-regex': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-unicode-sets-regex': 7.23.3(@babel/core@7.23.7) - '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.23.7) - babel-plugin-polyfill-corejs2: 0.4.7(@babel/core@7.23.7) - babel-plugin-polyfill-corejs3: 0.8.7(@babel/core@7.23.7) - babel-plugin-polyfill-regenerator: 0.5.4(@babel/core@7.23.7) - core-js-compat: 3.35.0 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.24.4(@babel/core@7.24.4) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.24.4) + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.4) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.24.4) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.24.4) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-syntax-import-assertions': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-syntax-import-attributes': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.24.4) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.4) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.4) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.24.4) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.24.4) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.24.4) + '@babel/plugin-transform-arrow-functions': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-async-generator-functions': 7.24.3(@babel/core@7.24.4) + '@babel/plugin-transform-async-to-generator': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-block-scoped-functions': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-block-scoping': 7.24.4(@babel/core@7.24.4) + '@babel/plugin-transform-class-properties': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-class-static-block': 7.24.4(@babel/core@7.24.4) + '@babel/plugin-transform-classes': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-computed-properties': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-destructuring': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-dotall-regex': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-duplicate-keys': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-dynamic-import': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-exponentiation-operator': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-export-namespace-from': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-for-of': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-function-name': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-json-strings': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-literals': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-logical-assignment-operators': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-member-expression-literals': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-modules-amd': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-modules-commonjs': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-modules-systemjs': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-modules-umd': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-named-capturing-groups-regex': 7.22.5(@babel/core@7.24.4) + '@babel/plugin-transform-new-target': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-nullish-coalescing-operator': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-numeric-separator': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-object-rest-spread': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-object-super': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-optional-catch-binding': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-optional-chaining': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-parameters': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-private-methods': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-private-property-in-object': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-property-literals': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-regenerator': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-reserved-words': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-shorthand-properties': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-spread': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-sticky-regex': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-template-literals': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-typeof-symbol': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-unicode-escapes': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-unicode-property-regex': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-unicode-regex': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-unicode-sets-regex': 7.24.1(@babel/core@7.24.4) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.24.4) + babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.24.4) + babel-plugin-polyfill-corejs3: 0.10.4(@babel/core@7.24.4) + babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.24.4) + core-js-compat: 3.37.0 semver: 6.3.1 transitivePeerDependencies: - supports-color dev: true - /@babel/preset-flow@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-7yn6hl8RIv+KNk6iIrGZ+D06VhVY35wLVf23Cz/mMu1zOr7u4MMP4j0nZ9tLf8+4ZFpnib8cFYgB/oYg9hfswA==} + /@babel/preset-flow@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-sWCV2G9pcqZf+JHyv/RyqEIpFypxdCSxWIxQjpdaQxenNog7cN1pr76hg8u0Fz8Qgg0H4ETkGcJnXL8d4j0PPA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 '@babel/helper-validator-option': 7.23.5 - '@babel/plugin-transform-flow-strip-types': 7.23.3(@babel/core@7.23.7) + '@babel/plugin-transform-flow-strip-types': 7.24.1(@babel/core@7.24.4) dev: true - /@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.23.7): + /@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.24.4): resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} peerDependencies: '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/types': 7.23.6 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 + '@babel/types': 7.24.0 esutils: 2.0.3 dev: true - /@babel/preset-typescript@7.23.3(@babel/core@7.23.7): - resolution: {integrity: sha512-17oIGVlqz6CchO9RFYn5U6ZpWRZIngayYCtrPRSgANSwC2V1Jb+iP74nVxzzXJte8b8BYxrL1yY96xfhTBrNNQ==} + /@babel/preset-typescript@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-1DBaMmRDpuYQBPWD8Pf/WEwCrtgRHxsZnP4mIy9G/X+hFfbI47Q2G4t1Paakld84+qsk2fSsUPMKg71jkoOOaQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-plugin-utils': 7.22.5 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.0 '@babel/helper-validator-option': 7.23.5 - '@babel/plugin-syntax-jsx': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-modules-commonjs': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-typescript': 7.23.6(@babel/core@7.23.7) + '@babel/plugin-syntax-jsx': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-modules-commonjs': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-typescript': 7.24.4(@babel/core@7.24.4) dev: true - /@babel/register@7.23.7(@babel/core@7.23.7): + /@babel/register@7.23.7(@babel/core@7.24.4): resolution: {integrity: sha512-EjJeB6+kvpk+Y5DAkEAmbOBEFkh9OASx0huoEkqYTFxAZHzOAX2Oh5uwAUuL2rUddqfM0SA+KPXV2TbzoZ2kvQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 + '@babel/core': 7.24.4 clone-deep: 4.0.1 find-cache-dir: 2.1.0 make-dir: 2.1.0 @@ -1555,8 +1552,8 @@ packages: resolution: {integrity: sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==} dev: true - /@babel/runtime@7.23.7: - resolution: {integrity: sha512-w06OXVOFso7LcbzMiDGt+3X7Rh7Ho8MmgPoWU3rarH+8upf+wSU/grlGbWzQyr3DkdN6ZeuMFjpdwW0Q+HxobA==} + /@babel/runtime@7.24.4: + resolution: {integrity: sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==} engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.14.1 @@ -1567,38 +1564,38 @@ packages: regenerator-runtime: 0.13.11 dev: false - /@babel/template@7.22.15: - resolution: {integrity: sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==} + /@babel/template@7.24.0: + resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/code-frame': 7.23.5 - '@babel/parser': 7.23.6 - '@babel/types': 7.23.6 + '@babel/code-frame': 7.24.2 + '@babel/parser': 7.24.4 + '@babel/types': 7.24.0 dev: true - /@babel/traverse@7.23.7: - resolution: {integrity: sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==} + /@babel/traverse@7.24.1: + resolution: {integrity: sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/code-frame': 7.23.5 - '@babel/generator': 7.23.6 + '@babel/code-frame': 7.24.2 + '@babel/generator': 7.24.4 '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-function-name': 7.23.0 '@babel/helper-hoist-variables': 7.22.5 '@babel/helper-split-export-declaration': 7.22.6 - '@babel/parser': 7.23.6 - '@babel/types': 7.23.6 + '@babel/parser': 7.24.4 + '@babel/types': 7.24.0 debug: 4.3.4 globals: 11.12.0 transitivePeerDependencies: - supports-color dev: true - /@babel/types@7.23.6: - resolution: {integrity: sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==} + /@babel/types@7.24.0: + resolution: {integrity: sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-string-parser': 7.23.4 + '@babel/helper-string-parser': 7.24.1 '@babel/helper-validator-identifier': 7.22.20 to-fast-properties: 2.0.0 dev: true @@ -1623,26 +1620,12 @@ packages: engines: {node: '>=10.0.0'} dev: true - /@emotion/is-prop-valid@0.8.8: - resolution: {integrity: sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==} - requiresBuild: true - dependencies: - '@emotion/memoize': 0.7.4 - dev: false - optional: true - /@emotion/is-prop-valid@1.2.1: resolution: {integrity: sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==} dependencies: '@emotion/memoize': 0.8.1 dev: true - /@emotion/memoize@0.7.4: - resolution: {integrity: sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==} - requiresBuild: true - dev: false - optional: true - /@emotion/memoize@0.8.1: resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==} dev: true @@ -1651,16 +1634,16 @@ packages: resolution: {integrity: sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==} dev: true - /@emotion/use-insertion-effect-with-fallbacks@1.0.1(react@18.2.0): + /@emotion/use-insertion-effect-with-fallbacks@1.0.1(react@18.3.1): resolution: {integrity: sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==} peerDependencies: react: '>=16.8.0' dependencies: - react: 18.2.0 + react: 18.3.1 dev: true - /@esbuild/aix-ppc64@0.19.11: - resolution: {integrity: sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==} + /@esbuild/aix-ppc64@0.19.12: + resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} engines: {node: '>=12'} cpu: [ppc64] os: [aix] @@ -1677,8 +1660,8 @@ packages: dev: true optional: true - /@esbuild/android-arm64@0.19.11: - resolution: {integrity: sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q==} + /@esbuild/android-arm64@0.19.12: + resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} engines: {node: '>=12'} cpu: [arm64] os: [android] @@ -1695,8 +1678,8 @@ packages: dev: true optional: true - /@esbuild/android-arm@0.19.11: - resolution: {integrity: sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw==} + /@esbuild/android-arm@0.19.12: + resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} engines: {node: '>=12'} cpu: [arm] os: [android] @@ -1713,8 +1696,8 @@ packages: dev: true optional: true - /@esbuild/android-x64@0.19.11: - resolution: {integrity: sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg==} + /@esbuild/android-x64@0.19.12: + resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} engines: {node: '>=12'} cpu: [x64] os: [android] @@ -1731,8 +1714,8 @@ packages: dev: true optional: true - /@esbuild/darwin-arm64@0.19.11: - resolution: {integrity: sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ==} + /@esbuild/darwin-arm64@0.19.12: + resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] @@ -1749,8 +1732,8 @@ packages: dev: true optional: true - /@esbuild/darwin-x64@0.19.11: - resolution: {integrity: sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g==} + /@esbuild/darwin-x64@0.19.12: + resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} engines: {node: '>=12'} cpu: [x64] os: [darwin] @@ -1767,8 +1750,8 @@ packages: dev: true optional: true - /@esbuild/freebsd-arm64@0.19.11: - resolution: {integrity: sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA==} + /@esbuild/freebsd-arm64@0.19.12: + resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] @@ -1785,8 +1768,8 @@ packages: dev: true optional: true - /@esbuild/freebsd-x64@0.19.11: - resolution: {integrity: sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw==} + /@esbuild/freebsd-x64@0.19.12: + resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] @@ -1803,8 +1786,8 @@ packages: dev: true optional: true - /@esbuild/linux-arm64@0.19.11: - resolution: {integrity: sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg==} + /@esbuild/linux-arm64@0.19.12: + resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} engines: {node: '>=12'} cpu: [arm64] os: [linux] @@ -1821,8 +1804,8 @@ packages: dev: true optional: true - /@esbuild/linux-arm@0.19.11: - resolution: {integrity: sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q==} + /@esbuild/linux-arm@0.19.12: + resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} engines: {node: '>=12'} cpu: [arm] os: [linux] @@ -1839,8 +1822,8 @@ packages: dev: true optional: true - /@esbuild/linux-ia32@0.19.11: - resolution: {integrity: sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA==} + /@esbuild/linux-ia32@0.19.12: + resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} engines: {node: '>=12'} cpu: [ia32] os: [linux] @@ -1857,8 +1840,8 @@ packages: dev: true optional: true - /@esbuild/linux-loong64@0.19.11: - resolution: {integrity: sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg==} + /@esbuild/linux-loong64@0.19.12: + resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} engines: {node: '>=12'} cpu: [loong64] os: [linux] @@ -1875,8 +1858,8 @@ packages: dev: true optional: true - /@esbuild/linux-mips64el@0.19.11: - resolution: {integrity: sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg==} + /@esbuild/linux-mips64el@0.19.12: + resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] @@ -1893,8 +1876,8 @@ packages: dev: true optional: true - /@esbuild/linux-ppc64@0.19.11: - resolution: {integrity: sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA==} + /@esbuild/linux-ppc64@0.19.12: + resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] @@ -1911,8 +1894,8 @@ packages: dev: true optional: true - /@esbuild/linux-riscv64@0.19.11: - resolution: {integrity: sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ==} + /@esbuild/linux-riscv64@0.19.12: + resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] @@ -1929,8 +1912,8 @@ packages: dev: true optional: true - /@esbuild/linux-s390x@0.19.11: - resolution: {integrity: sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q==} + /@esbuild/linux-s390x@0.19.12: + resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} engines: {node: '>=12'} cpu: [s390x] os: [linux] @@ -1947,8 +1930,8 @@ packages: dev: true optional: true - /@esbuild/linux-x64@0.19.11: - resolution: {integrity: sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA==} + /@esbuild/linux-x64@0.19.12: + resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} engines: {node: '>=12'} cpu: [x64] os: [linux] @@ -1965,8 +1948,8 @@ packages: dev: true optional: true - /@esbuild/netbsd-x64@0.19.11: - resolution: {integrity: sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ==} + /@esbuild/netbsd-x64@0.19.12: + resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] @@ -1983,8 +1966,8 @@ packages: dev: true optional: true - /@esbuild/openbsd-x64@0.19.11: - resolution: {integrity: sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw==} + /@esbuild/openbsd-x64@0.19.12: + resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] @@ -2001,8 +1984,8 @@ packages: dev: true optional: true - /@esbuild/sunos-x64@0.19.11: - resolution: {integrity: sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ==} + /@esbuild/sunos-x64@0.19.12: + resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} engines: {node: '>=12'} cpu: [x64] os: [sunos] @@ -2019,8 +2002,8 @@ packages: dev: true optional: true - /@esbuild/win32-arm64@0.19.11: - resolution: {integrity: sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ==} + /@esbuild/win32-arm64@0.19.12: + resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} engines: {node: '>=12'} cpu: [arm64] os: [win32] @@ -2037,8 +2020,8 @@ packages: dev: true optional: true - /@esbuild/win32-ia32@0.19.11: - resolution: {integrity: sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg==} + /@esbuild/win32-ia32@0.19.12: + resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} engines: {node: '>=12'} cpu: [ia32] os: [win32] @@ -2055,8 +2038,8 @@ packages: dev: true optional: true - /@esbuild/win32-x64@0.19.11: - resolution: {integrity: sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw==} + /@esbuild/win32-x64@0.19.12: + resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} engines: {node: '>=12'} cpu: [x64] os: [win32] @@ -2064,13 +2047,13 @@ packages: dev: true optional: true - /@eslint-community/eslint-utils@4.4.0(eslint@8.56.0): + /@eslint-community/eslint-utils@4.4.0(eslint@8.57.0): resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: - eslint: 8.56.0 + eslint: 8.57.0 eslint-visitor-keys: 3.4.3 dev: true @@ -2087,7 +2070,7 @@ packages: debug: 4.3.4 espree: 9.6.1 globals: 13.24.0 - ignore: 5.3.0 + ignore: 5.3.1 import-fresh: 3.3.0 js-yaml: 4.1.0 minimatch: 3.1.2 @@ -2096,8 +2079,8 @@ packages: - supports-color dev: true - /@eslint/js@8.56.0: - resolution: {integrity: sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==} + /@eslint/js@8.57.0: + resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true @@ -2105,38 +2088,38 @@ packages: resolution: {integrity: sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ==} dev: true - /@floating-ui/core@1.5.3: - resolution: {integrity: sha512-O0WKDOo0yhJuugCx6trZQj5jVJ9yR0ystG2JaNAemYUWce+pmM6WUEFIibnWyEJKdrDxhm75NoSRME35FNaM/Q==} + /@floating-ui/core@1.6.1: + resolution: {integrity: sha512-42UH54oPZHPdRHdw6BgoBD6cg/eVTmVrFcgeRDM3jbO7uxSoipVcmcIGFcA5jmOHO5apcyvBhkSKES3fQJnu7A==} dependencies: - '@floating-ui/utils': 0.2.1 + '@floating-ui/utils': 0.2.2 dev: true - /@floating-ui/dom@1.5.4: - resolution: {integrity: sha512-jByEsHIY+eEdCjnTVu+E3ephzTOzkQ8hgUfGwos+bg7NlH33Zc5uO+QHz1mrQUOgIKKDD1RtS201P9NvAfq3XQ==} + /@floating-ui/dom@1.6.4: + resolution: {integrity: sha512-0G8R+zOvQsAG1pg2Q99P21jiqxqGBW1iRe/iXHsBRBxnpXKFI8QwbB4x5KmYLggNO5m34IQgOIu9SCRfR/WWiQ==} dependencies: - '@floating-ui/core': 1.5.3 - '@floating-ui/utils': 0.2.1 + '@floating-ui/core': 1.6.1 + '@floating-ui/utils': 0.2.2 dev: true - /@floating-ui/react-dom@2.0.5(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-UsBK30Bg+s6+nsgblXtZmwHhgS2vmbuQK22qgt2pTQM6M3X6H1+cQcLXqgRY3ihVLcZJE6IvqDQozhsnIVqK/Q==} + /@floating-ui/react-dom@2.0.9(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-q0umO0+LQK4+p6aGyvzASqKbKOJcAHJ7ycE9CuUvfx3s9zTHWmGJTPOIlM/hmSBfUfg/XfY5YhLBLR/LHwShQQ==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' dependencies: - '@floating-ui/dom': 1.5.4 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + '@floating-ui/dom': 1.6.4 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) dev: true - /@floating-ui/utils@0.2.1: - resolution: {integrity: sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==} + /@floating-ui/utils@0.2.2: + resolution: {integrity: sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw==} dev: true - /@formatjs/ecma402-abstract@1.18.0: - resolution: {integrity: sha512-PEVLoa3zBevWSCZzPIM/lvPCi8P5l4G+NXQMc/CjEiaCWgyHieUoo0nM7Bs0n/NbuQ6JpXEolivQ9pKSBHaDlA==} + /@formatjs/ecma402-abstract@1.18.2: + resolution: {integrity: sha512-+QoPW4csYALsQIl8GbN14igZzDbuwzcpWrku9nyMXlaqAlwRBgl5V+p0vWMGFqHOw37czNXaP/lEk4wbLgcmtA==} dependencies: - '@formatjs/intl-localematcher': 0.5.2 + '@formatjs/intl-localematcher': 0.5.4 tslib: 2.6.2 dev: false @@ -2146,142 +2129,127 @@ packages: tslib: 2.6.2 dev: false - /@formatjs/icu-messageformat-parser@2.7.3: - resolution: {integrity: sha512-X/jy10V9S/vW+qlplqhMUxR8wErQ0mmIYSq4mrjpjDl9mbuGcCILcI1SUYkL5nlM4PJqpc0KOS0bFkkJNPxYRw==} + /@formatjs/icu-messageformat-parser@2.7.6: + resolution: {integrity: sha512-etVau26po9+eewJKYoiBKP6743I1br0/Ie00Pb/S/PtmYfmjTcOn2YCh2yNkSZI12h6Rg+BOgQYborXk46BvkA==} dependencies: - '@formatjs/ecma402-abstract': 1.18.0 - '@formatjs/icu-skeleton-parser': 1.7.0 + '@formatjs/ecma402-abstract': 1.18.2 + '@formatjs/icu-skeleton-parser': 1.8.0 tslib: 2.6.2 dev: false - /@formatjs/icu-skeleton-parser@1.7.0: - resolution: {integrity: sha512-Cfdo/fgbZzpN/jlN/ptQVe0lRHora+8ezrEeg2RfrNjyp+YStwBy7cqDY8k5/z2LzXg6O0AdzAV91XS0zIWv+A==} + /@formatjs/icu-skeleton-parser@1.8.0: + resolution: {integrity: sha512-QWLAYvM0n8hv7Nq5BEs4LKIjevpVpbGLAJgOaYzg9wABEoX1j0JO1q2/jVkO6CVlq0dbsxZCngS5aXbysYueqA==} dependencies: - '@formatjs/ecma402-abstract': 1.18.0 + '@formatjs/ecma402-abstract': 1.18.2 tslib: 2.6.2 dev: false - /@formatjs/intl-displaynames@6.6.4: - resolution: {integrity: sha512-ET8KQ+L9Q0K8x1SnJQa4DNssUcbATlMopWqYvGGR8yAvw5qwAQc1fv+DshCoZNIE9pbcue0IGC4kWNAkWqlFag==} + /@formatjs/intl-displaynames@6.6.6: + resolution: {integrity: sha512-Dg5URSjx0uzF8VZXtHb6KYZ6LFEEhCbAbKoYChYHEOnMFTw/ZU3jIo/NrujzQD2EfKPgQzIq73LOUvW6Z/LpFA==} dependencies: - '@formatjs/ecma402-abstract': 1.18.0 - '@formatjs/intl-localematcher': 0.5.2 + '@formatjs/ecma402-abstract': 1.18.2 + '@formatjs/intl-localematcher': 0.5.4 tslib: 2.6.2 dev: false - /@formatjs/intl-listformat@7.5.3: - resolution: {integrity: sha512-l7EOr0Yh1m8KagytukB90yw81uyzrM7amKFrgxXqphz4KeSIL0KPa68lPsdtZ+JmQB73GaDQRwLOwUKFZ1VZPQ==} + /@formatjs/intl-listformat@7.5.5: + resolution: {integrity: sha512-XoI52qrU6aBGJC9KJddqnacuBbPlb/bXFN+lIFVFhQ1RnFHpzuFrlFdjD9am2O7ZSYsyqzYRpkVcXeT1GHkwDQ==} dependencies: - '@formatjs/ecma402-abstract': 1.18.0 - '@formatjs/intl-localematcher': 0.5.2 + '@formatjs/ecma402-abstract': 1.18.2 + '@formatjs/intl-localematcher': 0.5.4 tslib: 2.6.2 dev: false - /@formatjs/intl-localematcher@0.5.2: - resolution: {integrity: sha512-txaaE2fiBMagLrR4jYhxzFO6wEdEG4TPMqrzBAcbr4HFUYzH/YC+lg6OIzKCHm8WgDdyQevxbAAV1OgcXctuGw==} + /@formatjs/intl-localematcher@0.5.4: + resolution: {integrity: sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==} dependencies: tslib: 2.6.2 dev: false - /@formatjs/intl@2.9.9(typescript@4.9.5): - resolution: {integrity: sha512-JI3CNgL2Zdg5lv9ncT2sYKqbAj2RGrCbdzaCckIxMPxn4QuHuOVvYUGmBAXVusBmfG/0sxLmMrnwnBioz+QKdA==} + /@formatjs/intl@2.10.1(typescript@4.9.5): + resolution: {integrity: sha512-dsLG15U7xDi8yzKf4hcAWSsCaez3XrjTO2oaRHPyHtXLm1aEzYbDw6bClo/HMHu+iwS5GbDqT3DV+hYP2ylScg==} peerDependencies: - typescript: '5' + typescript: ^4.7 || 5 peerDependenciesMeta: typescript: optional: true dependencies: - '@formatjs/ecma402-abstract': 1.18.0 + '@formatjs/ecma402-abstract': 1.18.2 '@formatjs/fast-memoize': 2.2.0 - '@formatjs/icu-messageformat-parser': 2.7.3 - '@formatjs/intl-displaynames': 6.6.4 - '@formatjs/intl-listformat': 7.5.3 - intl-messageformat: 10.5.8 + '@formatjs/icu-messageformat-parser': 2.7.6 + '@formatjs/intl-displaynames': 6.6.6 + '@formatjs/intl-listformat': 7.5.5 + intl-messageformat: 10.5.11 tslib: 2.6.2 typescript: 4.9.5 dev: false /@fortawesome/fontawesome-common-types@0.2.36: - resolution: {integrity: sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg==, tarball: https://npm.fontawesome.com/@fortawesome/fontawesome-common-types/-/0.2.36/fontawesome-common-types-0.2.36.tgz} + resolution: {integrity: sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg==} engines: {node: '>=6'} requiresBuild: true - - /@fortawesome/fontawesome-svg-core@1.2.36: - resolution: {integrity: sha512-YUcsLQKYb6DmaJjIHdDWpBIGCcyE/W+p/LMGvjQem55Mm2XWVAP5kWTMKWLv9lwpCVjpLxPyOMOyUocP1GxrtA==, tarball: https://npm.fontawesome.com/@fortawesome/fontawesome-svg-core/-/1.2.36/fontawesome-svg-core-1.2.36.tgz} - engines: {node: '>=6'} - requiresBuild: true - dependencies: - '@fortawesome/fontawesome-common-types': 0.2.36 dev: false - /@fortawesome/free-solid-svg-icons@5.15.4: - resolution: {integrity: sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w==, tarball: https://npm.fontawesome.com/@fortawesome/free-solid-svg-icons/-/5.15.4/free-solid-svg-icons-5.15.4.tgz} + /@fortawesome/fontawesome-common-types@0.3.0: + resolution: {integrity: sha512-CA3MAZBTxVsF6SkfkHXDerkhcQs0QPofy43eFdbWJJkZiq3SfiaH1msOkac59rQaqto5EqWnASboY1dBuKen5w==} engines: {node: '>=6'} + deprecated: Please upgrade to 6.1.0. https://fontawesome.com/docs/changelog/ requiresBuild: true - dependencies: - '@fortawesome/fontawesome-common-types': 0.2.36 dev: false - /@fortawesome/pro-light-svg-icons@5.15.4: - resolution: {integrity: sha512-+Hk1YVmuk+1TYhbRTD4WhabQhWuK/DG8IVGO+iXM5A4OW1D6rHFHr8rt9JNc7iybXFQWY7HuCRPoVXdCm4YZ0A==, tarball: https://npm.fontawesome.com/@fortawesome/pro-light-svg-icons/-/5.15.4/pro-light-svg-icons-5.15.4.tgz} - engines: {node: '>=6'} - requiresBuild: true - dependencies: - '@fortawesome/fontawesome-common-types': 0.2.36 - dev: true - - /@fortawesome/pro-regular-svg-icons@5.15.4: - resolution: {integrity: sha512-LN9wOPIISKg3jZBbAuMm6YcsCiMRzTcpVkotLonRuzDmw0OLszUPCHZ6vB5hEnOC7g6IIOwOOnV12fW0V8IS9Q==, tarball: https://npm.fontawesome.com/@fortawesome/pro-regular-svg-icons/-/5.15.4/pro-regular-svg-icons-5.15.4.tgz} + /@fortawesome/fontawesome-svg-core@1.3.0: + resolution: {integrity: sha512-UIL6crBWhjTNQcONt96ExjUnKt1D68foe3xjEensLDclqQ6YagwCRYVQdrp/hW0ALRp/5Fv/VKw+MqTUWYYvPg==} engines: {node: '>=6'} + deprecated: Please upgrade to 6.1.0. https://fontawesome.com/docs/changelog/ requiresBuild: true dependencies: - '@fortawesome/fontawesome-common-types': 0.2.36 - dev: true + '@fortawesome/fontawesome-common-types': 0.3.0 + dev: false - /@fortawesome/pro-solid-svg-icons@5.15.4: - resolution: {integrity: sha512-ZlSg/RyAlwYJcsJlw2WfZSeOJ8GMKwIQ3ZY+GNFRZ+7pF+RRXxyZp2WIgUKhPK/M43SfSou6R6XdKZyz2xVVEQ==, tarball: https://npm.fontawesome.com/@fortawesome/pro-solid-svg-icons/-/5.15.4/pro-solid-svg-icons-5.15.4.tgz} + /@fortawesome/free-solid-svg-icons@5.15.4: + resolution: {integrity: sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w==} engines: {node: '>=6'} requiresBuild: true dependencies: '@fortawesome/fontawesome-common-types': 0.2.36 - dev: true + dev: false - /@fortawesome/react-fontawesome@0.2.0(@fortawesome/fontawesome-svg-core@1.2.36)(react@18.2.0): - resolution: {integrity: sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==, tarball: https://npm.fontawesome.com/@fortawesome/react-fontawesome/-/0.2.0/react-fontawesome-0.2.0.tgz} + /@fortawesome/react-fontawesome@0.2.0(@fortawesome/fontawesome-svg-core@1.3.0)(react@18.3.1): + resolution: {integrity: sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==} peerDependencies: '@fortawesome/fontawesome-svg-core': ~1 || ~6 react: '>=16.3' dependencies: - '@fortawesome/fontawesome-svg-core': 1.2.36 + '@fortawesome/fontawesome-svg-core': 1.3.0 prop-types: 15.8.1 - react: 18.2.0 + react: 18.3.1 dev: false - /@hookform/error-message@2.0.1(react-dom@18.2.0)(react-hook-form@7.49.2)(react@18.2.0): + /@hookform/error-message@2.0.1(react-dom@18.3.1)(react-hook-form@7.51.3)(react@18.3.1): resolution: {integrity: sha512-U410sAr92xgxT1idlu9WWOVjndxLdgPUHEB8Schr27C9eh7/xUnITWpCMF93s+lGiG++D4JnbSnrb5A21AdSNg==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' react-hook-form: ^7.0.0 dependencies: - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-hook-form: 7.49.2(react@18.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-hook-form: 7.51.3(react@18.3.1) dev: false - /@hookform/resolvers@3.3.4(react-hook-form@7.49.2): + /@hookform/resolvers@3.3.4(react-hook-form@7.51.3): resolution: {integrity: sha512-o5cgpGOuJYrd+iMKvkttOclgwRW86EsWJZZRC23prf0uU2i48Htq4PuT73AVb9ionFyZrwYEITuOFGF+BydEtQ==} peerDependencies: react-hook-form: ^7.0.0 dependencies: - react-hook-form: 7.49.2(react@18.2.0) + react-hook-form: 7.51.3(react@18.3.1) dev: false - /@humanwhocodes/config-array@0.11.13: - resolution: {integrity: sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==} + /@humanwhocodes/config-array@0.11.14: + resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} engines: {node: '>=10.10.0'} dependencies: - '@humanwhocodes/object-schema': 2.0.1 + '@humanwhocodes/object-schema': 2.0.3 debug: 4.3.4 minimatch: 3.1.2 transitivePeerDependencies: @@ -2293,8 +2261,8 @@ packages: engines: {node: '>=12.22'} dev: true - /@humanwhocodes/object-schema@2.0.1: - resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==} + /@humanwhocodes/object-schema@2.0.3: + resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} dev: true /@hutson/parse-repository-url@3.0.2: @@ -2335,7 +2303,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.10.7 + '@types/node': 20.12.7 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 @@ -2356,14 +2324,14 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.10.7 + '@types/node': 20.12.7 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.10.7) + jest-config: 29.7.0(@types/node@20.12.7) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -2391,7 +2359,7 @@ packages: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.10.7 + '@types/node': 20.12.7 jest-mock: 29.7.0 dev: true @@ -2418,7 +2386,7 @@ packages: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 20.10.7 + '@types/node': 20.12.7 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -2450,18 +2418,18 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@jridgewell/trace-mapping': 0.3.20 - '@types/node': 20.10.7 + '@jridgewell/trace-mapping': 0.3.25 + '@types/node': 20.12.7 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 glob: 7.2.3 graceful-fs: 4.2.11 istanbul-lib-coverage: 3.2.2 - istanbul-lib-instrument: 6.0.1 + istanbul-lib-instrument: 6.0.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 4.0.1 - istanbul-reports: 3.1.6 + istanbul-reports: 3.1.7 jest-message-util: 29.7.0 jest-util: 29.7.0 jest-worker: 29.7.0 @@ -2484,7 +2452,7 @@ packages: resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jridgewell/trace-mapping': 0.3.20 + '@jridgewell/trace-mapping': 0.3.25 callsites: 3.1.0 graceful-fs: 4.2.11 dev: true @@ -2513,9 +2481,9 @@ packages: resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@babel/core': 7.23.7 + '@babel/core': 7.24.4 '@jest/types': 29.6.3 - '@jridgewell/trace-mapping': 0.3.20 + '@jridgewell/trace-mapping': 0.3.25 babel-plugin-istanbul: 6.1.1 chalk: 4.1.2 convert-source-map: 2.0.0 @@ -2539,12 +2507,12 @@ packages: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.10.7 + '@types/node': 20.12.7 '@types/yargs': 17.0.32 chalk: 4.1.2 dev: true - /@joshwooding/vite-plugin-react-docgen-typescript@0.3.0(typescript@4.9.5)(vite@4.5.1): + /@joshwooding/vite-plugin-react-docgen-typescript@0.3.0(typescript@4.9.5)(vite@4.5.3): resolution: {integrity: sha512-2D6y7fNvFmsLmRt6UCOFJPvFoPMJGT0Uh1Wg0RaigUp7kdQPs6yYn8Dmx6GZkOH/NW0yMTwRz/p0SRMMRo50vA==} peerDependencies: typescript: '>= 4.3.x' @@ -2558,42 +2526,42 @@ packages: magic-string: 0.27.0 react-docgen-typescript: 2.2.2(typescript@4.9.5) typescript: 4.9.5 - vite: 4.5.1 + vite: 4.5.3 dev: true - /@jridgewell/gen-mapping@0.3.3: - resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} + /@jridgewell/gen-mapping@0.3.5: + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} engines: {node: '>=6.0.0'} dependencies: - '@jridgewell/set-array': 1.1.2 + '@jridgewell/set-array': 1.2.1 '@jridgewell/sourcemap-codec': 1.4.15 - '@jridgewell/trace-mapping': 0.3.20 + '@jridgewell/trace-mapping': 0.3.25 dev: true - /@jridgewell/resolve-uri@3.1.1: - resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} + /@jridgewell/resolve-uri@3.1.2: + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} dev: true - /@jridgewell/set-array@1.1.2: - resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} + /@jridgewell/set-array@1.2.1: + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} engines: {node: '>=6.0.0'} dev: true - /@jridgewell/source-map@0.3.5: - resolution: {integrity: sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==} + /@jridgewell/source-map@0.3.6: + resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} dependencies: - '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.20 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 dev: true /@jridgewell/sourcemap-codec@1.4.15: resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - /@jridgewell/trace-mapping@0.3.20: - resolution: {integrity: sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==} + /@jridgewell/trace-mapping@0.3.25: + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} dependencies: - '@jridgewell/resolve-uri': 3.1.1 + '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.4.15 dev: true @@ -2601,14 +2569,14 @@ packages: resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==} dev: true - /@mdx-js/react@2.3.0(react@18.2.0): + /@mdx-js/react@2.3.0(react@18.3.1): resolution: {integrity: sha512-zQH//gdOmuu7nt2oJR29vFhDv88oGPmVw6BggmrHeMI+xgEkp1B2dX9/bMBSYtK0dyLX/aOmesKS09g222K1/g==} peerDependencies: react: '>=16' dependencies: - '@types/mdx': 2.0.10 - '@types/react': 17.0.74 - react: 18.2.0 + '@types/mdx': 2.0.13 + '@types/react': 17.0.80 + react: 18.3.1 dev: true /@ndelangen/get-tarball@3.0.9: @@ -2637,7 +2605,7 @@ packages: engines: {node: '>= 8'} dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.16.0 + fastq: 1.17.1 dev: true /@pkgjs/parseargs@0.11.0: @@ -2650,16 +2618,16 @@ packages: /@radix-ui/number@1.0.1: resolution: {integrity: sha512-T5gIdVO2mmPW3NNhjNgEP3cqMXjXL9UbO0BzWcXfvdBs+BohbQxvd/K5hSVKmn9/lbTdsQVKbUcP5WLCwvUbBg==} dependencies: - '@babel/runtime': 7.23.7 + '@babel/runtime': 7.24.4 dev: true /@radix-ui/primitive@1.0.1: resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==} dependencies: - '@babel/runtime': 7.23.7 + '@babel/runtime': 7.24.4 dev: true - /@radix-ui/react-arrow@1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): + /@radix-ui/react-arrow@1.0.3(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==} peerDependencies: '@types/react': '*' @@ -2672,14 +2640,14 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.23.7 - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@types/react': 17.0.74 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + '@babel/runtime': 7.24.4 + '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) + '@types/react': 17.0.80 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) dev: true - /@radix-ui/react-collection@1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): + /@radix-ui/react-collection@1.0.3(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==} peerDependencies: '@types/react': '*' @@ -2692,17 +2660,17 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.23.7 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-context': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-slot': 1.0.2(@types/react@17.0.74)(react@18.2.0) - '@types/react': 17.0.74 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + '@babel/runtime': 7.24.4 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-context': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-slot': 1.0.2(@types/react@17.0.80)(react@18.3.1) + '@types/react': 17.0.80 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) dev: true - /@radix-ui/react-compose-refs@1.0.1(@types/react@17.0.74)(react@18.2.0): + /@radix-ui/react-compose-refs@1.0.1(@types/react@17.0.80)(react@18.3.1): resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==} peerDependencies: '@types/react': '*' @@ -2711,12 +2679,12 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.23.7 - '@types/react': 17.0.74 - react: 18.2.0 + '@babel/runtime': 7.24.4 + '@types/react': 17.0.80 + react: 18.3.1 dev: true - /@radix-ui/react-context@1.0.1(@types/react@17.0.74)(react@18.2.0): + /@radix-ui/react-context@1.0.1(@types/react@17.0.80)(react@18.3.1): resolution: {integrity: sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==} peerDependencies: '@types/react': '*' @@ -2725,12 +2693,12 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.23.7 - '@types/react': 17.0.74 - react: 18.2.0 + '@babel/runtime': 7.24.4 + '@types/react': 17.0.80 + react: 18.3.1 dev: true - /@radix-ui/react-direction@1.0.1(@types/react@17.0.74)(react@18.2.0): + /@radix-ui/react-direction@1.0.1(@types/react@17.0.80)(react@18.3.1): resolution: {integrity: sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==} peerDependencies: '@types/react': '*' @@ -2739,12 +2707,12 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.23.7 - '@types/react': 17.0.74 - react: 18.2.0 + '@babel/runtime': 7.24.4 + '@types/react': 17.0.80 + react: 18.3.1 dev: true - /@radix-ui/react-dismissable-layer@1.0.4(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): + /@radix-ui/react-dismissable-layer@1.0.4(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-7UpBa/RKMoHJYjie1gkF1DlK8l1fdU/VKDpoS3rCCo8YBJR294GwcEHyxHw72yvphJ7ld0AXEcSLAzY2F/WyCg==} peerDependencies: '@types/react': '*' @@ -2757,18 +2725,18 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.23.7 + '@babel/runtime': 7.24.4 '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-use-escape-keydown': 1.0.3(@types/react@17.0.74)(react@18.2.0) - '@types/react': 17.0.74 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-use-escape-keydown': 1.0.3(@types/react@17.0.80)(react@18.3.1) + '@types/react': 17.0.80 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) dev: true - /@radix-ui/react-focus-guards@1.0.1(@types/react@17.0.74)(react@18.2.0): + /@radix-ui/react-focus-guards@1.0.1(@types/react@17.0.80)(react@18.3.1): resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==} peerDependencies: '@types/react': '*' @@ -2777,12 +2745,12 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.23.7 - '@types/react': 17.0.74 - react: 18.2.0 + '@babel/runtime': 7.24.4 + '@types/react': 17.0.80 + react: 18.3.1 dev: true - /@radix-ui/react-focus-scope@1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): + /@radix-ui/react-focus-scope@1.0.3(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-upXdPfqI4islj2CslyfUBNlaJCPybbqRHAi1KER7Isel9Q2AtSJ0zRBZv8mWQiFXD2nyAJ4BhC3yXgZ6kMBSrQ==} peerDependencies: '@types/react': '*' @@ -2795,16 +2763,16 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.23.7 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@types/react': 17.0.74 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + '@babel/runtime': 7.24.4 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@types/react': 17.0.80 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) dev: true - /@radix-ui/react-id@1.0.1(@types/react@17.0.74)(react@18.2.0): + /@radix-ui/react-id@1.0.1(@types/react@17.0.80)(react@18.3.1): resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==} peerDependencies: '@types/react': '*' @@ -2813,13 +2781,13 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.23.7 - '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@types/react': 17.0.74 - react: 18.2.0 + '@babel/runtime': 7.24.4 + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@types/react': 17.0.80 + react: 18.3.1 dev: true - /@radix-ui/react-popper@1.1.2(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): + /@radix-ui/react-popper@1.1.2(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-1CnGGfFi/bbqtJZZ0P/NQY20xdG3E0LALJaLUEoKwPLwl6PPPfbeiCqMVQnhoFRAxjJj4RpBRJzDmUgsex2tSg==} peerDependencies: '@types/react': '*' @@ -2832,23 +2800,23 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.23.7 - '@floating-ui/react-dom': 2.0.5(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-arrow': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-context': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-use-rect': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-use-size': 1.0.1(@types/react@17.0.74)(react@18.2.0) + '@babel/runtime': 7.24.4 + '@floating-ui/react-dom': 2.0.9(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-arrow': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-context': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-use-rect': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-use-size': 1.0.1(@types/react@17.0.80)(react@18.3.1) '@radix-ui/rect': 1.0.1 - '@types/react': 17.0.74 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + '@types/react': 17.0.80 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) dev: true - /@radix-ui/react-portal@1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): + /@radix-ui/react-portal@1.0.3(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-xLYZeHrWoPmA5mEKEfZZevoVRK/Q43GfzRXkWV6qawIWWK8t6ifIiLQdd7rmQ4Vk1bmI21XhqF9BN3jWf+phpA==} peerDependencies: '@types/react': '*' @@ -2861,14 +2829,14 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.23.7 - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@types/react': 17.0.74 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + '@babel/runtime': 7.24.4 + '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) + '@types/react': 17.0.80 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) dev: true - /@radix-ui/react-primitive@1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): + /@radix-ui/react-primitive@1.0.3(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==} peerDependencies: '@types/react': '*' @@ -2881,14 +2849,14 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.23.7 - '@radix-ui/react-slot': 1.0.2(@types/react@17.0.74)(react@18.2.0) - '@types/react': 17.0.74 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + '@babel/runtime': 7.24.4 + '@radix-ui/react-slot': 1.0.2(@types/react@17.0.80)(react@18.3.1) + '@types/react': 17.0.80 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) dev: true - /@radix-ui/react-roving-focus@1.0.4(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): + /@radix-ui/react-roving-focus@1.0.4(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==} peerDependencies: '@types/react': '*' @@ -2901,22 +2869,22 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.23.7 + '@babel/runtime': 7.24.4 '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-collection': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-context': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-direction': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-id': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@types/react': 17.0.74 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: true - - /@radix-ui/react-select@1.2.2(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): + '@radix-ui/react-collection': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-context': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-direction': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-id': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@types/react': 17.0.80 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + dev: true + + /@radix-ui/react-select@1.2.2(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-zI7McXr8fNaSrUY9mZe4x/HC0jTLY9fWNhO1oLWYMQGDXuV4UCivIGTxwioSzO0ZCYX9iSLyWmAh/1TOmX3Cnw==} peerDependencies: '@types/react': '*' @@ -2929,34 +2897,34 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.23.7 + '@babel/runtime': 7.24.4 '@radix-ui/number': 1.0.1 '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-collection': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-context': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-direction': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-dismissable-layer': 1.0.4(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-focus-guards': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-focus-scope': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-id': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-popper': 1.1.2(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-portal': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-slot': 1.0.2(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-use-previous': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-visually-hidden': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@types/react': 17.0.74 - aria-hidden: 1.2.3 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-remove-scroll: 2.5.5(@types/react@17.0.74)(react@18.2.0) - dev: true - - /@radix-ui/react-separator@1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): + '@radix-ui/react-collection': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-context': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-direction': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.0.4(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-focus-guards': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-id': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-popper': 1.1.2(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-portal': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-slot': 1.0.2(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-use-previous': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) + '@types/react': 17.0.80 + aria-hidden: 1.2.4 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.5.5(@types/react@17.0.80)(react@18.3.1) + dev: true + + /@radix-ui/react-separator@1.0.3(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-itYmTy/kokS21aiV5+Z56MZB54KrhPgn6eHDKkFeOLR34HMN2s8PaN47qZZAGnvupcjxHaFZnW4pQEh0BvvVuw==} peerDependencies: '@types/react': '*' @@ -2969,14 +2937,14 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.23.7 - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@types/react': 17.0.74 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + '@babel/runtime': 7.24.4 + '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) + '@types/react': 17.0.80 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) dev: true - /@radix-ui/react-slot@1.0.2(@types/react@17.0.74)(react@18.2.0): + /@radix-ui/react-slot@1.0.2(@types/react@17.0.80)(react@18.3.1): resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==} peerDependencies: '@types/react': '*' @@ -2985,13 +2953,13 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.23.7 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@types/react': 17.0.74 - react: 18.2.0 + '@babel/runtime': 7.24.4 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@types/react': 17.0.80 + react: 18.3.1 dev: true - /@radix-ui/react-toggle-group@1.0.4(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): + /@radix-ui/react-toggle-group@1.0.4(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-Uaj/M/cMyiyT9Bx6fOZO0SAG4Cls0GptBWiBmBxofmDbNVnYYoyRWj/2M/6VCi/7qcXFWnHhRUfdfZFvvkuu8A==} peerDependencies: '@types/react': '*' @@ -3004,20 +2972,20 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.23.7 + '@babel/runtime': 7.24.4 '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-context': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-direction': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-roving-focus': 1.0.4(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-toggle': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@types/react': 17.0.74 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: true - - /@radix-ui/react-toggle@1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): + '@radix-ui/react-context': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-direction': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-roving-focus': 1.0.4(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-toggle': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@types/react': 17.0.80 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + dev: true + + /@radix-ui/react-toggle@1.0.3(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-Pkqg3+Bc98ftZGsl60CLANXQBBQ4W3mTFS9EJvNxKMZ7magklKV69/id1mlAlOFDDfHvlCms0fx8fA4CMKDJHg==} peerDependencies: '@types/react': '*' @@ -3030,16 +2998,16 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.23.7 + '@babel/runtime': 7.24.4 '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@types/react': 17.0.74 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@types/react': 17.0.80 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) dev: true - /@radix-ui/react-toolbar@1.0.4(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): + /@radix-ui/react-toolbar@1.0.4(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-tBgmM/O7a07xbaEkYJWYTXkIdU/1pW4/KZORR43toC/4XWyBCURK0ei9kMUdp+gTPPKBgYLxXmRSH1EVcIDp8Q==} peerDependencies: '@types/react': '*' @@ -3052,20 +3020,20 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.23.7 + '@babel/runtime': 7.24.4 '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-context': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-direction': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-roving-focus': 1.0.4(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-separator': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-toggle-group': 1.0.4(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@types/react': 17.0.74 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: true - - /@radix-ui/react-use-callback-ref@1.0.1(@types/react@17.0.74)(react@18.2.0): + '@radix-ui/react-context': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-direction': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-roving-focus': 1.0.4(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-separator': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-toggle-group': 1.0.4(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) + '@types/react': 17.0.80 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + dev: true + + /@radix-ui/react-use-callback-ref@1.0.1(@types/react@17.0.80)(react@18.3.1): resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==} peerDependencies: '@types/react': '*' @@ -3074,12 +3042,12 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.23.7 - '@types/react': 17.0.74 - react: 18.2.0 + '@babel/runtime': 7.24.4 + '@types/react': 17.0.80 + react: 18.3.1 dev: true - /@radix-ui/react-use-controllable-state@1.0.1(@types/react@17.0.74)(react@18.2.0): + /@radix-ui/react-use-controllable-state@1.0.1(@types/react@17.0.80)(react@18.3.1): resolution: {integrity: sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==} peerDependencies: '@types/react': '*' @@ -3088,13 +3056,13 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.23.7 - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@types/react': 17.0.74 - react: 18.2.0 + '@babel/runtime': 7.24.4 + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@types/react': 17.0.80 + react: 18.3.1 dev: true - /@radix-ui/react-use-escape-keydown@1.0.3(@types/react@17.0.74)(react@18.2.0): + /@radix-ui/react-use-escape-keydown@1.0.3(@types/react@17.0.80)(react@18.3.1): resolution: {integrity: sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==} peerDependencies: '@types/react': '*' @@ -3103,13 +3071,13 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.23.7 - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@types/react': 17.0.74 - react: 18.2.0 + '@babel/runtime': 7.24.4 + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@types/react': 17.0.80 + react: 18.3.1 dev: true - /@radix-ui/react-use-layout-effect@1.0.1(@types/react@17.0.74)(react@18.2.0): + /@radix-ui/react-use-layout-effect@1.0.1(@types/react@17.0.80)(react@18.3.1): resolution: {integrity: sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==} peerDependencies: '@types/react': '*' @@ -3118,12 +3086,12 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.23.7 - '@types/react': 17.0.74 - react: 18.2.0 + '@babel/runtime': 7.24.4 + '@types/react': 17.0.80 + react: 18.3.1 dev: true - /@radix-ui/react-use-previous@1.0.1(@types/react@17.0.74)(react@18.2.0): + /@radix-ui/react-use-previous@1.0.1(@types/react@17.0.80)(react@18.3.1): resolution: {integrity: sha512-cV5La9DPwiQ7S0gf/0qiD6YgNqM5Fk97Kdrlc5yBcrF3jyEZQwm7vYFqMo4IfeHgJXsRaMvLABFtd0OVEmZhDw==} peerDependencies: '@types/react': '*' @@ -3132,12 +3100,12 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.23.7 - '@types/react': 17.0.74 - react: 18.2.0 + '@babel/runtime': 7.24.4 + '@types/react': 17.0.80 + react: 18.3.1 dev: true - /@radix-ui/react-use-rect@1.0.1(@types/react@17.0.74)(react@18.2.0): + /@radix-ui/react-use-rect@1.0.1(@types/react@17.0.80)(react@18.3.1): resolution: {integrity: sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==} peerDependencies: '@types/react': '*' @@ -3146,13 +3114,13 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.23.7 + '@babel/runtime': 7.24.4 '@radix-ui/rect': 1.0.1 - '@types/react': 17.0.74 - react: 18.2.0 + '@types/react': 17.0.80 + react: 18.3.1 dev: true - /@radix-ui/react-use-size@1.0.1(@types/react@17.0.74)(react@18.2.0): + /@radix-ui/react-use-size@1.0.1(@types/react@17.0.80)(react@18.3.1): resolution: {integrity: sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==} peerDependencies: '@types/react': '*' @@ -3161,13 +3129,13 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.23.7 - '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@17.0.74)(react@18.2.0) - '@types/react': 17.0.74 - react: 18.2.0 + '@babel/runtime': 7.24.4 + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@types/react': 17.0.80 + react: 18.3.1 dev: true - /@radix-ui/react-visually-hidden@1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): + /@radix-ui/react-visually-hidden@1.0.3(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==} peerDependencies: '@types/react': '*' @@ -3180,61 +3148,61 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.23.7 - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@types/react': 17.0.74 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + '@babel/runtime': 7.24.4 + '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) + '@types/react': 17.0.80 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) dev: true /@radix-ui/rect@1.0.1: resolution: {integrity: sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==} dependencies: - '@babel/runtime': 7.23.7 + '@babel/runtime': 7.24.4 dev: true - /@react-aria/ssr@3.9.1(react@18.2.0): - resolution: {integrity: sha512-NqzkLFP8ZVI4GSorS0AYljC13QW2sc8bDqJOkBvkAt3M8gbcAXJWVRGtZBCRscki9RZF+rNlnPdg0G0jYkhJcg==} + /@react-aria/ssr@3.9.2(react@18.3.1): + resolution: {integrity: sha512-0gKkgDYdnq1w+ey8KzG9l+H5Z821qh9vVjztk55rUg71vTk/Eaebeir+WtzcLLwTjw3m/asIjx8Y59y1lJZhBw==} engines: {node: '>= 12'} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 dependencies: - '@swc/helpers': 0.5.3 - react: 18.2.0 + '@swc/helpers': 0.5.11 + react: 18.3.1 dev: false - /@react-aria/utils@3.17.0(react@18.2.0): + /@react-aria/utils@3.17.0(react@18.3.1): resolution: {integrity: sha512-NEul0cQ6tQPdNSHYzNYD+EfFabeYNvDwEiHB82kK/Tsfhfm84SM+baben/at2N51K7iRrJPr5hC5fi4+P88lNg==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 dependencies: - '@react-aria/ssr': 3.9.1(react@18.2.0) - '@react-stately/utils': 3.9.0(react@18.2.0) - '@react-types/shared': 3.22.0(react@18.2.0) + '@react-aria/ssr': 3.9.2(react@18.3.1) + '@react-stately/utils': 3.9.1(react@18.3.1) + '@react-types/shared': 3.22.1(react@18.3.1) '@swc/helpers': 0.4.36 clsx: 1.2.1 - react: 18.2.0 + react: 18.3.1 dev: false - /@react-stately/utils@3.9.0(react@18.2.0): - resolution: {integrity: sha512-yPKFY1F88HxuZ15BG2qwAYxtpE4HnIU0Ofi4CuBE0xC6I8mwo4OQjDzi+DZjxQngM9D6AeTTD6F1V8gkozA0Gw==} + /@react-stately/utils@3.9.1(react@18.3.1): + resolution: {integrity: sha512-yzw75GE0iUWiyps02BOAPTrybcsMIxEJlzXqtvllAb01O9uX5n0i3X+u2eCpj2UoDF4zS08Ps0jPgWxg8xEYtA==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 dependencies: - '@swc/helpers': 0.5.3 - react: 18.2.0 + '@swc/helpers': 0.5.11 + react: 18.3.1 dev: false - /@react-types/shared@3.22.0(react@18.2.0): - resolution: {integrity: sha512-yVOekZWbtSmmiThGEIARbBpnmUIuePFlLyctjvCbgJgGhz8JnEJOipLQ/a4anaWfzAgzSceQP8j/K+VOOePleA==} + /@react-types/shared@3.22.1(react@18.3.1): + resolution: {integrity: sha512-PCpa+Vo6BKnRMuOEzy5zAZ3/H5tnQg1e80khMhK2xys0j6ZqzkgQC+fHMNZ7VDFNLqqNMj/o0eVeSBDh2POjkw==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 dependencies: - react: 18.2.0 + react: 18.3.1 dev: false - /@remix-run/router@1.14.1: - resolution: {integrity: sha512-Qg4DMQsfPNAs88rb2xkdk03N3bjK4jgX5fR24eHCTR9q6PrhZQZ4UJBPzCHJkIpTRN1UKxx2DzjZmnC+7Lj0Ow==} + /@remix-run/router@1.16.0: + resolution: {integrity: sha512-Quz1KOffeEf/zwkCBM3kBtH4ZoZ+pT3xIXBG4PPW/XFtDP7EGhtTiC2+gpL9GnR7+Qdet5Oa6cYSvwKYg6kN9Q==} engines: {node: '>=14.0.0'} dev: true @@ -3252,104 +3220,128 @@ packages: picomatch: 2.3.1 dev: true - /@rollup/rollup-android-arm-eabi@4.9.4: - resolution: {integrity: sha512-ub/SN3yWqIv5CWiAZPHVS1DloyZsJbtXmX4HxUTIpS0BHm9pW5iYBo2mIZi+hE3AeiTzHz33blwSnhdUo+9NpA==} + /@rollup/rollup-android-arm-eabi@4.17.1: + resolution: {integrity: sha512-P6Wg856Ou/DLpR+O0ZLneNmrv7QpqBg+hK4wE05ijbC/t349BRfMfx+UFj5Ha3fCFopIa6iSZlpdaB4agkWp2Q==} cpu: [arm] os: [android] requiresBuild: true dev: true optional: true - /@rollup/rollup-android-arm64@4.9.4: - resolution: {integrity: sha512-ehcBrOR5XTl0W0t2WxfTyHCR/3Cq2jfb+I4W+Ch8Y9b5G+vbAecVv0Fx/J1QKktOrgUYsIKxWAKgIpvw56IFNA==} + /@rollup/rollup-android-arm64@4.17.1: + resolution: {integrity: sha512-piwZDjuW2WiHr05djVdUkrG5JbjnGbtx8BXQchYCMfib/nhjzWoiScelZ+s5IJI7lecrwSxHCzW026MWBL+oJQ==} cpu: [arm64] os: [android] requiresBuild: true dev: true optional: true - /@rollup/rollup-darwin-arm64@4.9.4: - resolution: {integrity: sha512-1fzh1lWExwSTWy8vJPnNbNM02WZDS8AW3McEOb7wW+nPChLKf3WG2aG7fhaUmfX5FKw9zhsF5+MBwArGyNM7NA==} + /@rollup/rollup-darwin-arm64@4.17.1: + resolution: {integrity: sha512-LsZXXIsN5Q460cKDT4Y+bzoPDhBmO5DTr7wP80d+2EnYlxSgkwdPfE3hbE+Fk8dtya+8092N9srjBTJ0di8RIA==} cpu: [arm64] os: [darwin] requiresBuild: true dev: true optional: true - /@rollup/rollup-darwin-x64@4.9.4: - resolution: {integrity: sha512-Gc6cukkF38RcYQ6uPdiXi70JB0f29CwcQ7+r4QpfNpQFVHXRd0DfWFidoGxjSx1DwOETM97JPz1RXL5ISSB0pA==} + /@rollup/rollup-darwin-x64@4.17.1: + resolution: {integrity: sha512-S7TYNQpWXB9APkxu/SLmYHezWwCoZRA9QLgrDeml+SR2A1LLPD2DBUdUlvmCF7FUpRMKvbeeWky+iizQj65Etw==} cpu: [x64] os: [darwin] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm-gnueabihf@4.9.4: - resolution: {integrity: sha512-g21RTeFzoTl8GxosHbnQZ0/JkuFIB13C3T7Y0HtKzOXmoHhewLbVTFBQZu+z5m9STH6FZ7L/oPgU4Nm5ErN2fw==} + /@rollup/rollup-linux-arm-gnueabihf@4.17.1: + resolution: {integrity: sha512-Lq2JR5a5jsA5um2ZoLiXXEaOagnVyCpCW7xvlcqHC7y46tLwTEgUSTM3a2TfmmTMmdqv+jknUioWXlmxYxE9Yw==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm-musleabihf@4.17.1: + resolution: {integrity: sha512-9BfzwyPNV0IizQoR+5HTNBGkh1KXE8BqU0DBkqMngmyFW7BfuIZyMjQ0s6igJEiPSBvT3ZcnIFohZ19OqjhDPg==} cpu: [arm] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm64-gnu@4.9.4: - resolution: {integrity: sha512-TVYVWD/SYwWzGGnbfTkrNpdE4HON46orgMNHCivlXmlsSGQOx/OHHYiQcMIOx38/GWgwr/po2LBn7wypkWw/Mg==} + /@rollup/rollup-linux-arm64-gnu@4.17.1: + resolution: {integrity: sha512-e2uWaoxo/rtzA52OifrTSXTvJhAXb0XeRkz4CdHBK2KtxrFmuU/uNd544Ogkpu938BzEfvmWs8NZ8Axhw33FDw==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm64-musl@4.9.4: - resolution: {integrity: sha512-XcKvuendwizYYhFxpvQ3xVpzje2HHImzg33wL9zvxtj77HvPStbSGI9czrdbfrf8DGMcNNReH9pVZv8qejAQ5A==} + /@rollup/rollup-linux-arm64-musl@4.17.1: + resolution: {integrity: sha512-ekggix/Bc/d/60H1Mi4YeYb/7dbal1kEDZ6sIFVAE8pUSx7PiWeEh+NWbL7bGu0X68BBIkgF3ibRJe1oFTksQQ==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-riscv64-gnu@4.9.4: - resolution: {integrity: sha512-LFHS/8Q+I9YA0yVETyjonMJ3UA+DczeBd/MqNEzsGSTdNvSJa1OJZcSH8GiXLvcizgp9AlHs2walqRcqzjOi3A==} + /@rollup/rollup-linux-powerpc64le-gnu@4.17.1: + resolution: {integrity: sha512-UGV0dUo/xCv4pkr/C8KY7XLFwBNnvladt8q+VmdKrw/3RUd3rD0TptwjisvE2TTnnlENtuY4/PZuoOYRiGp8Gw==} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-riscv64-gnu@4.17.1: + resolution: {integrity: sha512-gEYmYYHaehdvX46mwXrU49vD6Euf1Bxhq9pPb82cbUU9UT2NV+RSckQ5tKWOnNXZixKsy8/cPGtiUWqzPuAcXQ==} cpu: [riscv64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-x64-gnu@4.9.4: - resolution: {integrity: sha512-dIYgo+j1+yfy81i0YVU5KnQrIJZE8ERomx17ReU4GREjGtDW4X+nvkBak2xAUpyqLs4eleDSj3RrV72fQos7zw==} + /@rollup/rollup-linux-s390x-gnu@4.17.1: + resolution: {integrity: sha512-xeae5pMAxHFp6yX5vajInG2toST5lsCTrckSRUFwNgzYqnUjNBcQyqk1bXUxX5yhjWFl2Mnz3F8vQjl+2FRIcw==} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-x64-gnu@4.17.1: + resolution: {integrity: sha512-AsdnINQoDWfKpBzCPqQWxSPdAWzSgnYbrJYtn6W0H2E9It5bZss99PiLA8CgmDRfvKygt20UpZ3xkhFlIfX9zQ==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-x64-musl@4.9.4: - resolution: {integrity: sha512-RoaYxjdHQ5TPjaPrLsfKqR3pakMr3JGqZ+jZM0zP2IkDtsGa4CqYaWSfQmZVgFUCgLrTnzX+cnHS3nfl+kB6ZQ==} + /@rollup/rollup-linux-x64-musl@4.17.1: + resolution: {integrity: sha512-KoB4fyKXTR+wYENkIG3fFF+5G6N4GFvzYx8Jax8BR4vmddtuqSb5oQmYu2Uu067vT/Fod7gxeQYKupm8gAcMSQ==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-arm64-msvc@4.9.4: - resolution: {integrity: sha512-T8Q3XHV+Jjf5e49B4EAaLKV74BbX7/qYBRQ8Wop/+TyyU0k+vSjiLVSHNWdVd1goMjZcbhDmYZUYW5RFqkBNHQ==} + /@rollup/rollup-win32-arm64-msvc@4.17.1: + resolution: {integrity: sha512-J0d3NVNf7wBL9t4blCNat+d0PYqAx8wOoY+/9Q5cujnafbX7BmtYk3XvzkqLmFECaWvXGLuHmKj/wrILUinmQg==} cpu: [arm64] os: [win32] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-ia32-msvc@4.9.4: - resolution: {integrity: sha512-z+JQ7JirDUHAsMecVydnBPWLwJjbppU+7LZjffGf+Jvrxq+dVjIE7By163Sc9DKc3ADSU50qPVw0KonBS+a+HQ==} + /@rollup/rollup-win32-ia32-msvc@4.17.1: + resolution: {integrity: sha512-xjgkWUwlq7IbgJSIxvl516FJ2iuC/7ttjsAxSPpC9kkI5iQQFHKyEN5BjbhvJ/IXIZ3yIBcW5QDlWAyrA+TFag==} cpu: [ia32] os: [win32] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-x64-msvc@4.9.4: - resolution: {integrity: sha512-LfdGXCV9rdEify1oxlN9eamvDSjv9md9ZVMAbNHA87xqIfFCxImxan9qZ8+Un54iK2nnqPlbnSi4R54ONtbWBw==} + /@rollup/rollup-win32-x64-msvc@4.17.1: + resolution: {integrity: sha512-0QbCkfk6cnnVKWqqlC0cUrrUMDMfu5ffvYMTUHf+qMN2uAb3MKP31LPcwiMXBNsvoFGs/kYdFOsuLmvppCopXA==} cpu: [x64] os: [win32] requiresBuild: true @@ -3360,8 +3352,8 @@ packages: resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} dev: true - /@sinonjs/commons@3.0.0: - resolution: {integrity: sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==} + /@sinonjs/commons@3.0.1: + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} dependencies: type-detect: 4.0.8 dev: true @@ -3369,39 +3361,39 @@ packages: /@sinonjs/fake-timers@10.3.0: resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} dependencies: - '@sinonjs/commons': 3.0.0 + '@sinonjs/commons': 3.0.1 dev: true - /@storybook/addon-a11y@7.6.7: - resolution: {integrity: sha512-poT2oXIYDwLnhqn6g9ACTQ+7gi8QDHVlib4TQANdcozC/qYg+Bs6Pd99wT6rT4lrC/npVNTSKKwLw+3oXqlCxg==} + /@storybook/addon-a11y@7.6.18: + resolution: {integrity: sha512-eTnj/eAh6Lzh5gZ5y/wn5qoGrei9tpKz/HliS4AxEKUlAG80LVXyDooYA+EXN1vo/9EH3TiiS01roRJOGdzTUA==} dependencies: - '@storybook/addon-highlight': 7.6.7 - axe-core: 4.8.3 + '@storybook/addon-highlight': 7.6.18 + axe-core: 4.9.0 dev: true - /@storybook/addon-actions@7.6.7: - resolution: {integrity: sha512-+6EZvhIeKEqG/RNsU3R5DxOrd60BL5GEvmzE2w60s2eKaNNxtyilDjiO1g4z2s2zDNyr7JL/Ft03pJ0Jgo0lew==} + /@storybook/addon-actions@7.6.18: + resolution: {integrity: sha512-HWS2NqUNH7FGG5QyWMvV3aw2IcwXw6xQwCx2xLUD7fJFqCAf4cDXZIsGnTVHCtoddVRBIlcS+LRmiGU8+mQKdw==} dependencies: - '@storybook/core-events': 7.6.7 + '@storybook/core-events': 7.6.18 '@storybook/global': 5.0.0 - '@types/uuid': 9.0.7 + '@types/uuid': 9.0.8 dequal: 2.0.3 polished: 4.3.1 uuid: 9.0.1 dev: true - /@storybook/addon-backgrounds@7.6.7: - resolution: {integrity: sha512-55sBy1YUqponAVe+qL16qtWxdf63vHEnIoqFyHEwGpk7K9IhFA1BmdSpFr5VnWEwXeJXKj30db78frh2LUdk3Q==} + /@storybook/addon-backgrounds@7.6.18: + resolution: {integrity: sha512-Bai0n3RfO+PmsQ69KdRhPvuwCistNLvpKtAEzo9nlpHfYh921OgVfZrKFfWJgYskvyVlaNu0DeR3t6TT8CbT/A==} dependencies: '@storybook/global': 5.0.0 memoizerific: 1.11.3 ts-dedent: 2.2.0 dev: true - /@storybook/addon-controls@7.6.7(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-DJ3gfvcdCgqi7AQxu83vx0AEUKiuJrNcSATfWV3Jqi8dH6fYO2yqpemHEeWOEy+DAHxIOaqLKwb1QjIBj+vSRQ==} + /@storybook/addon-controls@7.6.18(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-iH/JbltgjDFihRppeniNlGE3Qc86Q5oW8+p77E9B0ILn3yGk3rNOSlOTUg7a1seMjddJfsptDn4xMFHuunYuyQ==} dependencies: - '@storybook/blocks': 7.6.7(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) + '@storybook/blocks': 7.6.18(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) lodash: 4.17.21 ts-dedent: 2.2.0 transitivePeerDependencies: @@ -3413,30 +3405,30 @@ packages: - supports-color dev: true - /@storybook/addon-docs@7.6.7(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-2dfajNhweofJ3LxjGO83UE5sBMvKtJB0Agj7q8mMtK/9PUCUcbvsFSyZnO/s6X1zAjSn5ZrirbSoTXU4IqxwSA==} + /@storybook/addon-docs@7.6.18(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-+JzGL5ImwZ5VE+PiEUzRHWKbgvFsg/G2OTzyqZD8vQ+NlB6rmKGzGpXz0c4D6xEupzIJwjbpSN2ZOzgld0Du9Q==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: '@jest/transform': 29.7.0 - '@mdx-js/react': 2.3.0(react@18.2.0) - '@storybook/blocks': 7.6.7(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@storybook/client-logger': 7.6.7 - '@storybook/components': 7.6.7(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@storybook/csf-plugin': 7.6.7 - '@storybook/csf-tools': 7.6.7 + '@mdx-js/react': 2.3.0(react@18.3.1) + '@storybook/blocks': 7.6.18(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) + '@storybook/client-logger': 7.6.18 + '@storybook/components': 7.6.18(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) + '@storybook/csf-plugin': 7.6.18 + '@storybook/csf-tools': 7.6.18 '@storybook/global': 5.0.0 '@storybook/mdx2-csf': 1.1.0 - '@storybook/node-logger': 7.6.7 - '@storybook/postinstall': 7.6.7 - '@storybook/preview-api': 7.6.7 - '@storybook/react-dom-shim': 7.6.7(react-dom@18.2.0)(react@18.2.0) - '@storybook/theming': 7.6.7(react-dom@18.2.0)(react@18.2.0) - '@storybook/types': 7.6.7 + '@storybook/node-logger': 7.6.18 + '@storybook/postinstall': 7.6.18 + '@storybook/preview-api': 7.6.18 + '@storybook/react-dom-shim': 7.6.18(react-dom@18.3.1)(react@18.3.1) + '@storybook/theming': 7.6.18(react-dom@18.3.1)(react@18.3.1) + '@storybook/types': 7.6.18 fs-extra: 11.2.0 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) remark-external-links: 8.0.0 remark-slug: 6.1.0 ts-dedent: 2.2.0 @@ -3447,27 +3439,27 @@ packages: - supports-color dev: true - /@storybook/addon-essentials@7.6.7(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-nNLMrpIvc04z4XCA+kval/44eKAFJlUJeeL2pxwP7F/PSzjWe5BXv1bQHOiw8inRO5II0PzqwWnVCI9jsj7K5A==} + /@storybook/addon-essentials@7.6.18(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-qgVH442LhIdzCbx0E+eB1+xTj1TOKqSqrUy76viILCK1wfMSeIsU8TNkqnc8hzUQH2IatUJb/t76wXh2eV9s4w==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - '@storybook/addon-actions': 7.6.7 - '@storybook/addon-backgrounds': 7.6.7 - '@storybook/addon-controls': 7.6.7(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@storybook/addon-docs': 7.6.7(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@storybook/addon-highlight': 7.6.7 - '@storybook/addon-measure': 7.6.7 - '@storybook/addon-outline': 7.6.7 - '@storybook/addon-toolbars': 7.6.7 - '@storybook/addon-viewport': 7.6.7 - '@storybook/core-common': 7.6.7 - '@storybook/manager-api': 7.6.7(react-dom@18.2.0)(react@18.2.0) - '@storybook/node-logger': 7.6.7 - '@storybook/preview-api': 7.6.7 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + '@storybook/addon-actions': 7.6.18 + '@storybook/addon-backgrounds': 7.6.18 + '@storybook/addon-controls': 7.6.18(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) + '@storybook/addon-docs': 7.6.18(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) + '@storybook/addon-highlight': 7.6.18 + '@storybook/addon-measure': 7.6.18 + '@storybook/addon-outline': 7.6.18 + '@storybook/addon-toolbars': 7.6.18 + '@storybook/addon-viewport': 7.6.18 + '@storybook/core-common': 7.6.18 + '@storybook/manager-api': 7.6.18(react-dom@18.3.1)(react@18.3.1) + '@storybook/node-logger': 7.6.18 + '@storybook/preview-api': 7.6.18 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) ts-dedent: 2.2.0 transitivePeerDependencies: - '@types/react' @@ -3476,65 +3468,65 @@ packages: - supports-color dev: true - /@storybook/addon-highlight@7.6.7: - resolution: {integrity: sha512-2F/tJdn45d4zrvf/cmE1vsczl99wK8+I+kkj0G7jLsrJR0w1zTgbgjy6T9j86HBTBvWcnysNFNIRWPAOh5Wdbw==} + /@storybook/addon-highlight@7.6.18: + resolution: {integrity: sha512-XUR9sTcxqYbes9ckj1b/GyAJ3yFfE/2YnvPFz8vWO9hIZjlL0Wvyiy/1L2DePF1S+zHrYA8+dg65vK8pMXUrnQ==} dependencies: '@storybook/global': 5.0.0 dev: true - /@storybook/addon-measure@7.6.7: - resolution: {integrity: sha512-t1RnnNO4Xzgnsxu63FlZwsCTF0+9jKxr44NiJAUOxW9ppbCvs/JfSDOOvcDRtPWyjgnyzexNUUctMfxvLrU01A==} + /@storybook/addon-measure@7.6.18: + resolution: {integrity: sha512-ixEW/RG3iJCiyJQ51vKqlTJHq6vJ7O/xHGGMFV9+RYP0S2klZctQQwLZxUWUjSLUUjCX/DrxVlmK03h+7f+wWA==} dependencies: '@storybook/global': 5.0.0 - tiny-invariant: 1.3.1 + tiny-invariant: 1.3.3 dev: true - /@storybook/addon-outline@7.6.7: - resolution: {integrity: sha512-gu2y46ijjMkXlxy1f8Cctgjw5b5y8vSIqNAYlrs5/Qy+hJAWyU6lj2PFGOCCUG4L+F45fAjwWAin6qz43+WnRQ==} + /@storybook/addon-outline@7.6.18: + resolution: {integrity: sha512-YKHjir/+KZH0P/F8spmm9l/EC28VXlE0beAxeErvpPiA6t1Ykrh7GEPvPEolY1DydKBaLLnd20adLhDskl+oGg==} dependencies: '@storybook/global': 5.0.0 ts-dedent: 2.2.0 dev: true - /@storybook/addon-toolbars@7.6.7: - resolution: {integrity: sha512-vT+YMzw8yVwndhJglI0XtELfXWq1M0HEy5ST3XPzbjmsJ54LgTf1b29UMkh0E/05qBQNFCcbT9B/tLxqWezxlg==} + /@storybook/addon-toolbars@7.6.18: + resolution: {integrity: sha512-AlqW8rA5gNtxjbTyJtJlVfmqbcSJAWFHTvC7OfwbZRZLmF5agdBUQeAZYI75WBZpdlYrp23s88O+MRMa/CF2yA==} dev: true - /@storybook/addon-viewport@7.6.7: - resolution: {integrity: sha512-Q/BKjJaKzl4RWxH45K2iIXwkicj4ReVAUIpIyd7dPBb/Bx+hEDYZxR5dDg82AMkZdA71x5ttMnuDSuVpmWAE6g==} + /@storybook/addon-viewport@7.6.18: + resolution: {integrity: sha512-fgn38aXappEeDNg5u52fswhjkNN5Sru6Rf/2WhuuQXteIC2tX27J03Ud8h2aKydzHai7zz8jJ0IoGt7cA6W0Nw==} dependencies: memoizerific: 1.11.3 dev: true - /@storybook/blocks@7.6.7(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-+QEvGQ0he/YvFS3lsZORJWxhQIyqcCDWsxbJxJiByePd+Z4my3q8xwtPhHW0TKRL0xUgNE/GnTfMMqJfevTuSw==} + /@storybook/blocks@7.6.18(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-mCEyGew2nyiFwJ1iHfm4ItB/bDrVzYUODkKktmHDmJJgjKFIDQJPTgLsiQhXBtxqW0TImL4JpSU/aUAAbXpZeg==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - '@storybook/channels': 7.6.7 - '@storybook/client-logger': 7.6.7 - '@storybook/components': 7.6.7(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@storybook/core-events': 7.6.7 - '@storybook/csf': 0.1.2 - '@storybook/docs-tools': 7.6.7 + '@storybook/channels': 7.6.18 + '@storybook/client-logger': 7.6.18 + '@storybook/components': 7.6.18(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) + '@storybook/core-events': 7.6.18 + '@storybook/csf': 0.1.5 + '@storybook/docs-tools': 7.6.18 '@storybook/global': 5.0.0 - '@storybook/manager-api': 7.6.7(react-dom@18.2.0)(react@18.2.0) - '@storybook/preview-api': 7.6.7 - '@storybook/theming': 7.6.7(react-dom@18.2.0)(react@18.2.0) - '@storybook/types': 7.6.7 - '@types/lodash': 4.14.202 + '@storybook/manager-api': 7.6.18(react-dom@18.3.1)(react@18.3.1) + '@storybook/preview-api': 7.6.18 + '@storybook/theming': 7.6.18(react-dom@18.3.1)(react@18.3.1) + '@storybook/types': 7.6.18 + '@types/lodash': 4.17.0 color-convert: 2.0.1 dequal: 2.0.3 lodash: 4.17.21 - markdown-to-jsx: 7.4.0(react@18.2.0) + markdown-to-jsx: 7.4.7(react@18.3.1) memoizerific: 1.11.3 polished: 4.3.1 - react: 18.2.0 - react-colorful: 5.6.1(react-dom@18.2.0)(react@18.2.0) - react-dom: 18.2.0(react@18.2.0) + react: 18.3.1 + react-colorful: 5.6.1(react-dom@18.3.1)(react@18.3.1) + react-dom: 18.3.1(react@18.3.1) telejson: 7.2.0 - tocbot: 4.25.0 + tocbot: 4.27.13 ts-dedent: 2.2.0 util-deprecate: 1.0.2 transitivePeerDependencies: @@ -3544,21 +3536,21 @@ packages: - supports-color dev: true - /@storybook/builder-manager@7.6.7: - resolution: {integrity: sha512-6HYpj6+g/qbDMvImVz/G/aANbkhppyBa1ozfHxLK7tRD79YvozCWmj2Z9umRekPv9VIeMxnI5EEzJXOsoMX5DQ==} + /@storybook/builder-manager@7.6.18: + resolution: {integrity: sha512-kXnC/lDA3zUeXgwAoHKed+CXbDcKV8GJ6qrPCw1D1a3ug5Lw5DYPBJC/KP3CgNpVx6vukkeEIwKYg2M+LRmI6g==} dependencies: '@fal-works/esbuild-plugin-global-externals': 2.1.2 - '@storybook/core-common': 7.6.7 - '@storybook/manager': 7.6.7 - '@storybook/node-logger': 7.6.7 + '@storybook/core-common': 7.6.18 + '@storybook/manager': 7.6.18 + '@storybook/node-logger': 7.6.18 '@types/ejs': 3.1.5 '@types/find-cache-dir': 3.2.1 '@yarnpkg/esbuild-plugin-pnp': 3.0.0-rc.15(esbuild@0.18.20) browser-assert: 1.2.1 - ejs: 3.1.9 + ejs: 3.1.10 esbuild: 0.18.20 esbuild-plugin-alias: 0.2.1 - express: 4.18.2 + express: 4.19.2 find-cache-dir: 3.3.2 fs-extra: 11.2.0 process: 0.11.10 @@ -3568,8 +3560,8 @@ packages: - supports-color dev: true - /@storybook/builder-vite@7.6.7(typescript@4.9.5)(vite@4.5.1): - resolution: {integrity: sha512-Sv+0ROFU9k+mkvIPsPHC0lkKDzBeMpvfO9uFRl1RDSsXBfcPPZKNo5YK7U7fOhesH0BILzurGA+U/aaITMSZ9g==} + /@storybook/builder-vite@7.6.18(typescript@4.9.5)(vite@4.5.3): + resolution: {integrity: sha512-f3chnC6ug9KJDz3Z+HNl8yhJ/SUT0ASdJjKViVJ90MKKyFpeCvzs2DSgMGv2UJrPfBMh6PhFM2dy26+LksioCQ==} peerDependencies: '@preact/preset-vite': '*' typescript: '>= 4.3.x' @@ -3583,81 +3575,91 @@ packages: vite-plugin-glimmerx: optional: true dependencies: - '@storybook/channels': 7.6.7 - '@storybook/client-logger': 7.6.7 - '@storybook/core-common': 7.6.7 - '@storybook/csf-plugin': 7.6.7 - '@storybook/node-logger': 7.6.7 - '@storybook/preview': 7.6.7 - '@storybook/preview-api': 7.6.7 - '@storybook/types': 7.6.7 + '@storybook/channels': 7.6.18 + '@storybook/client-logger': 7.6.18 + '@storybook/core-common': 7.6.18 + '@storybook/csf-plugin': 7.6.18 + '@storybook/node-logger': 7.6.18 + '@storybook/preview': 7.6.18 + '@storybook/preview-api': 7.6.18 + '@storybook/types': 7.6.18 '@types/find-cache-dir': 3.2.1 browser-assert: 1.2.1 es-module-lexer: 0.9.3 - express: 4.18.2 + express: 4.19.2 find-cache-dir: 3.3.2 fs-extra: 11.2.0 - magic-string: 0.30.5 + magic-string: 0.30.10 rollup: 3.29.4 typescript: 4.9.5 - vite: 4.5.1 + vite: 4.5.3 transitivePeerDependencies: - encoding - supports-color dev: true - /@storybook/channels@7.6.7: - resolution: {integrity: sha512-u1hURhfQHHtZyRIDUENRCp+CRRm7IQfcjQaoWI06XCevQPuhVEtFUfXHjG+J74aA/JuuTLFUtqwNm1zGqbXTAQ==} + /@storybook/channels@7.6.17: + resolution: {integrity: sha512-GFG40pzaSxk1hUr/J/TMqW5AFDDPUSu+HkeE/oqSWJbOodBOLJzHN6CReJS6y1DjYSZLNFt1jftPWZZInG/XUA==} + dependencies: + '@storybook/client-logger': 7.6.17 + '@storybook/core-events': 7.6.17 + '@storybook/global': 5.0.0 + qs: 6.12.1 + telejson: 7.2.0 + tiny-invariant: 1.3.3 + dev: true + + /@storybook/channels@7.6.18: + resolution: {integrity: sha512-ayMJ6GJot81URJySXcwZG1mLacblUVdLgAMIhU7oSW1K1v4KvQPxv3FqjNN+48g/1s+2A9UraCDqN0qzO3wznQ==} dependencies: - '@storybook/client-logger': 7.6.7 - '@storybook/core-events': 7.6.7 + '@storybook/client-logger': 7.6.18 + '@storybook/core-events': 7.6.18 '@storybook/global': 5.0.0 - qs: 6.11.2 + qs: 6.12.1 telejson: 7.2.0 - tiny-invariant: 1.3.1 + tiny-invariant: 1.3.3 dev: true - /@storybook/cli@7.6.7: - resolution: {integrity: sha512-DwDWzkifBH17ry+n+d+u52Sv69dZQ+04ETJdDDzghcyAcKnFzrRNukj4tJ21cm+ZAU/r0fKR9d4Qpbogca9fAg==} + /@storybook/cli@7.6.18: + resolution: {integrity: sha512-2zlCyX4m1Jb3p+P/Z+7ioa7cXA+Sv+j0JevUWaaVZbBLrjj/G2k5bYzgrks0FhQZ6MLv5bkuZPGtJMgWQ8+c3Q==} hasBin: true dependencies: - '@babel/core': 7.23.7 - '@babel/preset-env': 7.23.7(@babel/core@7.23.7) - '@babel/types': 7.23.6 + '@babel/core': 7.24.4 + '@babel/preset-env': 7.24.4(@babel/core@7.24.4) + '@babel/types': 7.24.0 '@ndelangen/get-tarball': 3.0.9 - '@storybook/codemod': 7.6.7 - '@storybook/core-common': 7.6.7 - '@storybook/core-events': 7.6.7 - '@storybook/core-server': 7.6.7 - '@storybook/csf-tools': 7.6.7 - '@storybook/node-logger': 7.6.7 - '@storybook/telemetry': 7.6.7 - '@storybook/types': 7.6.7 - '@types/semver': 7.5.6 + '@storybook/codemod': 7.6.18 + '@storybook/core-common': 7.6.18 + '@storybook/core-events': 7.6.18 + '@storybook/core-server': 7.6.18 + '@storybook/csf-tools': 7.6.18 + '@storybook/node-logger': 7.6.18 + '@storybook/telemetry': 7.6.18 + '@storybook/types': 7.6.18 + '@types/semver': 7.5.8 '@yarnpkg/fslib': 2.10.3 '@yarnpkg/libzip': 2.3.0 chalk: 4.1.2 commander: 6.2.1 cross-spawn: 7.0.3 detect-indent: 6.1.0 - envinfo: 7.11.0 + envinfo: 7.13.0 execa: 5.1.1 - express: 4.18.2 + express: 4.19.2 find-up: 5.0.0 fs-extra: 11.2.0 get-npm-tarball-url: 2.1.0 get-port: 5.1.1 - giget: 1.2.1 + giget: 1.2.3 globby: 11.1.0 - jscodeshift: 0.15.1(@babel/preset-env@7.23.7) + jscodeshift: 0.15.2(@babel/preset-env@7.24.4) leven: 3.1.0 ora: 5.4.1 prettier: 2.8.8 prompts: 2.4.2 puppeteer-core: 2.1.1 read-pkg-up: 7.0.1 - semver: 7.5.4 - simple-update-notifier: 2.0.0 + semver: 7.6.0 strip-json-comments: 3.1.1 tempy: 1.0.1 ts-dedent: 2.2.0 @@ -3669,79 +3671,85 @@ packages: - utf-8-validate dev: true - /@storybook/client-api@7.6.7: - resolution: {integrity: sha512-nhQL2dtr6/MHY+FPmK55lyLR0by+J6wnfbs8znkSC08zhRXwHBYQpX7TrBwq2RrmzWbIcqgWvIHpbDsKonMNjA==} + /@storybook/client-api@7.6.17: + resolution: {integrity: sha512-rsxKBRLtUmBXbxG79Pf1GzUuMDMsFdhNR/a5k7kIA/mlEsvWD8are/aH/zk1oLr7+5QOqEkiXLL6+Erry7dzXA==} + dependencies: + '@storybook/client-logger': 7.6.17 + '@storybook/preview-api': 7.6.17 + dev: true + + /@storybook/client-logger@7.6.17: + resolution: {integrity: sha512-6WBYqixAXNAXlSaBWwgljWpAu10tPRBJrcFvx2gPUne58EeMM20Gi/iHYBz2kMCY+JLAgeIH7ZxInqwO8vDwiQ==} dependencies: - '@storybook/client-logger': 7.6.7 - '@storybook/preview-api': 7.6.7 + '@storybook/global': 5.0.0 dev: true - /@storybook/client-logger@7.6.7: - resolution: {integrity: sha512-A16zpWgsa0gSdXMR9P3bWVdC9u/1B1oG4H7Z1+JhNzgnL3CdyOYO0qFSiAtNBso4nOjIAJVb6/AoBzdRhmSVQg==} + /@storybook/client-logger@7.6.18: + resolution: {integrity: sha512-/mSKa968G++M7RTW1XLM0jgNMUATxKv/vggLyQ9Oo2UpQhRaXX8dKRl7GVu2yFDRm9sDKs7rg+KSsstrEjQcSg==} dependencies: '@storybook/global': 5.0.0 dev: true - /@storybook/codemod@7.6.7: - resolution: {integrity: sha512-an2pD5OHqO7CE8Wb7JxjrDnpQgeoxB22MyOs8PPJ9Rvclhpjg+Ku9RogoObYm//zR4g406l7Ec8mTltUkVCEOA==} + /@storybook/codemod@7.6.18: + resolution: {integrity: sha512-XV9/oZYctRKQzllqjwcH17Fys91cmaL+/Vy9aJmpnv/+yNFUdvsyrjqEGfVpl5c00/Ge3ueP+y7YhLYSjTezUg==} dependencies: - '@babel/core': 7.23.7 - '@babel/preset-env': 7.23.7(@babel/core@7.23.7) - '@babel/types': 7.23.6 - '@storybook/csf': 0.1.2 - '@storybook/csf-tools': 7.6.7 - '@storybook/node-logger': 7.6.7 - '@storybook/types': 7.6.7 + '@babel/core': 7.24.4 + '@babel/preset-env': 7.24.4(@babel/core@7.24.4) + '@babel/types': 7.24.0 + '@storybook/csf': 0.1.5 + '@storybook/csf-tools': 7.6.18 + '@storybook/node-logger': 7.6.18 + '@storybook/types': 7.6.18 '@types/cross-spawn': 6.0.6 cross-spawn: 7.0.3 globby: 11.1.0 - jscodeshift: 0.15.1(@babel/preset-env@7.23.7) + jscodeshift: 0.15.2(@babel/preset-env@7.24.4) lodash: 4.17.21 prettier: 2.8.8 - recast: 0.23.4 + recast: 0.23.6 transitivePeerDependencies: - supports-color dev: true - /@storybook/components@7.6.7(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-1HN4p+MCI4Tx9VGZayZyqbW7SB7mXQLnS5fUbTE1gXaMYHpzFvcrRNROeV1LZPClJX6qx1jgE5ngZojhxGuxMA==} + /@storybook/components@7.6.18(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-t27jyQUTkLgpQc2b7AQ848MJkihOfTgXsDIIMW1sYixqYO1R2anWE2qF5+1ZXZ58xyQEbUWnWUNYrGj3jGwAOw==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - '@radix-ui/react-select': 1.2.2(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-toolbar': 1.0.4(@types/react@17.0.74)(react-dom@18.2.0)(react@18.2.0) - '@storybook/client-logger': 7.6.7 - '@storybook/csf': 0.1.2 + '@radix-ui/react-select': 1.2.2(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-toolbar': 1.0.4(@types/react@17.0.80)(react-dom@18.3.1)(react@18.3.1) + '@storybook/client-logger': 7.6.18 + '@storybook/csf': 0.1.5 '@storybook/global': 5.0.0 - '@storybook/theming': 7.6.7(react-dom@18.2.0)(react@18.2.0) - '@storybook/types': 7.6.7 + '@storybook/theming': 7.6.18(react-dom@18.3.1)(react@18.3.1) + '@storybook/types': 7.6.18 memoizerific: 1.11.3 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - use-resize-observer: 9.1.0(react-dom@18.2.0)(react@18.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + use-resize-observer: 9.1.0(react-dom@18.3.1)(react@18.3.1) util-deprecate: 1.0.2 transitivePeerDependencies: - '@types/react' - '@types/react-dom' dev: true - /@storybook/core-client@7.6.7: - resolution: {integrity: sha512-ZQivyEzYsZok8vRj5Qan7LbiMUnO89rueWzTnZs4IS6JIaQtjoPI1rGVq+h6qOCM6tki478hic8FS+zwGQ6q+w==} + /@storybook/core-client@7.6.18: + resolution: {integrity: sha512-gKelPHlE4Xr8mkC0q1CotxB1hoR54P94LeJ6NrmNp2W8vZLiV8d/3CShJwTyEEkhhOB8diEGyya2LawboMYPpg==} dependencies: - '@storybook/client-logger': 7.6.7 - '@storybook/preview-api': 7.6.7 + '@storybook/client-logger': 7.6.18 + '@storybook/preview-api': 7.6.18 dev: true - /@storybook/core-common@7.6.7: - resolution: {integrity: sha512-F1fJnauVSPQtAlpicbN/O4XW38Ai8kf/IoU0Hgm9gEwurIk6MF5hiVLsaTI/5GUbrepMl9d9J+iIL4lHAT8IyA==} + /@storybook/core-common@7.6.18: + resolution: {integrity: sha512-ZZbvjpDKs3KPyoUWLTaMn8/0N2S8tXZpMfdrZrHHOzy9O3mmbk2Silr1OytWS6CBICFgDb71p7EWZ026KOVNkA==} dependencies: - '@storybook/core-events': 7.6.7 - '@storybook/node-logger': 7.6.7 - '@storybook/types': 7.6.7 + '@storybook/core-events': 7.6.18 + '@storybook/node-logger': 7.6.18 + '@storybook/types': 7.6.18 '@types/find-cache-dir': 3.2.1 - '@types/node': 18.19.5 - '@types/node-fetch': 2.6.10 + '@types/node': 18.19.31 + '@types/node-fetch': 2.6.11 '@types/pretty-hrtime': 1.0.3 chalk: 4.1.2 esbuild: 0.18.20 @@ -3750,7 +3758,7 @@ packages: find-cache-dir: 3.3.2 find-up: 5.0.0 fs-extra: 11.2.0 - glob: 10.3.10 + glob: 10.3.12 handlebars: 4.7.8 lazy-universal-dotenv: 4.0.0 node-fetch: 2.7.0 @@ -3764,56 +3772,62 @@ packages: - supports-color dev: true - /@storybook/core-events@7.6.7: - resolution: {integrity: sha512-KZ5d03c47pnr5/kY26pJtWq7WpmCPXLbgyjJZDSc+TTY153BdZksvlBXRHtqM1yj2UM6QsSyIuiJaADJNAbP2w==} + /@storybook/core-events@7.6.17: + resolution: {integrity: sha512-AriWMCm/k1cxlv10f+jZ1wavThTRpLaN3kY019kHWbYT9XgaSuLU67G7GPr3cGnJ6HuA6uhbzu8qtqVCd6OfXA==} + dependencies: + ts-dedent: 2.2.0 + dev: true + + /@storybook/core-events@7.6.18: + resolution: {integrity: sha512-K4jrHedFRfokvkIfKfNtQTcguPzeWF3oiuyXQR4gv4bnMCndCoiSRKfCE5zesgGmfml/Krt2zb4nNz/UPLbDeA==} dependencies: ts-dedent: 2.2.0 dev: true - /@storybook/core-server@7.6.7: - resolution: {integrity: sha512-elKRv/DNahNNkGcQY/FdOBrLPmZF0T0fwmAmbc4qqeAisjl+to9TO77zdo2ieaEHKyRwE3B3dOB4EXomdF4N/g==} + /@storybook/core-server@7.6.18: + resolution: {integrity: sha512-LXsbVqsHHcF/9mCcCDebRUO+ZuvK10Xtrgt8KJfAuWGU2nj8D2sJLw7suuDEB7UBTNMsJMOAmyrVU9FQbfWLCQ==} dependencies: '@aw-web-design/x-default-browser': 1.4.126 '@discoveryjs/json-ext': 0.5.7 - '@storybook/builder-manager': 7.6.7 - '@storybook/channels': 7.6.7 - '@storybook/core-common': 7.6.7 - '@storybook/core-events': 7.6.7 - '@storybook/csf': 0.1.2 - '@storybook/csf-tools': 7.6.7 + '@storybook/builder-manager': 7.6.18 + '@storybook/channels': 7.6.18 + '@storybook/core-common': 7.6.18 + '@storybook/core-events': 7.6.18 + '@storybook/csf': 0.1.5 + '@storybook/csf-tools': 7.6.18 '@storybook/docs-mdx': 0.1.0 '@storybook/global': 5.0.0 - '@storybook/manager': 7.6.7 - '@storybook/node-logger': 7.6.7 - '@storybook/preview-api': 7.6.7 - '@storybook/telemetry': 7.6.7 - '@storybook/types': 7.6.7 + '@storybook/manager': 7.6.18 + '@storybook/node-logger': 7.6.18 + '@storybook/preview-api': 7.6.18 + '@storybook/telemetry': 7.6.18 + '@storybook/types': 7.6.18 '@types/detect-port': 1.3.5 - '@types/node': 18.19.5 + '@types/node': 18.19.31 '@types/pretty-hrtime': 1.0.3 - '@types/semver': 7.5.6 + '@types/semver': 7.5.8 better-opn: 3.0.2 chalk: 4.1.2 - cli-table3: 0.6.3 + cli-table3: 0.6.4 compression: 1.7.4 detect-port: 1.5.1 - express: 4.18.2 + express: 4.19.2 fs-extra: 11.2.0 globby: 11.1.0 - ip: 2.0.0 + ip: 2.0.1 lodash: 4.17.21 open: 8.4.2 pretty-hrtime: 1.0.3 prompts: 2.4.2 read-pkg-up: 7.0.1 - semver: 7.5.4 + semver: 7.6.0 telejson: 7.2.0 - tiny-invariant: 1.3.1 + tiny-invariant: 1.3.3 ts-dedent: 2.2.0 util: 0.12.5 util-deprecate: 1.0.2 - watchpack: 2.4.0 - ws: 8.16.0 + watchpack: 2.4.1 + ws: 8.17.0 transitivePeerDependencies: - bufferutil - encoding @@ -3821,33 +3835,33 @@ packages: - utf-8-validate dev: true - /@storybook/csf-plugin@7.6.7: - resolution: {integrity: sha512-YL7e6H4iVcsDI0UpgpdQX2IiGDrlbgaQMHQgDLWXmZyKxBcy0ONROAX5zoT1ml44EHkL60TMaG4f7SinviJCog==} + /@storybook/csf-plugin@7.6.18: + resolution: {integrity: sha512-dV/f0oIuv/OsmAh3FVqBkZAvQ5YRQXglZlHynaqt8cUVXi+Nsc/b7kFTBGj2GyIi9TCdiqfV5Yns+Bq2bIVHrA==} dependencies: - '@storybook/csf-tools': 7.6.7 - unplugin: 1.6.0 + '@storybook/csf-tools': 7.6.18 + unplugin: 1.10.1 transitivePeerDependencies: - supports-color dev: true - /@storybook/csf-tools@7.6.7: - resolution: {integrity: sha512-hyRbUGa2Uxvz3U09BjcOfMNf/5IYgRum1L6XszqK2O8tK9DGte1r6hArCIAcqiEmFMC40d0kalPzqu6WMNn7sg==} + /@storybook/csf-tools@7.6.18: + resolution: {integrity: sha512-ngRNHEtLJv6vMlqCeJaG8dh1CwtCaGCHi7xuS+b71Y97xXLJlA6RR9rhsMG6bDwMJR+xiIqKUc6HH3ZBSVVhiA==} dependencies: - '@babel/generator': 7.23.6 - '@babel/parser': 7.23.6 - '@babel/traverse': 7.23.7 - '@babel/types': 7.23.6 - '@storybook/csf': 0.1.2 - '@storybook/types': 7.6.7 + '@babel/generator': 7.24.4 + '@babel/parser': 7.24.4 + '@babel/traverse': 7.24.1 + '@babel/types': 7.24.0 + '@storybook/csf': 0.1.5 + '@storybook/types': 7.6.18 fs-extra: 11.2.0 - recast: 0.23.4 + recast: 0.23.6 ts-dedent: 2.2.0 transitivePeerDependencies: - supports-color dev: true - /@storybook/csf@0.1.2: - resolution: {integrity: sha512-ePrvE/pS1vsKR9Xr+o+YwdqNgHUyXvg+1Xjx0h9LrVx7Zq4zNe06pd63F5EvzTbCbJsHj7GHr9tkiaqm7U8WRA==} + /@storybook/csf@0.1.5: + resolution: {integrity: sha512-pW7Dtk/bE2JGrAe/KuBY4Io02NBe/2CLP2DkgVgWlSwvEVdm/rbQyiwy8RaL0lQlJCv9CsGBY+n9HQG8d4bZjQ==} dependencies: type-fest: 2.19.0 dev: true @@ -3856,12 +3870,12 @@ packages: resolution: {integrity: sha512-JDaBR9lwVY4eSH5W8EGHrhODjygPd6QImRbwjAuJNEnY0Vw4ie3bPkeGfnacB3OBW6u/agqPv2aRlR46JcAQLg==} dev: true - /@storybook/docs-tools@7.6.7: - resolution: {integrity: sha512-enTO/xVjBqwUraGCYTwdyjMvug3OSAM7TPPUEJ3KPieJNwAzcYkww/qNDMIAR4S39zPMrkAmtS3STvVadlJz7g==} + /@storybook/docs-tools@7.6.18: + resolution: {integrity: sha512-gE4He4YoOAFnFwarSsOJVLC1YVN6iilQXMZsKD2SNI0M30nOeqK5NjFwXtAklq6QQvBZVZV7VRG5sY7i4aGBcQ==} dependencies: - '@storybook/core-common': 7.6.7 - '@storybook/preview-api': 7.6.7 - '@storybook/types': 7.6.7 + '@storybook/core-common': 7.6.18 + '@storybook/preview-api': 7.6.18 + '@storybook/types': 7.6.18 '@types/doctrine': 0.0.3 assert: 2.1.0 doctrine: 3.0.0 @@ -3875,21 +3889,21 @@ packages: resolution: {integrity: sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==} dev: true - /@storybook/manager-api@7.6.7(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-3Wk/BvuGUlw/X05s57zZO7gJbzfUeE9Xe+CSIvuH7RY5jx9PYnNwqNlTXPXhJ5LPvwMthae7WJVn3SuBpbptoQ==} + /@storybook/manager-api@7.6.18(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-4c2japUMjnHiel38wQoNWh5RVac6ATMcWxvzPhOKx3I19gbSoUF1CcDg+1piRMWuSyzUBIBlIrBB3s4/02gnnA==} dependencies: - '@storybook/channels': 7.6.7 - '@storybook/client-logger': 7.6.7 - '@storybook/core-events': 7.6.7 - '@storybook/csf': 0.1.2 + '@storybook/channels': 7.6.18 + '@storybook/client-logger': 7.6.18 + '@storybook/core-events': 7.6.18 + '@storybook/csf': 0.1.5 '@storybook/global': 5.0.0 - '@storybook/router': 7.6.7 - '@storybook/theming': 7.6.7(react-dom@18.2.0)(react@18.2.0) - '@storybook/types': 7.6.7 + '@storybook/router': 7.6.18 + '@storybook/theming': 7.6.18(react-dom@18.3.1)(react@18.3.1) + '@storybook/types': 7.6.18 dequal: 2.0.3 lodash: 4.17.21 memoizerific: 1.11.3 - store2: 2.14.2 + store2: 2.14.3 telejson: 7.2.0 ts-dedent: 2.2.0 transitivePeerDependencies: @@ -3897,73 +3911,92 @@ packages: - react-dom dev: true - /@storybook/manager@7.6.7: - resolution: {integrity: sha512-ZCrkB2zEXogzdOcVzD242ZVm4tlHqrayotnI6iOn9uiun0Pgny0m2d7s9Zge6K2dTOO1vZiOHuA/Mr6nnIDjsA==} + /@storybook/manager@7.6.18: + resolution: {integrity: sha512-ZFatbkbK5qv2a4jJEm6WqKZZqkYm++t0uAZozBA6TNq/bWMaD9ihummPTGND8R0M7SW0rfUVFDAE8bv14gLcdg==} dev: true /@storybook/mdx2-csf@1.1.0: resolution: {integrity: sha512-TXJJd5RAKakWx4BtpwvSNdgTDkKM6RkXU8GK34S/LhidQ5Pjz3wcnqb0TxEkfhK/ztbP8nKHqXFwLfa2CYkvQw==} dev: true - /@storybook/node-logger@7.6.7: - resolution: {integrity: sha512-XLih8MxylkpZG9+8tgp8sPGc2tldlWF+DpuAkUv6J3Mc81mPyc3cQKQWZ7Hb+m1LpRGqKV4wyOQj1rC+leVMoQ==} + /@storybook/node-logger@7.6.18: + resolution: {integrity: sha512-e75XQ6TekxjpzdlW6rZAFtv/9aD/nQb4z9kaBr3GhuVMGVJNihs9ek6eVEFZLxpks4FDVSPTSg0QtFpSgOpbrg==} dev: true - /@storybook/postinstall@7.6.7: - resolution: {integrity: sha512-mrpRmcwFd9FcvtHPXA9x6vOrHLVCKScZX/Xx2QPWgAvB3W6uzP8G+8QNb1u834iToxrWeuszUMB9UXZK4Qj5yg==} + /@storybook/postinstall@7.6.18: + resolution: {integrity: sha512-TTTvuR6LyaRfzrtJvSr+L4Bys8gp3wOKACOErZBXjt3UCQR4rwhwGP7k2GsysiHHLbxGu25ZU2fnnT2OYYeTNA==} + dev: true + + /@storybook/preview-api@7.6.17: + resolution: {integrity: sha512-wLfDdI9RWo1f2zzFe54yRhg+2YWyxLZvqdZnSQ45mTs4/7xXV5Wfbv3QNTtcdw8tT3U5KRTrN1mTfTCiRJc0Kw==} + dependencies: + '@storybook/channels': 7.6.17 + '@storybook/client-logger': 7.6.17 + '@storybook/core-events': 7.6.17 + '@storybook/csf': 0.1.5 + '@storybook/global': 5.0.0 + '@storybook/types': 7.6.17 + '@types/qs': 6.9.15 + dequal: 2.0.3 + lodash: 4.17.21 + memoizerific: 1.11.3 + qs: 6.12.1 + synchronous-promise: 2.0.17 + ts-dedent: 2.2.0 + util-deprecate: 1.0.2 dev: true - /@storybook/preview-api@7.6.7: - resolution: {integrity: sha512-ja85ItrT6q2TeBQ6n0CNoRi1R6L8yF2kkis9hVeTQHpwLdZyHUTRqqR5WmhtLqqQXcofyasBPOeJV06wuOhgRQ==} + /@storybook/preview-api@7.6.18: + resolution: {integrity: sha512-X3r3MnoLJWUhHTVFggJcfHzDLCKSOdHNOpXXRNkdG2WXFcCZAlTdm0KqThCvQmdqS4OAOJMfn4pHqtxPG8yfyg==} dependencies: - '@storybook/channels': 7.6.7 - '@storybook/client-logger': 7.6.7 - '@storybook/core-events': 7.6.7 - '@storybook/csf': 0.1.2 + '@storybook/channels': 7.6.18 + '@storybook/client-logger': 7.6.18 + '@storybook/core-events': 7.6.18 + '@storybook/csf': 0.1.5 '@storybook/global': 5.0.0 - '@storybook/types': 7.6.7 - '@types/qs': 6.9.11 + '@storybook/types': 7.6.18 + '@types/qs': 6.9.15 dequal: 2.0.3 lodash: 4.17.21 memoizerific: 1.11.3 - qs: 6.11.2 + qs: 6.12.1 synchronous-promise: 2.0.17 ts-dedent: 2.2.0 util-deprecate: 1.0.2 dev: true - /@storybook/preview@7.6.7: - resolution: {integrity: sha512-/ddKIyT+6b8CKGJAma1wood4nwCAoi/E1olCqgpCmviMeUtAiMzgK0xzPwvq5Mxkz/cPeXVi8CQgaQZCa4yvNA==} + /@storybook/preview@7.6.18: + resolution: {integrity: sha512-iltkZxz991GmzXMNkM9b7ddM45IsfZoQ+pMGXOv902Xawx9otvNkMVxBMhpXG+tf7G3FrSM1DFT6V9SycC6pqg==} dev: true - /@storybook/react-dom-shim@7.6.7(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-b/rmy/YzVrwP+ifyZG4yXVIdeFVdTbmziodHUlbrWiUNsqtTZZur9kqkKRUH/7ofji9MFe81nd0MRlcTNFomqg==} + /@storybook/react-dom-shim@7.6.18(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-s4eIq5KVnS7E4pIXdq31YzqRZX0FZEYKoUeZziBBajRvmPAJ/zWSBbrGeOIR71xDHT7UkUoeb5EuyfykS9yuoA==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) dev: true - /@storybook/react-vite@7.6.7(react-dom@18.2.0)(react@18.2.0)(typescript@4.9.5)(vite@4.5.1): - resolution: {integrity: sha512-1cBpxVZ4vLO5rGbhTBNR2SjL+ZePCUAEY+I31tbORYFAoOKmlsNef4fRLnXJ9NYUAyjwZpUmbW0cIxxOFk7nGA==} + /@storybook/react-vite@7.6.18(react-dom@18.3.1)(react@18.3.1)(typescript@4.9.5)(vite@4.5.3): + resolution: {integrity: sha512-7Q4x7SEA9ajb348z5bX+dXM8x+JWgLus1xGPySnVKariYrlgmJrSbFSE07oMsA0m0Y2mwXzWQgv7imjU7Jwklg==} engines: {node: '>=16'} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 vite: ^3.0.0 || ^4.0.0 || ^5.0.0 dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.3.0(typescript@4.9.5)(vite@4.5.1) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.3.0(typescript@4.9.5)(vite@4.5.3) '@rollup/pluginutils': 5.1.0 - '@storybook/builder-vite': 7.6.7(typescript@4.9.5)(vite@4.5.1) - '@storybook/react': 7.6.7(react-dom@18.2.0)(react@18.2.0)(typescript@4.9.5) - '@vitejs/plugin-react': 3.1.0(vite@4.5.1) - magic-string: 0.30.5 - react: 18.2.0 - react-docgen: 7.0.1 - react-dom: 18.2.0(react@18.2.0) - vite: 4.5.1 + '@storybook/builder-vite': 7.6.18(typescript@4.9.5)(vite@4.5.3) + '@storybook/react': 7.6.18(react-dom@18.3.1)(react@18.3.1)(typescript@4.9.5) + '@vitejs/plugin-react': 3.1.0(vite@4.5.3) + magic-string: 0.30.10 + react: 18.3.1 + react-docgen: 7.0.3 + react-dom: 18.3.1(react@18.3.1) + vite: 4.5.3 transitivePeerDependencies: - '@preact/preset-vite' - encoding @@ -3973,8 +4006,8 @@ packages: - vite-plugin-glimmerx dev: true - /@storybook/react@7.6.7(react-dom@18.2.0)(react@18.2.0)(typescript@4.9.5): - resolution: {integrity: sha512-uT9IBPDM1SQg6FglWqb7IemOJ1Z8kYB5rehIDEDToi0u5INihSY8rHd003TxG4Wx4REp6J+rfbDJO2aVui/gxA==} + /@storybook/react@7.6.18(react-dom@18.3.1)(react@18.3.1)(typescript@4.9.5): + resolution: {integrity: sha512-cWAMz8W7Xa1fv8ugFsUCw0w08GsWGGw5XiYgLJJ+2/zQNhkMGzsY9zl7XQtULhIBfY0MptC7CLIYHc0t61xvHw==} engines: {node: '>=16.0.0'} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -3984,16 +4017,16 @@ packages: typescript: optional: true dependencies: - '@storybook/client-logger': 7.6.7 - '@storybook/core-client': 7.6.7 - '@storybook/docs-tools': 7.6.7 + '@storybook/client-logger': 7.6.18 + '@storybook/core-client': 7.6.18 + '@storybook/docs-tools': 7.6.18 '@storybook/global': 5.0.0 - '@storybook/preview-api': 7.6.7 - '@storybook/react-dom-shim': 7.6.7(react-dom@18.2.0)(react@18.2.0) - '@storybook/types': 7.6.7 + '@storybook/preview-api': 7.6.18 + '@storybook/react-dom-shim': 7.6.18(react-dom@18.3.1)(react@18.3.1) + '@storybook/types': 7.6.18 '@types/escodegen': 0.0.6 '@types/estree': 0.0.51 - '@types/node': 18.19.5 + '@types/node': 18.19.31 acorn: 7.4.1 acorn-jsx: 5.3.2(acorn@7.4.1) acorn-walk: 7.2.0 @@ -4001,9 +4034,9 @@ packages: html-tags: 3.3.1 lodash: 4.17.21 prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-element-to-jsx-string: 15.0.0(react-dom@18.2.0)(react@18.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-element-to-jsx-string: 15.0.0(react-dom@18.3.1)(react@18.3.1) ts-dedent: 2.2.0 type-fest: 2.19.0 typescript: 4.9.5 @@ -4013,20 +4046,20 @@ packages: - supports-color dev: true - /@storybook/router@7.6.7: - resolution: {integrity: sha512-kkhNSdC3fXaQxILg8a26RKk4/ZbF/AUVrepUEyO8lwvbJ6LItTyWSE/4I9Ih4qV2Mjx33ncc8vLqM9p8r5qnMA==} + /@storybook/router@7.6.18: + resolution: {integrity: sha512-Kw6nAPWRAFE9DM//pnyjL7Xnxt+yQIONdERDnPrdEmHG5mErXGtO18aFMsb/7GiAD50J/i5ObTp7FJsWffAnbg==} dependencies: - '@storybook/client-logger': 7.6.7 + '@storybook/client-logger': 7.6.18 memoizerific: 1.11.3 - qs: 6.11.2 + qs: 6.12.1 dev: true - /@storybook/telemetry@7.6.7: - resolution: {integrity: sha512-NHGzC/LGLXpK4AFbVj8ln5ab86ZiiNFvORQMn3+LNGwUt3ZdsHBzExN+WPZdw7OPtfk4ubUY89FXH2GedhTALw==} + /@storybook/telemetry@7.6.18: + resolution: {integrity: sha512-fVgQtWYpAA1Htiu05GwipBNM5odCi05FpaoaxnCO/CsqrTfKYBJTorVo8mh8wc03gfQJs1/nXN2v0WEo0ahUoA==} dependencies: - '@storybook/client-logger': 7.6.7 - '@storybook/core-common': 7.6.7 - '@storybook/csf-tools': 7.6.7 + '@storybook/client-logger': 7.6.18 + '@storybook/core-common': 7.6.18 + '@storybook/csf-tools': 7.6.18 chalk: 4.1.2 detect-package-manager: 2.0.1 fetch-retry: 5.0.6 @@ -4037,24 +4070,33 @@ packages: - supports-color dev: true - /@storybook/theming@7.6.7(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-+42rfC4rZtWVAXJ7JBUQKnQ6vWBXJVHZ9HtNUWzQLPR9sJSMmHnnSMV6y5tizGgZqmBnAIkuoYk+Tt6NfwUmSA==} + /@storybook/theming@7.6.18(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-5nwqV/rAVzS8wZ6DbsX5/ugDLV189hn2m3K9JlJmhVW9b2mSDYW5i1cTjpoChh1t9gMZl82VPnEhgPRMx5bXgw==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0) - '@storybook/client-logger': 7.6.7 + '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.3.1) + '@storybook/client-logger': 7.6.18 '@storybook/global': 5.0.0 memoizerific: 1.11.3 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) dev: true - /@storybook/types@7.6.7: - resolution: {integrity: sha512-VcGwrI4AkBENxkoAUJ+Z7SyMK73hpoY0TTtw2J7tc05/xdiXhkQTX15Qa12IBWIkoXCyNrtaU+q7KR8Tjzi+uw==} + /@storybook/types@7.6.17: + resolution: {integrity: sha512-GRY0xEJQ0PrL7DY2qCNUdIfUOE0Gsue6N+GBJw9ku1IUDFLJRDOF+4Dx2BvYcVCPI5XPqdWKlEyZdMdKjiQN7Q==} dependencies: - '@storybook/channels': 7.6.7 + '@storybook/channels': 7.6.17 + '@types/babel__core': 7.20.5 + '@types/express': 4.17.21 + file-system-cache: 2.3.0 + dev: true + + /@storybook/types@7.6.18: + resolution: {integrity: sha512-W7/8kUtMhEopZhwXFMOKlXwQCrz0PBJ5wQwmJNZ4i0YPTVfFzb+/6pgpkzUNtbXiTp6dfxi3ERoAF9wz9Zyt7w==} + dependencies: + '@storybook/channels': 7.6.18 '@types/babel__core': 7.20.5 '@types/express': 4.17.21 file-system-cache: 2.3.0 @@ -4073,30 +4115,30 @@ packages: tslib: 2.6.2 dev: false - /@swc/helpers@0.5.3: - resolution: {integrity: sha512-FaruWX6KdudYloq1AHD/4nU+UsMTdNE8CKyrseXWEcgjDAbvkwJg2QGPAnfIJLIWsjZOSPLOAykK6fuYp4vp4A==} + /@swc/helpers@0.5.11: + resolution: {integrity: sha512-YNlnKRWF2sVojTpIyzwou9XoTNbzbzONwRhOoniEioF1AtaitTvVZblaQRrAzChWQ1bLYyYSWzM18y4WwgzJ+A==} dependencies: tslib: 2.6.2 dev: false - /@tanstack/query-core@5.28.13: - resolution: {integrity: sha512-C3+CCOcza+mrZ7LglQbjeYEOTEC3LV0VN0eYaIN6GvqAZ8Foegdgch7n6QYPtT4FuLae5ALy+m+ZMEKpD6tMCQ==} + /@tanstack/query-core@5.32.0: + resolution: {integrity: sha512-Z3flEgCat55DRXU5UMwYU1U+DgFZKA3iufyOKs+II7iRAo0uXkeU7PH5e6sOH1CGEag0IpKmZxlUFpCg6roSKw==} dev: false - /@tanstack/react-query@5.28.14(react@18.2.0): - resolution: {integrity: sha512-cZqt03Igb3I9tM72qNX5TAAmeYl75Z+k4Mv92VkXIXc2hCrv0fIywd7GN3JV1BBJl4mr7Cc+OOKKOPy8sNVOkA==} + /@tanstack/react-query@5.32.0(react@18.3.1): + resolution: {integrity: sha512-+E3UudQtarnx9A6xhpgMZapyF+aJfNBGFMgI459FnduEZqT/9KhOWnMOneZahLRt52yzskSA0AuOyLkXHK0yBA==} peerDependencies: react: ^18.0.0 dependencies: - '@tanstack/query-core': 5.28.13 - react: 18.2.0 + '@tanstack/query-core': 5.32.0 + react: 18.3.1 dev: false /@types/babel__core@7.20.5: resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} dependencies: - '@babel/parser': 7.23.6 - '@babel/types': 7.23.6 + '@babel/parser': 7.24.4 + '@babel/types': 7.24.0 '@types/babel__generator': 7.6.8 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.20.5 @@ -4105,39 +4147,39 @@ packages: /@types/babel__generator@7.6.8: resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} dependencies: - '@babel/types': 7.23.6 + '@babel/types': 7.24.0 dev: true /@types/babel__template@7.4.4: resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} dependencies: - '@babel/parser': 7.23.6 - '@babel/types': 7.23.6 + '@babel/parser': 7.24.4 + '@babel/types': 7.24.0 dev: true /@types/babel__traverse@7.20.5: resolution: {integrity: sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==} dependencies: - '@babel/types': 7.23.6 + '@babel/types': 7.24.0 dev: true /@types/body-parser@1.19.5: resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} dependencies: '@types/connect': 3.4.38 - '@types/node': 20.10.7 + '@types/node': 20.12.7 dev: true /@types/connect@3.4.38: resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} dependencies: - '@types/node': 20.10.7 + '@types/node': 20.12.7 dev: true /@types/cross-spawn@6.0.6: resolution: {integrity: sha512-fXRhhUkG4H3TQk5dBhQ7m/JDdSNHKwR2BBia62lhwEIq9xGiQKLxd6LymNhn47SjXhsUEPmxi+PKw2OkW4LLjA==} dependencies: - '@types/node': 20.10.7 + '@types/node': 20.12.7 dev: true /@types/detect-port@1.3.5: @@ -4167,12 +4209,12 @@ packages: /@types/eslint-scope@3.7.7: resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} dependencies: - '@types/eslint': 8.56.1 + '@types/eslint': 8.56.10 '@types/estree': 1.0.5 dev: true - /@types/eslint@8.56.1: - resolution: {integrity: sha512-18PLWRzhy9glDQp3+wOgfLYRWlhgX0azxgJ63rdpoUHyrC9z0f5CkFburjQx4uD7ZCruw85ZtMt6K+L+R8fLJQ==} + /@types/eslint@8.56.10: + resolution: {integrity: sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==} dependencies: '@types/estree': 1.0.5 '@types/json-schema': 7.0.15 @@ -4186,11 +4228,11 @@ packages: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} dev: true - /@types/express-serve-static-core@4.17.41: - resolution: {integrity: sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==} + /@types/express-serve-static-core@4.19.0: + resolution: {integrity: sha512-bGyep3JqPCRry1wq+O5n7oiBgGWmeIJXPjXXCo8EK0u8duZGSYar7cGqd3ML2JUsLGeB7fmc06KYo9fLGWqPvQ==} dependencies: - '@types/node': 20.10.7 - '@types/qs': 6.9.11 + '@types/node': 20.12.7 + '@types/qs': 6.9.15 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 dev: true @@ -4199,9 +4241,9 @@ packages: resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} dependencies: '@types/body-parser': 1.19.5 - '@types/express-serve-static-core': 4.17.41 - '@types/qs': 6.9.11 - '@types/serve-static': 1.15.5 + '@types/express-serve-static-core': 4.19.0 + '@types/qs': 6.9.15 + '@types/serve-static': 1.15.7 dev: true /@types/find-cache-dir@3.2.1: @@ -4212,19 +4254,19 @@ packages: resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} dependencies: '@types/minimatch': 5.1.2 - '@types/node': 20.10.7 + '@types/node': 20.12.7 dev: true /@types/graceful-fs@4.1.9: resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} dependencies: - '@types/node': 20.10.7 + '@types/node': 20.12.7 dev: true /@types/hoist-non-react-statics@3.3.5: resolution: {integrity: sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==} dependencies: - '@types/react': 17.0.74 + '@types/react': 17.0.80 hoist-non-react-statics: 3.3.2 /@types/http-errors@2.0.4: @@ -4247,8 +4289,8 @@ packages: '@types/istanbul-lib-report': 3.0.3 dev: true - /@types/jest@29.5.11: - resolution: {integrity: sha512-S2mHmYIVe13vrm6q4kN6fLYYAka15ALQki/vgDC3mIukEOx8WJlv0kQPM+d4w8Gp6u0uSdKND04IlTXBv0rwnQ==} + /@types/jest@29.5.12: + resolution: {integrity: sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==} dependencies: expect: 29.7.0 pretty-format: 29.7.0 @@ -4266,12 +4308,12 @@ packages: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: true - /@types/lodash@4.14.202: - resolution: {integrity: sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==} + /@types/lodash@4.17.0: + resolution: {integrity: sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==} dev: true - /@types/mdx@2.0.10: - resolution: {integrity: sha512-Rllzc5KHk0Al5/WANwgSPl1/CwjqCy+AZrGd78zuK+jO9aDM6ffblZ+zIjgPNAaEBmlO0RYDvLNh7wD0zKVgEg==} + /@types/mdx@2.0.13: + resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} dev: true /@types/mime-types@2.1.4: @@ -4282,10 +4324,6 @@ packages: resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} dev: true - /@types/mime@3.0.4: - resolution: {integrity: sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==} - dev: true - /@types/minimatch@5.1.2: resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} dev: true @@ -4294,21 +4332,21 @@ packages: resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} dev: true - /@types/node-fetch@2.6.10: - resolution: {integrity: sha512-PPpPK6F9ALFTn59Ka3BaL+qGuipRfxNE8qVgkp0bVixeiR2c2/L+IVOiBdu9JhhT22sWnQEp6YyHGI2b2+CMcA==} + /@types/node-fetch@2.6.11: + resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==} dependencies: - '@types/node': 18.19.5 + '@types/node': 18.19.31 form-data: 4.0.0 dev: true - /@types/node@18.19.5: - resolution: {integrity: sha512-22MG6T02Hos2JWfa1o5jsIByn+bc5iOt1IS4xyg6OG68Bu+wMonVZzdrgCw693++rpLE9RUT/Bx15BeDzO0j+g==} + /@types/node@18.19.31: + resolution: {integrity: sha512-ArgCD39YpyyrtFKIqMDvjz79jto5fcI/SVUs2HwB+f0dAzq68yqOdyaSivLiLugSziTpNXLQrVb7RZFmdZzbhA==} dependencies: undici-types: 5.26.5 dev: true - /@types/node@20.10.7: - resolution: {integrity: sha512-fRbIKb8C/Y2lXxB5eVMj4IU7xpdox0Lh8bUPEdtLysaylsml1hOOx1+STloRs/B9nf7C6kPRmmg/V7aQW7usNg==} + /@types/node@20.12.7: + resolution: {integrity: sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==} dependencies: undici-types: 5.26.5 dev: true @@ -4333,11 +4371,11 @@ packages: resolution: {integrity: sha512-nj39q0wAIdhwn7DGUyT9irmsKK1tV0bd5WFEhgpqNTMFZ8cE+jieuTphCW0tfdm47S2zVT5mr09B28b1chmQMA==} dev: true - /@types/prop-types@15.7.11: - resolution: {integrity: sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==} + /@types/prop-types@15.7.12: + resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} - /@types/qs@6.9.11: - resolution: {integrity: sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==} + /@types/qs@6.9.15: + resolution: {integrity: sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==} dev: true /@types/range-parser@1.2.7: @@ -4347,31 +4385,31 @@ packages: /@types/react-helmet@6.1.11: resolution: {integrity: sha512-0QcdGLddTERotCXo3VFlUSWO3ztraw8nZ6e3zJSgG7apwV5xt+pJUS8ewPBqT4NYB1optGLprNQzFleIY84u/g==} dependencies: - '@types/react': 17.0.74 + '@types/react': 17.0.80 dev: true /@types/react-infinite-scroller@1.2.5: resolution: {integrity: sha512-fJU1jhMgoL6NJFrqTM0Ob7tnd2sQWGxe2ESwiU6FZWbJK/VO/Er5+AOhc+e2zbT0dk5pLygqctsulOLJ8xnSzw==} dependencies: - '@types/react': 17.0.74 + '@types/react': 17.0.80 dev: true /@types/react-mentions@4.1.13: resolution: {integrity: sha512-kRulAAjlmhCtsJ9bapO0foocknaE/rEuFKpmFEU81fBfnXZmZNBaJ9J/DBjwigT3WDHjQVUmYoi5sxEXrcdzAw==} dependencies: - '@types/react': 17.0.74 + '@types/react': 17.0.80 dev: true /@types/react-timeago@4.1.7: resolution: {integrity: sha512-ogD4Ror/hDG+pQggCX+TgPgJ8W2jeeUxsgNU485Qpm0Ma+E2TND2EJuKwK5+sxlkDXDEgsHradO0zWBkTgLzNg==} dependencies: - '@types/react': 17.0.74 + '@types/react': 17.0.80 dev: true - /@types/react@17.0.74: - resolution: {integrity: sha512-nBtFGaeTMzpiL/p73xbmCi00SiCQZDTJUk9ZuHOLtil3nI+y7l269LHkHIAYpav99ZwGnPJzuJsJpfLXjiQ52g==} + /@types/react@17.0.80: + resolution: {integrity: sha512-LrgHIu2lEtIo8M7d1FcI3BdwXWoRQwMoXOZ7+dPTW0lYREjmlHl3P0U1VD0i/9tppOuv8/sam7sOjx34TxSFbA==} dependencies: - '@types/prop-types': 15.7.11 + '@types/prop-types': 15.7.12 '@types/scheduler': 0.16.8 csstype: 3.1.3 @@ -4382,23 +4420,23 @@ packages: /@types/scheduler@0.16.8: resolution: {integrity: sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==} - /@types/semver@7.5.6: - resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==} + /@types/semver@7.5.8: + resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} dev: true /@types/send@0.17.4: resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} dependencies: '@types/mime': 1.3.5 - '@types/node': 20.10.7 + '@types/node': 20.12.7 dev: true - /@types/serve-static@1.15.5: - resolution: {integrity: sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==} + /@types/serve-static@1.15.7: + resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==} dependencies: '@types/http-errors': 2.0.4 - '@types/mime': 3.0.4 - '@types/node': 20.10.7 + '@types/node': 20.12.7 + '@types/send': 0.17.4 dev: true /@types/stack-utils@2.0.3: @@ -4409,7 +4447,7 @@ packages: resolution: {integrity: sha512-mmiVvwpYklFIv9E8qfxuPyIt/OuyIrn6gMOAMOFUO3WJfSrSE+sGUoa4PiZj77Ut7bKZpaa6o1fBKS/4TOEvnA==} dependencies: '@types/hoist-non-react-statics': 3.3.5 - '@types/react': 17.0.74 + '@types/react': 17.0.80 csstype: 3.1.3 dev: true @@ -4421,8 +4459,8 @@ packages: resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==} dev: true - /@types/uuid@9.0.7: - resolution: {integrity: sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==} + /@types/uuid@9.0.8: + resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==} dev: true /@types/yargs-parser@21.0.3: @@ -4435,8 +4473,8 @@ packages: '@types/yargs-parser': 21.0.3 dev: true - /@typescript-eslint/eslint-plugin@6.18.0(@typescript-eslint/parser@6.18.0)(eslint@8.56.0)(typescript@4.9.5): - resolution: {integrity: sha512-3lqEvQUdCozi6d1mddWqd+kf8KxmGq2Plzx36BlkjuQe3rSTm/O98cLf0A4uDO+a5N1KD2SeEEl6fW97YHY+6w==} + /@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.0)(typescript@4.9.5): + resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha @@ -4447,25 +4485,25 @@ packages: optional: true dependencies: '@eslint-community/regexpp': 4.10.0 - '@typescript-eslint/parser': 6.18.0(eslint@8.56.0)(typescript@4.9.5) - '@typescript-eslint/scope-manager': 6.18.0 - '@typescript-eslint/type-utils': 6.18.0(eslint@8.56.0)(typescript@4.9.5) - '@typescript-eslint/utils': 6.18.0(eslint@8.56.0)(typescript@4.9.5) - '@typescript-eslint/visitor-keys': 6.18.0 + '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@4.9.5) + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.0)(typescript@4.9.5) + '@typescript-eslint/utils': 6.21.0(eslint@8.57.0)(typescript@4.9.5) + '@typescript-eslint/visitor-keys': 6.21.0 debug: 4.3.4 - eslint: 8.56.0 + eslint: 8.57.0 graphemer: 1.4.0 - ignore: 5.3.0 + ignore: 5.3.1 natural-compare: 1.4.0 - semver: 7.5.4 - ts-api-utils: 1.0.3(typescript@4.9.5) + semver: 7.6.0 + ts-api-utils: 1.3.0(typescript@4.9.5) typescript: 4.9.5 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/parser@6.18.0(eslint@8.56.0)(typescript@4.9.5): - resolution: {integrity: sha512-v6uR68SFvqhNQT41frCMCQpsP+5vySy6IdgjlzUWoo7ALCnpaWYcz/Ij2k4L8cEsL0wkvOviCMpjmtRtHNOKzA==} + /@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5): + resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 @@ -4474,12 +4512,12 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 6.18.0 - '@typescript-eslint/types': 6.18.0 - '@typescript-eslint/typescript-estree': 6.18.0(typescript@4.9.5) - '@typescript-eslint/visitor-keys': 6.18.0 + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/typescript-estree': 6.21.0(typescript@4.9.5) + '@typescript-eslint/visitor-keys': 6.21.0 debug: 4.3.4 - eslint: 8.56.0 + eslint: 8.57.0 typescript: 4.9.5 transitivePeerDependencies: - supports-color @@ -4493,16 +4531,16 @@ packages: '@typescript-eslint/visitor-keys': 5.62.0 dev: true - /@typescript-eslint/scope-manager@6.18.0: - resolution: {integrity: sha512-o/UoDT2NgOJ2VfHpfr+KBY2ErWvCySNUIX/X7O9g8Zzt/tXdpfEU43qbNk8LVuWUT2E0ptzTWXh79i74PP0twA==} + /@typescript-eslint/scope-manager@6.21.0: + resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} engines: {node: ^16.0.0 || >=18.0.0} dependencies: - '@typescript-eslint/types': 6.18.0 - '@typescript-eslint/visitor-keys': 6.18.0 + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/visitor-keys': 6.21.0 dev: true - /@typescript-eslint/type-utils@6.18.0(eslint@8.56.0)(typescript@4.9.5): - resolution: {integrity: sha512-ZeMtrXnGmTcHciJN1+u2CigWEEXgy1ufoxtWcHORt5kGvpjjIlK9MUhzHm4RM8iVy6dqSaZA/6PVkX6+r+ChjQ==} + /@typescript-eslint/type-utils@6.21.0(eslint@8.57.0)(typescript@4.9.5): + resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 @@ -4511,11 +4549,11 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 6.18.0(typescript@4.9.5) - '@typescript-eslint/utils': 6.18.0(eslint@8.56.0)(typescript@4.9.5) + '@typescript-eslint/typescript-estree': 6.21.0(typescript@4.9.5) + '@typescript-eslint/utils': 6.21.0(eslint@8.57.0)(typescript@4.9.5) debug: 4.3.4 - eslint: 8.56.0 - ts-api-utils: 1.0.3(typescript@4.9.5) + eslint: 8.57.0 + ts-api-utils: 1.3.0(typescript@4.9.5) typescript: 4.9.5 transitivePeerDependencies: - supports-color @@ -4526,8 +4564,8 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /@typescript-eslint/types@6.18.0: - resolution: {integrity: sha512-/RFVIccwkwSdW/1zeMx3hADShWbgBxBnV/qSrex6607isYjj05t36P6LyONgqdUrNLl5TYU8NIKdHUYpFvExkA==} + /@typescript-eslint/types@6.21.0: + resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} engines: {node: ^16.0.0 || >=18.0.0} dev: true @@ -4545,15 +4583,15 @@ packages: debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 - semver: 7.5.4 + semver: 7.6.0 tsutils: 3.21.0(typescript@4.9.5) typescript: 4.9.5 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/typescript-estree@6.18.0(typescript@4.9.5): - resolution: {integrity: sha512-klNvl+Ql4NsBNGB4W9TZ2Od03lm7aGvTbs0wYaFYsplVPhr+oeXjlPZCDI4U9jgJIDK38W1FKhacCFzCC+nbIg==} + /@typescript-eslint/typescript-estree@6.21.0(typescript@4.9.5): + resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: typescript: '*' @@ -4561,53 +4599,53 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/types': 6.18.0 - '@typescript-eslint/visitor-keys': 6.18.0 + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/visitor-keys': 6.21.0 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 - semver: 7.5.4 - ts-api-utils: 1.0.3(typescript@4.9.5) + semver: 7.6.0 + ts-api-utils: 1.3.0(typescript@4.9.5) typescript: 4.9.5 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/utils@5.62.0(eslint@8.56.0)(typescript@4.9.5): + /@typescript-eslint/utils@5.62.0(eslint@8.57.0)(typescript@4.9.5): resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@types/json-schema': 7.0.15 - '@types/semver': 7.5.6 + '@types/semver': 7.5.8 '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 '@typescript-eslint/typescript-estree': 5.62.0(typescript@4.9.5) - eslint: 8.56.0 + eslint: 8.57.0 eslint-scope: 5.1.1 - semver: 7.5.4 + semver: 7.6.0 transitivePeerDependencies: - supports-color - typescript dev: true - /@typescript-eslint/utils@6.18.0(eslint@8.56.0)(typescript@4.9.5): - resolution: {integrity: sha512-wiKKCbUeDPGaYEYQh1S580dGxJ/V9HI7K5sbGAVklyf+o5g3O+adnS4UNJajplF4e7z2q0uVBaTdT/yLb4XAVA==} + /@typescript-eslint/utils@6.21.0(eslint@8.57.0)(typescript@4.9.5): + resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@types/json-schema': 7.0.15 - '@types/semver': 7.5.6 - '@typescript-eslint/scope-manager': 6.18.0 - '@typescript-eslint/types': 6.18.0 - '@typescript-eslint/typescript-estree': 6.18.0(typescript@4.9.5) - eslint: 8.56.0 - semver: 7.5.4 + '@types/semver': 7.5.8 + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/typescript-estree': 6.21.0(typescript@4.9.5) + eslint: 8.57.0 + semver: 7.6.0 transitivePeerDependencies: - supports-color - typescript @@ -4621,11 +4659,11 @@ packages: eslint-visitor-keys: 3.4.3 dev: true - /@typescript-eslint/visitor-keys@6.18.0: - resolution: {integrity: sha512-1wetAlSZpewRDb2h9p/Q8kRjdGuqdTAQbkJIOUMLug2LBLG+QOjiWoSj6/3B/hA9/tVTFFdtiKvAYoYnSRW/RA==} + /@typescript-eslint/visitor-keys@6.21.0: + resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} engines: {node: ^16.0.0 || >=18.0.0} dependencies: - '@typescript-eslint/types': 6.18.0 + '@typescript-eslint/types': 6.21.0 eslint-visitor-keys: 3.4.3 dev: true @@ -4633,40 +4671,40 @@ packages: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} dev: true - /@vitejs/plugin-react@3.1.0(vite@4.5.1): + /@vitejs/plugin-react@3.1.0(vite@4.5.3): resolution: {integrity: sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: vite: ^4.1.0-beta.0 dependencies: - '@babel/core': 7.23.7 - '@babel/plugin-transform-react-jsx-self': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-react-jsx-source': 7.23.3(@babel/core@7.23.7) + '@babel/core': 7.24.4 + '@babel/plugin-transform-react-jsx-self': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-react-jsx-source': 7.24.1(@babel/core@7.24.4) magic-string: 0.27.0 - react-refresh: 0.14.0 - vite: 4.5.1 + react-refresh: 0.14.2 + vite: 4.5.3 transitivePeerDependencies: - supports-color dev: true - /@vitejs/plugin-react@4.2.1(vite@4.5.1): + /@vitejs/plugin-react@4.2.1(vite@4.5.3): resolution: {integrity: sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: vite: ^4.2.0 || ^5.0.0 dependencies: - '@babel/core': 7.23.7 - '@babel/plugin-transform-react-jsx-self': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-react-jsx-source': 7.23.3(@babel/core@7.23.7) + '@babel/core': 7.24.4 + '@babel/plugin-transform-react-jsx-self': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-react-jsx-source': 7.24.1(@babel/core@7.24.4) '@types/babel__core': 7.20.5 - react-refresh: 0.14.0 - vite: 4.5.1 + react-refresh: 0.14.2 + vite: 4.5.3 transitivePeerDependencies: - supports-color dev: true - /@webassemblyjs/ast@1.11.6: - resolution: {integrity: sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==} + /@webassemblyjs/ast@1.12.1: + resolution: {integrity: sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==} dependencies: '@webassemblyjs/helper-numbers': 1.11.6 '@webassemblyjs/helper-wasm-bytecode': 1.11.6 @@ -4680,8 +4718,8 @@ packages: resolution: {integrity: sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==} dev: true - /@webassemblyjs/helper-buffer@1.11.6: - resolution: {integrity: sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==} + /@webassemblyjs/helper-buffer@1.12.1: + resolution: {integrity: sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==} dev: true /@webassemblyjs/helper-numbers@1.11.6: @@ -4696,13 +4734,13 @@ packages: resolution: {integrity: sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==} dev: true - /@webassemblyjs/helper-wasm-section@1.11.6: - resolution: {integrity: sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==} + /@webassemblyjs/helper-wasm-section@1.12.1: + resolution: {integrity: sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==} dependencies: - '@webassemblyjs/ast': 1.11.6 - '@webassemblyjs/helper-buffer': 1.11.6 + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/helper-buffer': 1.12.1 '@webassemblyjs/helper-wasm-bytecode': 1.11.6 - '@webassemblyjs/wasm-gen': 1.11.6 + '@webassemblyjs/wasm-gen': 1.12.1 dev: true /@webassemblyjs/ieee754@1.11.6: @@ -4721,42 +4759,42 @@ packages: resolution: {integrity: sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==} dev: true - /@webassemblyjs/wasm-edit@1.11.6: - resolution: {integrity: sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==} + /@webassemblyjs/wasm-edit@1.12.1: + resolution: {integrity: sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==} dependencies: - '@webassemblyjs/ast': 1.11.6 - '@webassemblyjs/helper-buffer': 1.11.6 + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/helper-buffer': 1.12.1 '@webassemblyjs/helper-wasm-bytecode': 1.11.6 - '@webassemblyjs/helper-wasm-section': 1.11.6 - '@webassemblyjs/wasm-gen': 1.11.6 - '@webassemblyjs/wasm-opt': 1.11.6 - '@webassemblyjs/wasm-parser': 1.11.6 - '@webassemblyjs/wast-printer': 1.11.6 + '@webassemblyjs/helper-wasm-section': 1.12.1 + '@webassemblyjs/wasm-gen': 1.12.1 + '@webassemblyjs/wasm-opt': 1.12.1 + '@webassemblyjs/wasm-parser': 1.12.1 + '@webassemblyjs/wast-printer': 1.12.1 dev: true - /@webassemblyjs/wasm-gen@1.11.6: - resolution: {integrity: sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==} + /@webassemblyjs/wasm-gen@1.12.1: + resolution: {integrity: sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==} dependencies: - '@webassemblyjs/ast': 1.11.6 + '@webassemblyjs/ast': 1.12.1 '@webassemblyjs/helper-wasm-bytecode': 1.11.6 '@webassemblyjs/ieee754': 1.11.6 '@webassemblyjs/leb128': 1.11.6 '@webassemblyjs/utf8': 1.11.6 dev: true - /@webassemblyjs/wasm-opt@1.11.6: - resolution: {integrity: sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==} + /@webassemblyjs/wasm-opt@1.12.1: + resolution: {integrity: sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==} dependencies: - '@webassemblyjs/ast': 1.11.6 - '@webassemblyjs/helper-buffer': 1.11.6 - '@webassemblyjs/wasm-gen': 1.11.6 - '@webassemblyjs/wasm-parser': 1.11.6 + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/helper-buffer': 1.12.1 + '@webassemblyjs/wasm-gen': 1.12.1 + '@webassemblyjs/wasm-parser': 1.12.1 dev: true - /@webassemblyjs/wasm-parser@1.11.6: - resolution: {integrity: sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==} + /@webassemblyjs/wasm-parser@1.12.1: + resolution: {integrity: sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==} dependencies: - '@webassemblyjs/ast': 1.11.6 + '@webassemblyjs/ast': 1.12.1 '@webassemblyjs/helper-api-error': 1.11.6 '@webassemblyjs/helper-wasm-bytecode': 1.11.6 '@webassemblyjs/ieee754': 1.11.6 @@ -4764,10 +4802,10 @@ packages: '@webassemblyjs/utf8': 1.11.6 dev: true - /@webassemblyjs/wast-printer@1.11.6: - resolution: {integrity: sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==} + /@webassemblyjs/wast-printer@1.12.1: + resolution: {integrity: sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==} dependencies: - '@webassemblyjs/ast': 1.11.6 + '@webassemblyjs/ast': 1.12.1 '@xtuc/long': 4.2.2 dev: true @@ -4988,18 +5026,19 @@ packages: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} dev: true - /aria-hidden@1.2.3: - resolution: {integrity: sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==} + /aria-hidden@1.2.4: + resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==} engines: {node: '>=10'} dependencies: tslib: 2.6.2 dev: true - /array-buffer-byte-length@1.0.0: - resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} + /array-buffer-byte-length@1.0.1: + resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} + engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 - is-array-buffer: 3.0.2 + call-bind: 1.0.7 + is-array-buffer: 3.0.4 dev: true /array-flatten@1.1.1: @@ -5010,14 +5049,15 @@ packages: resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} dev: true - /array-includes@3.1.7: - resolution: {integrity: sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==} + /array-includes@3.1.8: + resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 - get-intrinsic: 1.2.2 + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 + get-intrinsic: 1.2.4 is-string: 1.0.7 dev: true @@ -5026,24 +5066,25 @@ packages: engines: {node: '>=8'} dev: true - /array.prototype.findlastindex@1.2.3: - resolution: {integrity: sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==} + /array.prototype.findlastindex@1.2.5: + resolution: {integrity: sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 es-shim-unscopables: 1.0.2 - get-intrinsic: 1.2.2 dev: true /array.prototype.flat@1.3.2: resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 + es-abstract: 1.23.3 es-shim-unscopables: 1.0.2 dev: true @@ -5051,23 +5092,24 @@ packages: resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 + es-abstract: 1.23.3 es-shim-unscopables: 1.0.2 dev: true - /arraybuffer.prototype.slice@1.0.2: - resolution: {integrity: sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==} + /arraybuffer.prototype.slice@1.0.3: + resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} engines: {node: '>= 0.4'} dependencies: - array-buffer-byte-length: 1.0.0 - call-bind: 1.0.5 + array-buffer-byte-length: 1.0.1 + call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 - get-intrinsic: 1.2.2 - is-array-buffer: 3.0.2 - is-shared-array-buffer: 1.0.2 + es-abstract: 1.23.3 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + is-array-buffer: 3.0.4 + is-shared-array-buffer: 1.0.3 dev: true /arraybuffer.slice@0.0.7: @@ -5082,9 +5124,9 @@ packages: /assert@2.1.0: resolution: {integrity: sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 is-nan: 1.3.2 - object-is: 1.1.5 + object-is: 1.1.6 object.assign: 4.1.5 util: 0.12.5 dev: true @@ -5108,61 +5150,63 @@ packages: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} dev: true - /autoprefixer@10.4.16(postcss@8.4.33): - resolution: {integrity: sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==} + /autoprefixer@10.4.19(postcss@8.4.38): + resolution: {integrity: sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==} engines: {node: ^10 || ^12 || >=14} hasBin: true peerDependencies: postcss: ^8.1.0 dependencies: - browserslist: 4.22.2 - caniuse-lite: 1.0.30001576 + browserslist: 4.23.0 + caniuse-lite: 1.0.30001614 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.0.0 - postcss: 8.4.33 + postcss: 8.4.38 postcss-value-parser: 4.2.0 dev: true - /available-typed-arrays@1.0.5: - resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} + /available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} + dependencies: + possible-typed-array-names: 1.0.0 dev: true - /axe-core@4.8.3: - resolution: {integrity: sha512-d5ZQHPSPkF9Tw+yfyDcRoUOc4g/8UloJJe5J8m4L5+c7AtDdjDLRxew/knnI4CxvtdxEUVgWz4x3OIQUIFiMfw==} + /axe-core@4.9.0: + resolution: {integrity: sha512-H5orY+M2Fr56DWmMFpMrq5Ge93qjNdPVqzBv5gWK3aD1OvjBEJlEzxf09z93dGVQeI0LiW+aCMIx1QtShC/zUw==} engines: {node: '>=4'} dev: true - /axios@1.6.5(debug@4.3.4): - resolution: {integrity: sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==} + /axios@1.6.8(debug@4.3.4): + resolution: {integrity: sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==} dependencies: - follow-redirects: 1.15.4(debug@4.3.4) + follow-redirects: 1.15.6(debug@4.3.4) form-data: 4.0.0 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug dev: true - /babel-core@7.0.0-bridge.0(@babel/core@7.23.7): + /babel-core@7.0.0-bridge.0(@babel/core@7.24.4): resolution: {integrity: sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.7 + '@babel/core': 7.24.4 dev: true - /babel-jest@29.7.0(@babel/core@7.23.7): + /babel-jest@29.7.0(@babel/core@7.24.4): resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: '@babel/core': ^7.8.0 dependencies: - '@babel/core': 7.23.7 + '@babel/core': 7.24.4 '@jest/transform': 29.7.0 '@types/babel__core': 7.20.5 babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.6.3(@babel/core@7.23.7) + babel-preset-jest: 29.6.3(@babel/core@7.24.4) chalk: 4.1.2 graceful-fs: 4.2.11 slash: 3.0.0 @@ -5174,7 +5218,7 @@ packages: resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} engines: {node: '>=8'} dependencies: - '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-plugin-utils': 7.24.0 '@istanbuljs/load-nyc-config': 1.1.0 '@istanbuljs/schema': 0.1.3 istanbul-lib-instrument: 5.2.1 @@ -5187,77 +5231,77 @@ packages: resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@babel/template': 7.22.15 - '@babel/types': 7.23.6 + '@babel/template': 7.24.0 + '@babel/types': 7.24.0 '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.20.5 dev: true - /babel-plugin-polyfill-corejs2@0.4.7(@babel/core@7.23.7): - resolution: {integrity: sha512-LidDk/tEGDfuHW2DWh/Hgo4rmnw3cduK6ZkOI1NPFceSK3n/yAGeOsNT7FLnSGHkXj3RHGSEVkN3FsCTY6w2CQ==} + /babel-plugin-polyfill-corejs2@0.4.11(@babel/core@7.24.4): + resolution: {integrity: sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 dependencies: - '@babel/compat-data': 7.23.5 - '@babel/core': 7.23.7 - '@babel/helper-define-polyfill-provider': 0.4.4(@babel/core@7.23.7) + '@babel/compat-data': 7.24.4 + '@babel/core': 7.24.4 + '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.24.4) semver: 6.3.1 transitivePeerDependencies: - supports-color dev: true - /babel-plugin-polyfill-corejs3@0.8.7(@babel/core@7.23.7): - resolution: {integrity: sha512-KyDvZYxAzkC0Aj2dAPyDzi2Ym15e5JKZSK+maI7NAwSqofvuFglbSsxE7wUOvTg9oFVnHMzVzBKcqEb4PJgtOA==} + /babel-plugin-polyfill-corejs3@0.10.4(@babel/core@7.24.4): + resolution: {integrity: sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-define-polyfill-provider': 0.4.4(@babel/core@7.23.7) - core-js-compat: 3.35.0 + '@babel/core': 7.24.4 + '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.24.4) + core-js-compat: 3.37.0 transitivePeerDependencies: - supports-color dev: true - /babel-plugin-polyfill-regenerator@0.5.4(@babel/core@7.23.7): - resolution: {integrity: sha512-S/x2iOCvDaCASLYsOOgWOq4bCfKYVqvO/uxjkaYyZ3rVsVE3CeAI/c84NpyuBBymEgNvHgjEot3a9/Z/kXvqsg==} + /babel-plugin-polyfill-regenerator@0.6.2(@babel/core@7.24.4): + resolution: {integrity: sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 dependencies: - '@babel/core': 7.23.7 - '@babel/helper-define-polyfill-provider': 0.4.4(@babel/core@7.23.7) + '@babel/core': 7.24.4 + '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.24.4) transitivePeerDependencies: - supports-color dev: true - /babel-preset-current-node-syntax@1.0.1(@babel/core@7.23.7): + /babel-preset-current-node-syntax@1.0.1(@babel/core@7.24.4): resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.23.7 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.23.7) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.23.7) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.23.7) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.23.7) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.23.7) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.23.7) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.23.7) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.23.7) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.23.7) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.23.7) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.23.7) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.23.7) - dev: true - - /babel-preset-jest@29.6.3(@babel/core@7.23.7): + '@babel/core': 7.24.4 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.4) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.24.4) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.24.4) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.4) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.4) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.24.4) + dev: true + + /babel-preset-jest@29.6.3(@babel/core@7.24.4): resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.23.7 + '@babel/core': 7.24.4 babel-plugin-jest-hoist: 29.6.3 - babel-preset-current-node-syntax: 1.0.1(@babel/core@7.23.7) + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.24.4) dev: true /backo2@1.0.2: @@ -5303,8 +5347,8 @@ packages: resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==} dev: true - /binary-extensions@2.2.0: - resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + /binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} dev: true @@ -5320,8 +5364,8 @@ packages: resolution: {integrity: sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==} dev: true - /body-parser@1.20.1: - resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==} + /body-parser@1.20.2: + resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} dependencies: bytes: 3.1.2 @@ -5333,7 +5377,7 @@ packages: iconv-lite: 0.4.24 on-finished: 2.4.1 qs: 6.11.0 - raw-body: 2.5.1 + raw-body: 2.5.2 type-is: 1.6.18 unpipe: 1.0.0 transitivePeerDependencies: @@ -5377,15 +5421,15 @@ packages: pako: 0.2.9 dev: true - /browserslist@4.22.2: - resolution: {integrity: sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==} + /browserslist@4.23.0: + resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001576 - electron-to-chromium: 1.4.623 + caniuse-lite: 1.0.30001614 + electron-to-chromium: 1.4.750 node-releases: 2.0.14 - update-browserslist-db: 1.0.13(browserslist@4.22.2) + update-browserslist-db: 1.0.13(browserslist@4.23.0) dev: true /bs-logger@0.2.6: @@ -5416,13 +5460,13 @@ packages: ieee754: 1.2.1 dev: true - /bundle-require@4.0.2(esbuild@0.19.11): - resolution: {integrity: sha512-jwzPOChofl67PSTW2SGubV9HBQAhhR2i6nskiOThauo9dzwDUgOWQScFVaJkjEfYX+UXiD+LEx8EblQMc2wIag==} + /bundle-require@4.0.3(esbuild@0.19.12): + resolution: {integrity: sha512-2iscZ3fcthP2vka4Y7j277YJevwmsby/FpFDwjgw34Nl7dtCpt7zz/4TexmHMzY6KZEih7En9ImlbbgUNNQGtA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} peerDependencies: esbuild: '>=0.17' dependencies: - esbuild: 0.19.11 + esbuild: 0.19.12 load-tsconfig: 0.2.5 dev: true @@ -5441,12 +5485,15 @@ packages: engines: {node: '>=8'} dev: true - /call-bind@1.0.5: - resolution: {integrity: sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==} + /call-bind@1.0.7: + resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} + engines: {node: '>= 0.4'} dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 function-bind: 1.1.2 - get-intrinsic: 1.2.2 - set-function-length: 1.1.1 + get-intrinsic: 1.2.4 + set-function-length: 1.2.2 dev: true /callsite@1.0.0: @@ -5481,8 +5528,8 @@ packages: resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} dev: true - /caniuse-lite@1.0.30001576: - resolution: {integrity: sha512-ff5BdakGe2P3SQsMsiqmt1Lc8221NR1VzHj5jXN5vBny9A6fpze94HiVV/n7XRosOlsShJcvMv5mdnpjOGCEgg==} + /caniuse-lite@1.0.30001614: + resolution: {integrity: sha512-jmZQ1VpmlRwHgdP1/uiKzgiAuGOfLEJsYFP4+GBou/QQ4U6IOJCB4NP1c+1p9RGLpwObcT94jA5/uO+F1vBbog==} dev: true /chalk@2.4.2: @@ -5512,8 +5559,8 @@ packages: engines: {node: '>=10'} dev: true - /chokidar@3.5.3: - resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + /chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} dependencies: anymatch: 3.1.3 @@ -5546,14 +5593,14 @@ packages: engines: {node: '>=8'} dev: true - /citty@0.1.5: - resolution: {integrity: sha512-AS7n5NSc0OQVMV9v6wt3ByujNIrne0/cTjiC2MYqhvao57VNfiuVksTSr2p17nVOhEr2KtqiAkGwHcgMC/qUuQ==} + /citty@0.1.6: + resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} dependencies: consola: 3.2.3 dev: true - /cjs-module-lexer@1.2.3: - resolution: {integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==} + /cjs-module-lexer@1.3.1: + resolution: {integrity: sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==} dev: true /clean-stack@2.2.0: @@ -5580,8 +5627,8 @@ packages: engines: {node: '>=6'} dev: true - /cli-table3@0.6.3: - resolution: {integrity: sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==} + /cli-table3@0.6.4: + resolution: {integrity: sha512-Lm3L0p+/npIQWNIiyF/nAn7T5dnOwR3xNTHXYEBFBFVPXzCVNZ5lqEC/1eo/EVfpDsQ1I+TX4ORPQgp+UI0CRw==} engines: {node: 10.* || >= 12.*} dependencies: string-width: 4.2.3 @@ -5632,8 +5679,8 @@ packages: engines: {node: '>=6'} dev: false - /clsx@2.1.0: - resolution: {integrity: sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==} + /clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} dev: false @@ -5969,8 +6016,8 @@ packages: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} dev: true - /cookie@0.5.0: - resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} + /cookie@0.6.0: + resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} dev: true @@ -5986,10 +6033,10 @@ packages: toggle-selection: 1.0.6 dev: false - /core-js-compat@3.35.0: - resolution: {integrity: sha512-5blwFAddknKeNgsjBzilkdQ0+YK8L1PfqPYq40NOYMYFSS38qj+hpTcLLWwpIwA2A5bje/x5jmVn2tzUMg9IVw==} + /core-js-compat@3.37.0: + resolution: {integrity: sha512-vYq4L+T8aS5UuFg4UwDhc7YNRWVeVZwltad9C/jV3R2LgVOpS9BDr7l/WL6BN0dbV3k1XejPTHqqEzJgsa0frA==} dependencies: - browserslist: 4.22.2 + browserslist: 4.23.0 dev: true /core-util-is@1.0.3: @@ -6005,7 +6052,7 @@ packages: chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.10.7) + jest-config: 29.7.0(@types/node@20.12.7) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -6074,6 +6121,33 @@ packages: engines: {node: '>=8'} dev: true + /data-view-buffer@1.0.1: + resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + dev: true + + /data-view-byte-length@1.0.1: + resolution: {integrity: sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + dev: true + + /data-view-byte-offset@1.0.0: + resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + dev: true + /dateformat@3.0.3: resolution: {integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==} dev: true @@ -6136,8 +6210,8 @@ packages: engines: {node: '>=0.10.0'} dev: true - /dedent@1.5.1: - resolution: {integrity: sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==} + /dedent@1.5.3: + resolution: {integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==} peerDependencies: babel-plugin-macros: ^3.1.0 peerDependenciesMeta: @@ -6168,13 +6242,13 @@ packages: clone: 1.0.4 dev: true - /define-data-property@1.1.1: - resolution: {integrity: sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==} + /define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} dependencies: - get-intrinsic: 1.2.2 + es-define-property: 1.0.0 + es-errors: 1.3.0 gopd: 1.0.1 - has-property-descriptors: 1.0.1 dev: true /define-lazy-prop@2.0.0: @@ -6186,8 +6260,8 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} dependencies: - define-data-property: 1.1.1 - has-property-descriptors: 1.0.1 + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 object-keys: 1.1.1 dev: true @@ -6298,11 +6372,6 @@ packages: engines: {node: '>=12'} dev: true - /dotenv@16.3.1: - resolution: {integrity: sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==} - engines: {node: '>=12'} - dev: true - /dotenv@16.4.5: resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} engines: {node: '>=12'} @@ -6322,16 +6391,16 @@ packages: end-of-stream: 1.4.4 inherits: 2.0.4 readable-stream: 2.3.8 - stream-shift: 1.0.1 + stream-shift: 1.0.3 dev: true - /duplexify@4.1.2: - resolution: {integrity: sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==} + /duplexify@4.1.3: + resolution: {integrity: sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==} dependencies: end-of-stream: 1.4.4 inherits: 2.0.4 readable-stream: 3.6.2 - stream-shift: 1.0.1 + stream-shift: 1.0.3 dev: true /eastasianwidth@0.2.0: @@ -6342,16 +6411,16 @@ packages: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} dev: true - /ejs@3.1.9: - resolution: {integrity: sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==} + /ejs@3.1.10: + resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} engines: {node: '>=0.10.0'} hasBin: true dependencies: jake: 10.8.7 dev: true - /electron-to-chromium@1.4.623: - resolution: {integrity: sha512-lKoz10iCYlP1WtRYdh5MvocQPWVRoI7ysp6qf18bmeBgR8abE6+I2CsfyNKztRDZvhdWc+krKT6wS7Neg8sw3A==} + /electron-to-chromium@1.4.750: + resolution: {integrity: sha512-9ItEpeu15hW5m8jKdriL+BQrgwDTXEL9pn4SkillWFu73ZNNNQ2BKKLS+ZHv2vC9UkNhosAeyfxOf/5OSeTCPA==} dev: true /element-resize-detector@1.2.4: @@ -6418,16 +6487,16 @@ packages: has-binary2: 1.0.3 dev: true - /enhanced-resolve@5.15.0: - resolution: {integrity: sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==} + /enhanced-resolve@5.16.0: + resolution: {integrity: sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==} engines: {node: '>=10.13.0'} dependencies: graceful-fs: 4.2.11 tapable: 2.2.1 dev: true - /envinfo@7.11.0: - resolution: {integrity: sha512-G9/6xF1FPbIw0TtalAMaVPpiq2aDEuKLXM314jPVAO9r2fo2a4BLqMNkmRS7O/xPPZ+COAhGIz3ETvHEV3eUcg==} + /envinfo@7.13.0: + resolution: {integrity: sha512-cvcaMr7KqXVh4nyzGTVqTum+gAiL265x5jUWQIDLq//zOGbW+gSW/C+OWLleY/rs9Qole6AZLMXPbtIFQbqu+Q==} engines: {node: '>=4'} hasBin: true dev: true @@ -6453,72 +6522,98 @@ packages: stackframe: 1.3.4 dev: false - /es-abstract@1.22.3: - resolution: {integrity: sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==} + /es-abstract@1.23.3: + resolution: {integrity: sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==} engines: {node: '>= 0.4'} dependencies: - array-buffer-byte-length: 1.0.0 - arraybuffer.prototype.slice: 1.0.2 - available-typed-arrays: 1.0.5 - call-bind: 1.0.5 - es-set-tostringtag: 2.0.2 + array-buffer-byte-length: 1.0.1 + arraybuffer.prototype.slice: 1.0.3 + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + data-view-buffer: 1.0.1 + data-view-byte-length: 1.0.1 + data-view-byte-offset: 1.0.0 + es-define-property: 1.0.0 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + es-set-tostringtag: 2.0.3 es-to-primitive: 1.2.1 function.prototype.name: 1.1.6 - get-intrinsic: 1.2.2 - get-symbol-description: 1.0.0 + get-intrinsic: 1.2.4 + get-symbol-description: 1.0.2 globalthis: 1.0.3 gopd: 1.0.1 - has-property-descriptors: 1.0.1 - has-proto: 1.0.1 + has-property-descriptors: 1.0.2 + has-proto: 1.0.3 has-symbols: 1.0.3 - hasown: 2.0.0 - internal-slot: 1.0.6 - is-array-buffer: 3.0.2 + hasown: 2.0.2 + internal-slot: 1.0.7 + is-array-buffer: 3.0.4 is-callable: 1.2.7 - is-negative-zero: 2.0.2 + is-data-view: 1.0.1 + is-negative-zero: 2.0.3 is-regex: 1.1.4 - is-shared-array-buffer: 1.0.2 + is-shared-array-buffer: 1.0.3 is-string: 1.0.7 - is-typed-array: 1.1.12 + is-typed-array: 1.1.13 is-weakref: 1.0.2 object-inspect: 1.13.1 object-keys: 1.1.1 object.assign: 4.1.5 - regexp.prototype.flags: 1.5.1 - safe-array-concat: 1.0.1 - safe-regex-test: 1.0.0 - string.prototype.trim: 1.2.8 - string.prototype.trimend: 1.0.7 - string.prototype.trimstart: 1.0.7 - typed-array-buffer: 1.0.0 - typed-array-byte-length: 1.0.0 - typed-array-byte-offset: 1.0.0 - typed-array-length: 1.0.4 + regexp.prototype.flags: 1.5.2 + safe-array-concat: 1.1.2 + safe-regex-test: 1.0.3 + string.prototype.trim: 1.2.9 + string.prototype.trimend: 1.0.8 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.2 + typed-array-byte-length: 1.0.1 + typed-array-byte-offset: 1.0.2 + typed-array-length: 1.0.6 unbox-primitive: 1.0.2 - which-typed-array: 1.1.13 + which-typed-array: 1.1.15 + dev: true + + /es-define-property@1.0.0: + resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.4 + dev: true + + /es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} dev: true /es-module-lexer@0.9.3: resolution: {integrity: sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==} dev: true - /es-module-lexer@1.4.1: - resolution: {integrity: sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==} + /es-module-lexer@1.5.2: + resolution: {integrity: sha512-l60ETUTmLqbVbVHv1J4/qj+M8nq7AwMzEcg3kmJDt9dCNrTk+yHcYFf/Kw75pMDwd9mPcIGCG5LcS20SxYRzFA==} dev: true - /es-set-tostringtag@2.0.2: - resolution: {integrity: sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==} + /es-object-atoms@1.0.0: + resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} engines: {node: '>= 0.4'} dependencies: - get-intrinsic: 1.2.2 - has-tostringtag: 1.0.0 - hasown: 2.0.0 + es-errors: 1.3.0 + dev: true + + /es-set-tostringtag@2.0.3: + resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.4 + has-tostringtag: 1.0.2 + hasown: 2.0.2 dev: true /es-shim-unscopables@1.0.2: resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} dependencies: - hasown: 2.0.0 + hasown: 2.0.2 dev: true /es-to-primitive@1.2.1: @@ -6581,39 +6676,39 @@ packages: '@esbuild/win32-x64': 0.18.20 dev: true - /esbuild@0.19.11: - resolution: {integrity: sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA==} + /esbuild@0.19.12: + resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} engines: {node: '>=12'} hasBin: true requiresBuild: true optionalDependencies: - '@esbuild/aix-ppc64': 0.19.11 - '@esbuild/android-arm': 0.19.11 - '@esbuild/android-arm64': 0.19.11 - '@esbuild/android-x64': 0.19.11 - '@esbuild/darwin-arm64': 0.19.11 - '@esbuild/darwin-x64': 0.19.11 - '@esbuild/freebsd-arm64': 0.19.11 - '@esbuild/freebsd-x64': 0.19.11 - '@esbuild/linux-arm': 0.19.11 - '@esbuild/linux-arm64': 0.19.11 - '@esbuild/linux-ia32': 0.19.11 - '@esbuild/linux-loong64': 0.19.11 - '@esbuild/linux-mips64el': 0.19.11 - '@esbuild/linux-ppc64': 0.19.11 - '@esbuild/linux-riscv64': 0.19.11 - '@esbuild/linux-s390x': 0.19.11 - '@esbuild/linux-x64': 0.19.11 - '@esbuild/netbsd-x64': 0.19.11 - '@esbuild/openbsd-x64': 0.19.11 - '@esbuild/sunos-x64': 0.19.11 - '@esbuild/win32-arm64': 0.19.11 - '@esbuild/win32-ia32': 0.19.11 - '@esbuild/win32-x64': 0.19.11 - dev: true - - /escalade@3.1.1: - resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} + '@esbuild/aix-ppc64': 0.19.12 + '@esbuild/android-arm': 0.19.12 + '@esbuild/android-arm64': 0.19.12 + '@esbuild/android-x64': 0.19.12 + '@esbuild/darwin-arm64': 0.19.12 + '@esbuild/darwin-x64': 0.19.12 + '@esbuild/freebsd-arm64': 0.19.12 + '@esbuild/freebsd-x64': 0.19.12 + '@esbuild/linux-arm': 0.19.12 + '@esbuild/linux-arm64': 0.19.12 + '@esbuild/linux-ia32': 0.19.12 + '@esbuild/linux-loong64': 0.19.12 + '@esbuild/linux-mips64el': 0.19.12 + '@esbuild/linux-ppc64': 0.19.12 + '@esbuild/linux-riscv64': 0.19.12 + '@esbuild/linux-s390x': 0.19.12 + '@esbuild/linux-x64': 0.19.12 + '@esbuild/netbsd-x64': 0.19.12 + '@esbuild/openbsd-x64': 0.19.12 + '@esbuild/sunos-x64': 0.19.12 + '@esbuild/win32-arm64': 0.19.12 + '@esbuild/win32-ia32': 0.19.12 + '@esbuild/win32-x64': 0.19.12 + dev: true + + /escalade@3.1.2: + resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} engines: {node: '>=6'} /escape-html@1.0.3: @@ -6647,13 +6742,13 @@ packages: source-map: 0.6.1 dev: true - /eslint-config-prettier@9.1.0(eslint@8.56.0): + /eslint-config-prettier@9.1.0(eslint@8.57.0): resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} hasBin: true peerDependencies: eslint: '>=7.0.0' dependencies: - eslint: 8.56.0 + eslint: 8.57.0 dev: true /eslint-import-resolver-node@0.3.9: @@ -6666,7 +6761,7 @@ packages: - supports-color dev: true - /eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.18.0)(eslint-plugin-import@2.29.1)(eslint@8.56.0): + /eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0): resolution: {integrity: sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: @@ -6674,12 +6769,12 @@ packages: eslint-plugin-import: '*' dependencies: debug: 4.3.4 - enhanced-resolve: 5.15.0 - eslint: 8.56.0 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.18.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.18.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) + enhanced-resolve: 5.16.0 + eslint: 8.57.0 + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) fast-glob: 3.3.2 - get-tsconfig: 4.7.2 + get-tsconfig: 4.7.3 is-core-module: 2.13.1 is-glob: 4.0.3 transitivePeerDependencies: @@ -6689,8 +6784,8 @@ packages: - supports-color dev: true - /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.18.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0): - resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} + /eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): + resolution: {integrity: sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==} engines: {node: '>=4'} peerDependencies: '@typescript-eslint/parser': '*' @@ -6710,16 +6805,16 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 6.18.0(eslint@8.56.0)(typescript@4.9.5) + '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@4.9.5) debug: 3.2.7 - eslint: 8.56.0 + eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.18.0)(eslint-plugin-import@2.29.1)(eslint@8.56.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0) transitivePeerDependencies: - supports-color dev: true - /eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.18.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0): + /eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==} engines: {node: '>=4'} peerDependencies: @@ -6729,23 +6824,23 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 6.18.0(eslint@8.56.0)(typescript@4.9.5) - array-includes: 3.1.7 - array.prototype.findlastindex: 1.2.3 + '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@4.9.5) + array-includes: 3.1.8 + array.prototype.findlastindex: 1.2.5 array.prototype.flat: 1.3.2 array.prototype.flatmap: 1.3.2 debug: 3.2.7 doctrine: 2.1.0 - eslint: 8.56.0 + eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.18.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) - hasown: 2.0.0 + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + hasown: 2.0.2 is-core-module: 2.13.1 is-glob: 4.0.3 minimatch: 3.1.2 - object.fromentries: 2.0.7 - object.groupby: 1.0.1 - object.values: 1.1.7 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.0 semver: 6.3.1 tsconfig-paths: 3.15.0 transitivePeerDependencies: @@ -6754,11 +6849,11 @@ packages: - supports-color dev: true - /eslint-plugin-jest@27.6.1(@typescript-eslint/eslint-plugin@6.18.0)(eslint@8.56.0)(jest@29.7.0)(typescript@4.9.5): - resolution: {integrity: sha512-WEYkyVXD9NlmFBKvrkmzrC+C9yZoz5pAml2hO19PlS3spJtoiwj4p2u8spd/7zx5IvRsZsCmsoImaAvBB9X93Q==} + /eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint@8.57.0)(jest@29.7.0)(typescript@4.9.5): + resolution: {integrity: sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: - '@typescript-eslint/eslint-plugin': ^5.0.0 || ^6.0.0 + '@typescript-eslint/eslint-plugin': ^5.0.0 || ^6.0.0 || ^7.0.0 eslint: ^7.0.0 || ^8.0.0 jest: '*' peerDependenciesMeta: @@ -6767,9 +6862,9 @@ packages: jest: optional: true dependencies: - '@typescript-eslint/eslint-plugin': 6.18.0(@typescript-eslint/parser@6.18.0)(eslint@8.56.0)(typescript@4.9.5) - '@typescript-eslint/utils': 5.62.0(eslint@8.56.0)(typescript@4.9.5) - eslint: 8.56.0 + '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.0)(typescript@4.9.5) + '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@4.9.5) + eslint: 8.57.0 jest: 29.7.0 transitivePeerDependencies: - supports-color @@ -6797,16 +6892,16 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /eslint@8.56.0: - resolution: {integrity: sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==} + /eslint@8.57.0: + resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@eslint-community/regexpp': 4.10.0 '@eslint/eslintrc': 2.1.4 - '@eslint/js': 8.56.0 - '@humanwhocodes/config-array': 0.11.13 + '@eslint/js': 8.57.0 + '@humanwhocodes/config-array': 0.11.14 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 '@ungap/structured-clone': 1.2.0 @@ -6827,7 +6922,7 @@ packages: glob-parent: 6.0.2 globals: 13.24.0 graphemer: 1.4.0 - ignore: 5.3.0 + ignore: 5.3.1 imurmurhash: 0.1.4 is-glob: 4.0.3 is-path-inside: 3.0.3 @@ -6837,7 +6932,7 @@ packages: lodash.merge: 4.6.2 minimatch: 3.1.2 natural-compare: 1.4.0 - optionator: 0.9.3 + optionator: 0.9.4 strip-ansi: 6.0.1 text-table: 0.2.0 transitivePeerDependencies: @@ -6930,7 +7025,7 @@ packages: human-signals: 4.3.1 is-stream: 3.0.0 merge-stream: 2.0.0 - npm-run-path: 5.2.0 + npm-run-path: 5.3.0 onetime: 6.0.0 signal-exit: 3.0.7 strip-final-newline: 3.0.0 @@ -6945,7 +7040,7 @@ packages: human-signals: 5.0.0 is-stream: 3.0.0 merge-stream: 2.0.0 - npm-run-path: 5.2.0 + npm-run-path: 5.3.0 onetime: 6.0.0 signal-exit: 4.1.0 strip-final-newline: 3.0.0 @@ -6967,16 +7062,16 @@ packages: jest-util: 29.7.0 dev: true - /express@4.18.2: - resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} + /express@4.19.2: + resolution: {integrity: sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==} engines: {node: '>= 0.10.0'} dependencies: accepts: 1.3.8 array-flatten: 1.1.1 - body-parser: 1.20.1 + body-parser: 1.20.2 content-disposition: 0.5.4 content-type: 1.0.5 - cookie: 0.5.0 + cookie: 0.6.0 cookie-signature: 1.0.6 debug: 2.6.9 depd: 2.0.0 @@ -7010,8 +7105,8 @@ packages: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} dev: true - /extract-colors@4.0.2: - resolution: {integrity: sha512-G7v2C3LJqW38U+yRUPD6nJCjBRfdLD7y8efEHn+1qONt1mhj+OZBpzFmiaS+dZiHU/k0dvgK2kgIkbAllHVbRw==} + /extract-colors@4.0.4: + resolution: {integrity: sha512-/ZfiJzkTBpTA46ZrUEmk6mtfQy+xJqS8m5N259oiF3yKjUDJprS0H4buTpMGt5IA7LMLY/gEHVx5Qx/v9PynNQ==} dev: false /extract-zip@1.7.0: @@ -7060,8 +7155,8 @@ packages: resolution: {integrity: sha512-bijHueCGd0LqqNK9b5oCMHc0MluJAx0cwqASgbWMvkO01lCYgIhacVRLcaDz3QnyYIRNJRDwMb41VuT6pHJ91Q==} dev: false - /fastq@1.16.0: - resolution: {integrity: sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==} + /fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} dependencies: reusify: 1.0.4 dev: true @@ -7096,7 +7191,7 @@ packages: flat-cache: 3.2.0 dev: true - /file-loader@6.2.0(webpack@5.89.0): + /file-loader@6.2.0(webpack@5.91.0): resolution: {integrity: sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -7104,7 +7199,7 @@ packages: dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.89.0(esbuild@0.19.11) + webpack: 5.91.0(esbuild@0.19.12) dev: true /file-system-cache@2.3.0: @@ -7199,22 +7294,22 @@ packages: resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} engines: {node: ^10.12.0 || >=12.0.0} dependencies: - flatted: 3.2.9 + flatted: 3.3.1 keyv: 4.5.4 rimraf: 3.0.2 dev: true - /flatted@3.2.9: - resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} + /flatted@3.3.1: + resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} dev: true - /flow-parser@0.225.1: - resolution: {integrity: sha512-50fjR6zbLQcpq5IFNkheUSY/AFPxVeeLiBM5B3NQBSKId2G0cUuExOlDDOguxc49dl9lnh8hI1xcYlPJWNp4KQ==} + /flow-parser@0.235.1: + resolution: {integrity: sha512-s04193L4JE+ntEcQXbD6jxRRlyj9QXcgEl2W6xSjH4l9x4b0eHoCHfbYHjqf9LdZFUiM5LhgpiqsvLj/AyOyYQ==} engines: {node: '>=0.4.0'} dev: true - /follow-redirects@1.15.4(debug@4.3.4): - resolution: {integrity: sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==} + /follow-redirects@1.15.6(debug@4.3.4): + resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} engines: {node: '>=4.0'} peerDependencies: debug: '*' @@ -7257,22 +7352,23 @@ packages: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} dev: true - /framer-motion@10.18.0(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-oGlDh1Q1XqYPksuTD/usb0I70hq95OUzmL9+6Zd+Hs4XV0oaISBa/UUMSjYiq6m8EUF32132mOJ8xVZS+I0S6w==} + /framer-motion@11.1.7(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-cW11Pu53eDAXUEhv5hEiWuIXWhfkbV32PlgVISn7jRdcAiVrJ1S03YQQ0/DzoswGYYwKi4qYmHHjCzAH52eSdQ==} peerDependencies: + '@emotion/is-prop-valid': '*' react: ^18.0.0 react-dom: ^18.0.0 peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true react: optional: true react-dom: optional: true dependencies: - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) tslib: 2.6.2 - optionalDependencies: - '@emotion/is-prop-valid': 0.8.8 dev: false /fresh@0.5.2: @@ -7329,9 +7425,9 @@ packages: resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 + es-abstract: 1.23.3 functions-have-names: 1.2.3 dev: true @@ -7348,13 +7444,15 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - /get-intrinsic@1.2.2: - resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==} + /get-intrinsic@1.2.4: + resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} + engines: {node: '>= 0.4'} dependencies: + es-errors: 1.3.0 function-bind: 1.1.2 - has-proto: 1.0.1 + has-proto: 1.0.3 has-symbols: 1.0.3 - hasown: 2.0.0 + hasown: 2.0.2 dev: true /get-nonce@1.0.1: @@ -7398,32 +7496,33 @@ packages: engines: {node: '>=16'} dev: true - /get-symbol-description@1.0.0: - resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} + /get-symbol-description@1.0.2: + resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 - get-intrinsic: 1.2.2 + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 dev: true - /get-tsconfig@4.7.2: - resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==} + /get-tsconfig@4.7.3: + resolution: {integrity: sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==} dependencies: resolve-pkg-maps: 1.0.0 dev: true - /giget@1.2.1: - resolution: {integrity: sha512-4VG22mopWtIeHwogGSy1FViXVo0YT+m6BrqZfz0JJFwbSsePsCdOzdLIIli5BtMp7Xe8f/o2OmBpQX2NBOC24g==} + /giget@1.2.3: + resolution: {integrity: sha512-8EHPljDvs7qKykr6uw8b+lqLiUc/vUg+KVTI0uND4s63TdsZM2Xus3mflvF0DDG9SiM4RlCkFGL+7aAjRmV7KA==} hasBin: true dependencies: - citty: 0.1.5 + citty: 0.1.6 consola: 3.2.3 defu: 6.1.4 - node-fetch-native: 1.6.1 - nypm: 0.3.4 + node-fetch-native: 1.6.4 + nypm: 0.3.8 ohash: 1.1.3 - pathe: 1.1.1 - tar: 6.2.0 + pathe: 1.1.2 + tar: 6.2.1 dev: true /git-raw-commits@2.0.11: @@ -7493,16 +7592,16 @@ packages: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} dev: true - /glob@10.3.10: - resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} + /glob@10.3.12: + resolution: {integrity: sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==} engines: {node: '>=16 || 14 >=14.17'} hasBin: true dependencies: foreground-child: 3.1.1 jackspeak: 2.3.6 - minimatch: 9.0.3 + minimatch: 9.0.4 minipass: 7.0.4 - path-scurry: 1.10.1 + path-scurry: 1.10.2 dev: true /glob@7.2.3: @@ -7542,7 +7641,7 @@ packages: array-union: 2.1.0 dir-glob: 3.0.1 fast-glob: 3.3.2 - ignore: 5.3.0 + ignore: 5.3.1 merge2: 1.4.1 slash: 3.0.0 dev: true @@ -7554,7 +7653,7 @@ packages: /gopd@1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} dependencies: - get-intrinsic: 1.2.2 + get-intrinsic: 1.2.4 dev: true /graceful-fs@4.2.11: @@ -7619,14 +7718,14 @@ packages: engines: {node: '>=8'} dev: true - /has-property-descriptors@1.0.1: - resolution: {integrity: sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==} + /has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} dependencies: - get-intrinsic: 1.2.2 + es-define-property: 1.0.0 dev: true - /has-proto@1.0.1: - resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} + /has-proto@1.0.3: + resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} engines: {node: '>= 0.4'} dev: true @@ -7635,15 +7734,15 @@ packages: engines: {node: '>= 0.4'} dev: true - /has-tostringtag@1.0.0: - resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} + /has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} dependencies: has-symbols: 1.0.3 dev: true - /hasown@2.0.0: - resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==} + /hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} dependencies: function-bind: 1.1.2 @@ -7656,8 +7755,8 @@ packages: readable-stream: 3.6.2 dev: true - /hls.js@1.4.14: - resolution: {integrity: sha512-UppQjyvPVclg+6t2KY/Rv03h0+bA5u6zwqVoz4LAC/L0fgYmIaCD7ZCrwe8WI1Gv01be1XL0QFsRbSdIHV/Wbw==} + /hls.js@1.5.8: + resolution: {integrity: sha512-hJYMPfLhWO7/7+n4f9pn6bOheCGx0WgvVz7k3ouq3Pp1bja48NN+HeCQu3XCGYzqWQF/wo7Sk6dJAyWVJD8ECA==} /hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} @@ -7765,8 +7864,8 @@ packages: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} dev: true - /ignore@5.3.0: - resolution: {integrity: sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==} + /ignore@5.3.1: + resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} engines: {node: '>= 4'} dev: true @@ -7835,21 +7934,21 @@ packages: fast-loops: 1.1.3 dev: false - /internal-slot@1.0.6: - resolution: {integrity: sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==} + /internal-slot@1.0.7: + resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} engines: {node: '>= 0.4'} dependencies: - get-intrinsic: 1.2.2 - hasown: 2.0.0 - side-channel: 1.0.4 + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.0.6 dev: true - /intl-messageformat@10.5.8: - resolution: {integrity: sha512-NRf0jpBWV0vd671G5b06wNofAN8tp7WWDogMZyaU8GUAsmbouyvgwmFJI7zLjfAMpm3zK+vSwRP3jzaoIcMbaA==} + /intl-messageformat@10.5.11: + resolution: {integrity: sha512-eYq5fkFBVxc7GIFDzpFQkDOZgNayNTQn4Oufe8jw6YY6OHVw70/4pA3FyCsQ0Gb2DnvEJEMmN2tOaXUGByM+kg==} dependencies: - '@formatjs/ecma402-abstract': 1.18.0 + '@formatjs/ecma402-abstract': 1.18.2 '@formatjs/fast-memoize': 2.2.0 - '@formatjs/icu-messageformat-parser': 2.7.3 + '@formatjs/icu-messageformat-parser': 2.7.6 tslib: 2.6.2 dev: false @@ -7858,8 +7957,8 @@ packages: dependencies: loose-envify: 1.4.0 - /ip@2.0.0: - resolution: {integrity: sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==} + /ip@2.0.1: + resolution: {integrity: sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==} dev: true /ipaddr.js@1.9.1: @@ -7876,16 +7975,16 @@ packages: resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 - has-tostringtag: 1.0.0 + call-bind: 1.0.7 + has-tostringtag: 1.0.2 dev: true - /is-array-buffer@3.0.2: - resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} + /is-array-buffer@3.0.4: + resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} + engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 - get-intrinsic: 1.2.2 - is-typed-array: 1.1.12 + call-bind: 1.0.7 + get-intrinsic: 1.2.4 dev: true /is-arrayish@0.2.1: @@ -7902,15 +8001,15 @@ packages: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} dependencies: - binary-extensions: 2.2.0 + binary-extensions: 2.3.0 dev: true /is-boolean-object@1.1.2: resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 - has-tostringtag: 1.0.0 + call-bind: 1.0.7 + has-tostringtag: 1.0.2 dev: true /is-callable@1.2.7: @@ -7921,14 +8020,21 @@ packages: /is-core-module@2.13.1: resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} dependencies: - hasown: 2.0.0 + hasown: 2.0.2 + dev: true + + /is-data-view@1.0.1: + resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==} + engines: {node: '>= 0.4'} + dependencies: + is-typed-array: 1.1.13 dev: true /is-date-object@1.0.5: resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} engines: {node: '>= 0.4'} dependencies: - has-tostringtag: 1.0.0 + has-tostringtag: 1.0.2 dev: true /is-deflate@1.0.0: @@ -7964,7 +8070,7 @@ packages: resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} engines: {node: '>= 0.4'} dependencies: - has-tostringtag: 1.0.0 + has-tostringtag: 1.0.2 dev: true /is-glob@4.0.3: @@ -7988,12 +8094,12 @@ packages: resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 dev: true - /is-negative-zero@2.0.2: - resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} + /is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} engines: {node: '>= 0.4'} dev: true @@ -8001,7 +8107,7 @@ packages: resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} engines: {node: '>= 0.4'} dependencies: - has-tostringtag: 1.0.0 + has-tostringtag: 1.0.2 dev: true /is-number@7.0.0: @@ -8045,14 +8151,15 @@ packages: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 - has-tostringtag: 1.0.0 + call-bind: 1.0.7 + has-tostringtag: 1.0.2 dev: true - /is-shared-array-buffer@1.0.2: - resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} + /is-shared-array-buffer@1.0.3: + resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} + engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 dev: true /is-stream@2.0.1: @@ -8069,7 +8176,7 @@ packages: resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} engines: {node: '>= 0.4'} dependencies: - has-tostringtag: 1.0.0 + has-tostringtag: 1.0.2 dev: true /is-symbol@1.0.4: @@ -8086,11 +8193,11 @@ packages: text-extensions: 1.9.0 dev: true - /is-typed-array@1.1.12: - resolution: {integrity: sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==} + /is-typed-array@1.1.13: + resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} engines: {node: '>= 0.4'} dependencies: - which-typed-array: 1.1.13 + which-typed-array: 1.1.15 dev: true /is-unicode-supported@0.1.0: @@ -8101,7 +8208,7 @@ packages: /is-weakref@1.0.2: resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 dev: true /is-what@3.14.1: @@ -8145,8 +8252,8 @@ packages: resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} engines: {node: '>=8'} dependencies: - '@babel/core': 7.23.7 - '@babel/parser': 7.23.6 + '@babel/core': 7.24.4 + '@babel/parser': 7.24.4 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 6.3.1 @@ -8154,15 +8261,15 @@ packages: - supports-color dev: true - /istanbul-lib-instrument@6.0.1: - resolution: {integrity: sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==} + /istanbul-lib-instrument@6.0.2: + resolution: {integrity: sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw==} engines: {node: '>=10'} dependencies: - '@babel/core': 7.23.7 - '@babel/parser': 7.23.6 + '@babel/core': 7.24.4 + '@babel/parser': 7.24.4 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 - semver: 7.5.4 + semver: 7.6.0 transitivePeerDependencies: - supports-color dev: true @@ -8187,8 +8294,8 @@ packages: - supports-color dev: true - /istanbul-reports@3.1.6: - resolution: {integrity: sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==} + /istanbul-reports@3.1.7: + resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} engines: {node: '>=8'} dependencies: html-escaper: 2.0.2 @@ -8232,10 +8339,10 @@ packages: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.10.7 + '@types/node': 20.12.7 chalk: 4.1.2 co: 4.6.0 - dedent: 1.5.1 + dedent: 1.5.3 is-generator-fn: 2.1.0 jest-each: 29.7.0 jest-matcher-utils: 29.7.0 @@ -8245,7 +8352,7 @@ packages: jest-util: 29.7.0 p-limit: 3.1.0 pretty-format: 29.7.0 - pure-rand: 6.0.4 + pure-rand: 6.1.0 slash: 3.0.0 stack-utils: 2.0.6 transitivePeerDependencies: @@ -8270,7 +8377,7 @@ packages: create-jest: 29.7.0 exit: 0.1.2 import-local: 3.1.0 - jest-config: 29.7.0(@types/node@20.10.7) + jest-config: 29.7.0(@types/node@20.12.7) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -8281,7 +8388,7 @@ packages: - ts-node dev: true - /jest-config@29.7.0(@types/node@20.10.7): + /jest-config@29.7.0(@types/node@20.12.7): resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: @@ -8293,11 +8400,11 @@ packages: ts-node: optional: true dependencies: - '@babel/core': 7.23.7 + '@babel/core': 7.24.4 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.10.7 - babel-jest: 29.7.0(@babel/core@7.23.7) + '@types/node': 20.12.7 + babel-jest: 29.7.0(@babel/core@7.24.4) chalk: 4.1.2 ci-info: 3.9.0 deepmerge: 4.3.1 @@ -8356,7 +8463,7 @@ packages: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.10.7 + '@types/node': 20.12.7 jest-mock: 29.7.0 jest-util: 29.7.0 dev: true @@ -8372,7 +8479,7 @@ packages: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 20.10.7 + '@types/node': 20.12.7 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -8407,7 +8514,7 @@ packages: resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@babel/code-frame': 7.23.5 + '@babel/code-frame': 7.24.2 '@jest/types': 29.6.3 '@types/stack-utils': 2.0.3 chalk: 4.1.2 @@ -8423,7 +8530,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.10.7 + '@types/node': 20.12.7 jest-util: 29.7.0 dev: true @@ -8478,7 +8585,7 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.10.7 + '@types/node': 20.12.7 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -8509,9 +8616,9 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.10.7 + '@types/node': 20.12.7 chalk: 4.1.2 - cjs-module-lexer: 1.2.3 + cjs-module-lexer: 1.3.1 collect-v8-coverage: 1.0.2 glob: 7.2.3 graceful-fs: 4.2.11 @@ -8532,15 +8639,15 @@ packages: resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@babel/core': 7.23.7 - '@babel/generator': 7.23.6 - '@babel/plugin-syntax-jsx': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-syntax-typescript': 7.23.3(@babel/core@7.23.7) - '@babel/types': 7.23.6 + '@babel/core': 7.24.4 + '@babel/generator': 7.24.4 + '@babel/plugin-syntax-jsx': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-syntax-typescript': 7.24.1(@babel/core@7.24.4) + '@babel/types': 7.24.0 '@jest/expect-utils': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - babel-preset-current-node-syntax: 1.0.1(@babel/core@7.23.7) + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.24.4) chalk: 4.1.2 expect: 29.7.0 graceful-fs: 4.2.11 @@ -8551,7 +8658,7 @@ packages: jest-util: 29.7.0 natural-compare: 1.4.0 pretty-format: 29.7.0 - semver: 7.5.4 + semver: 7.6.0 transitivePeerDependencies: - supports-color dev: true @@ -8561,7 +8668,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.10.7 + '@types/node': 20.12.7 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -8586,7 +8693,7 @@ packages: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.10.7 + '@types/node': 20.12.7 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -8598,7 +8705,7 @@ packages: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 20.10.7 + '@types/node': 20.12.7 merge-stream: 2.0.0 supports-color: 8.1.1 dev: true @@ -8607,7 +8714,7 @@ packages: resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@types/node': 20.10.7 + '@types/node': 20.12.7 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -8639,8 +8746,8 @@ packages: engines: {node: '>=10'} dev: true - /js-base64@3.7.5: - resolution: {integrity: sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA==} + /js-base64@3.7.7: + resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==} dev: true /js-cookie@2.2.1: @@ -8669,8 +8776,8 @@ packages: argparse: 2.0.1 dev: true - /jscodeshift@0.15.1(@babel/preset-env@7.23.7): - resolution: {integrity: sha512-hIJfxUy8Rt4HkJn/zZPU9ChKfKZM1342waJ1QC2e2YsPcWhM+3BJ4dcfQCzArTrk1jJeNLB341H+qOcEHRxJZg==} + /jscodeshift@0.15.2(@babel/preset-env@7.24.4): + resolution: {integrity: sha512-FquR7Okgmc4Sd0aEDwqho3rEiKR3BdvuG9jfdHjLJ6JQoWSMpavug3AoIfnfWhxFlf+5pzQh8qjqz0DWFrNQzA==} hasBin: true peerDependencies: '@babel/preset-env': ^7.1.6 @@ -8678,25 +8785,25 @@ packages: '@babel/preset-env': optional: true dependencies: - '@babel/core': 7.23.7 - '@babel/parser': 7.23.6 - '@babel/plugin-transform-class-properties': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-modules-commonjs': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-nullish-coalescing-operator': 7.23.4(@babel/core@7.23.7) - '@babel/plugin-transform-optional-chaining': 7.23.4(@babel/core@7.23.7) - '@babel/plugin-transform-private-methods': 7.23.3(@babel/core@7.23.7) - '@babel/preset-env': 7.23.7(@babel/core@7.23.7) - '@babel/preset-flow': 7.23.3(@babel/core@7.23.7) - '@babel/preset-typescript': 7.23.3(@babel/core@7.23.7) - '@babel/register': 7.23.7(@babel/core@7.23.7) - babel-core: 7.0.0-bridge.0(@babel/core@7.23.7) + '@babel/core': 7.24.4 + '@babel/parser': 7.24.4 + '@babel/plugin-transform-class-properties': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-modules-commonjs': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-nullish-coalescing-operator': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-optional-chaining': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-private-methods': 7.24.1(@babel/core@7.24.4) + '@babel/preset-env': 7.24.4(@babel/core@7.24.4) + '@babel/preset-flow': 7.24.1(@babel/core@7.24.4) + '@babel/preset-typescript': 7.24.1(@babel/core@7.24.4) + '@babel/register': 7.23.7(@babel/core@7.24.4) + babel-core: 7.0.0-bridge.0(@babel/core@7.24.4) chalk: 4.1.2 - flow-parser: 0.225.1 + flow-parser: 0.235.1 graceful-fs: 4.2.11 micromatch: 4.0.5 neo-async: 2.6.2 node-dir: 0.1.17 - recast: 0.23.4 + recast: 0.23.6 temp: 0.8.4 write-file-atomic: 2.4.3 transitivePeerDependencies: @@ -8785,7 +8892,7 @@ packages: engines: {node: '>=14.0.0'} dependencies: app-root-dir: 1.0.2 - dotenv: 16.3.1 + dotenv: 16.4.5 dotenv-expand: 10.0.0 dev: true @@ -8830,8 +8937,8 @@ packages: engines: {node: '>=10'} dev: true - /lilconfig@3.0.0: - resolution: {integrity: sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==} + /lilconfig@3.1.1: + resolution: {integrity: sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==} engines: {node: '>=14'} dev: true @@ -8846,14 +8953,14 @@ packages: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} dev: true - /linkify-react@4.1.3(linkifyjs@4.1.3)(react@18.2.0): + /linkify-react@4.1.3(linkifyjs@4.1.3)(react@18.3.1): resolution: {integrity: sha512-rhI3zM/fxn5BfRPHfi4r9N7zgac4vOIxub1wHIWXLA5ENTMs+BGaIaFO1D1PhmxgwhIKmJz3H7uCP0Dg5JwSlA==} peerDependencies: linkifyjs: ^4.0.0 react: '>= 15.0.0' dependencies: linkifyjs: 4.1.3 - react: 18.2.0 + react: 18.3.1 dev: false /linkifyjs@4.1.3: @@ -8893,7 +9000,7 @@ packages: colorette: 2.0.20 eventemitter3: 5.0.1 log-update: 5.0.1 - rfdc: 1.3.0 + rfdc: 1.3.1 wrap-ansi: 8.1.0 dev: true @@ -9008,8 +9115,8 @@ packages: dependencies: js-tokens: 4.0.0 - /lru-cache@10.1.0: - resolution: {integrity: sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==} + /lru-cache@10.2.2: + resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} engines: {node: 14 || >=16.14} dev: true @@ -9039,9 +9146,8 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true - /magic-string@0.30.5: - resolution: {integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==} - engines: {node: '>=12'} + /magic-string@0.30.10: + resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} dependencies: '@jridgewell/sourcemap-codec': 1.4.15 dev: true @@ -9065,7 +9171,7 @@ packages: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} dependencies: - semver: 7.5.4 + semver: 7.6.0 dev: true /make-error@1.3.6: @@ -9092,13 +9198,13 @@ packages: resolution: {integrity: sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg==} dev: true - /markdown-to-jsx@7.4.0(react@18.2.0): - resolution: {integrity: sha512-zilc+MIkVVXPyTb4iIUTIz9yyqfcWjszGXnwF9K/aiBWcHXFcmdEMTkG01/oQhwSCH7SY1BnG6+ev5BzWmbPrg==} + /markdown-to-jsx@7.4.7(react@18.3.1): + resolution: {integrity: sha512-0+ls1IQZdU6cwM1yu0ZjjiVWYtkbExSyUIFU2ZeDIFuZM1W42Mh4OlJ4nb4apX4H8smxDHRdFaoIVJGwfv5hkg==} engines: {node: '>= 10'} peerDependencies: react: '>= 0.14.0' dependencies: - react: 18.2.0 + react: 18.3.1 dev: true /mdast-util-definitions@4.0.0: @@ -9239,6 +9345,13 @@ packages: brace-expansion: 2.0.1 dev: true + /minimatch@9.0.4: + resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + brace-expansion: 2.0.1 + dev: true + /minimist-options@4.1.0: resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} engines: {node: '>= 6'} @@ -9321,7 +9434,7 @@ packages: commist: 1.1.0 concat-stream: 2.0.0 debug: 4.3.4 - duplexify: 4.1.2 + duplexify: 4.1.3 help-me: 3.0.0 inherits: 2.0.4 lru-cache: 6.0.0 @@ -9331,7 +9444,7 @@ packages: pump: 3.0.0 readable-stream: 3.6.2 reinterval: 1.1.0 - rfdc: 1.3.0 + rfdc: 1.3.1 split2: 3.2.2 ws: 7.5.9 xtend: 4.0.2 @@ -9361,7 +9474,7 @@ packages: thenify-all: 1.6.0 dev: true - /nano-css@5.6.1(react-dom@18.2.0)(react@18.2.0): + /nano-css@5.6.1(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-T2Mhc//CepkTa3X4pUhKgbEheJHYAxD0VptuqFhDbGMUWVV2m+lkNiW/Ieuj35wrfC8Zm0l7HvssQh7zcEttSw==} peerDependencies: react: '*' @@ -9372,11 +9485,11 @@ packages: csstype: 3.1.3 fastest-stable-stringify: 2.0.2 inline-style-prefixer: 7.0.0 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) rtl-css-js: 1.16.1 stacktrace-js: 2.0.2 - stylis: 4.3.1 + stylis: 4.3.2 dev: false /nanoid@3.3.7: @@ -9416,8 +9529,8 @@ packages: minimatch: 3.1.2 dev: true - /node-fetch-native@1.6.1: - resolution: {integrity: sha512-bW9T/uJDPAJB2YNYEpWzE54U5O3MQidXsOyTfnbKYtTtFexRvGzb1waphBN4ZwP6EcIvYYEOwW0b72BpAqydTw==} + /node-fetch-native@1.6.4: + resolution: {integrity: sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==} dev: true /node-fetch@2.7.0: @@ -9455,7 +9568,7 @@ packages: dependencies: hosted-git-info: 4.1.0 is-core-module: 2.13.1 - semver: 7.5.4 + semver: 7.6.0 validate-npm-package-license: 3.0.4 dev: true @@ -9476,8 +9589,8 @@ packages: path-key: 3.1.1 dev: true - /npm-run-path@5.2.0: - resolution: {integrity: sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==} + /npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: path-key: 4.0.0 @@ -9492,15 +9605,16 @@ packages: - supports-color dev: true - /nypm@0.3.4: - resolution: {integrity: sha512-1JLkp/zHBrkS3pZ692IqOaIKSYHmQXgqfELk6YTOfVBnwealAmPA1q2kKK7PHJAHSMBozerThEFZXP3G6o7Ukg==} + /nypm@0.3.8: + resolution: {integrity: sha512-IGWlC6So2xv6V4cIDmoV0SwwWx7zLG086gyqkyumteH2fIgCAM4nDVFB2iDRszDvmdSVW9xb1N+2KjQ6C7d4og==} engines: {node: ^14.16.0 || >=16.10.0} hasBin: true dependencies: - citty: 0.1.5 + citty: 0.1.6 + consola: 3.2.3 execa: 8.0.1 - pathe: 1.1.1 - ufo: 1.3.2 + pathe: 1.1.2 + ufo: 1.5.3 dev: true /object-assign@4.1.1: @@ -9520,11 +9634,11 @@ packages: resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} dev: true - /object-is@1.1.5: - resolution: {integrity: sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==} + /object-is@1.1.6: + resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 dev: true @@ -9537,37 +9651,38 @@ packages: resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 has-symbols: 1.0.3 object-keys: 1.1.1 dev: true - /object.fromentries@2.0.7: - resolution: {integrity: sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==} + /object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 dev: true - /object.groupby@1.0.1: - resolution: {integrity: sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==} + /object.groupby@1.0.3: + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 - get-intrinsic: 1.2.2 + es-abstract: 1.23.3 dev: true - /object.values@1.1.7: - resolution: {integrity: sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==} + /object.values@1.2.0: + resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 + es-object-atoms: 1.0.0 dev: true /ohash@1.1.3: @@ -9615,16 +9730,16 @@ packages: is-wsl: 2.2.0 dev: true - /optionator@0.9.3: - resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} + /optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} dependencies: - '@aashutoshrathi/word-wrap': 1.2.6 deep-is: 0.1.4 fast-levenshtein: 2.0.6 levn: 0.4.1 prelude-ls: 1.2.1 type-check: 0.4.0 + word-wrap: 1.2.5 dev: true /ora@5.4.1: @@ -9731,7 +9846,7 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} dependencies: - '@babel/code-frame': 7.23.5 + '@babel/code-frame': 7.24.2 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -9788,11 +9903,11 @@ packages: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} dev: true - /path-scurry@1.10.1: - resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} + /path-scurry@1.10.2: + resolution: {integrity: sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==} engines: {node: '>=16 || 14 >=14.17'} dependencies: - lru-cache: 10.1.0 + lru-cache: 10.2.2 minipass: 7.0.4 dev: true @@ -9812,8 +9927,8 @@ packages: engines: {node: '>=8'} dev: true - /pathe@1.1.1: - resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==} + /pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} dev: true /peek-stream@1.1.3: @@ -9889,7 +10004,12 @@ packages: resolution: {integrity: sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==} engines: {node: '>=10'} dependencies: - '@babel/runtime': 7.23.7 + '@babel/runtime': 7.24.4 + + /possible-typed-array-names@1.0.0: + resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} + engines: {node: '>= 0.4'} + dev: true /postcss-load-config@3.1.4(postcss@8.4.38): resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} @@ -9908,7 +10028,7 @@ packages: yaml: 1.10.2 dev: true - /postcss-load-config@4.0.2(postcss@8.4.33): + /postcss-load-config@4.0.2(postcss@8.4.38): resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} engines: {node: '>= 14'} peerDependencies: @@ -9920,9 +10040,9 @@ packages: ts-node: optional: true dependencies: - lilconfig: 3.0.0 - postcss: 8.4.33 - yaml: 2.3.4 + lilconfig: 3.1.1 + postcss: 8.4.38 + yaml: 2.4.2 dev: true /postcss-modules-extract-imports@3.1.0(postcss@8.4.38): @@ -9974,16 +10094,7 @@ packages: dependencies: nanoid: 3.3.7 picocolors: 1.0.0 - source-map-js: 1.0.2 - dev: true - - /postcss@8.4.33: - resolution: {integrity: sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==} - engines: {node: ^10 || ^12 || >=14} - dependencies: - nanoid: 3.3.7 - picocolors: 1.0.0 - source-map-js: 1.0.2 + source-map-js: 1.2.0 dev: true /postcss@8.4.38: @@ -10018,7 +10129,7 @@ packages: dependencies: '@jest/schemas': 29.6.3 ansi-styles: 5.2.0 - react-is: 18.2.0 + react-is: 18.3.1 dev: true /pretty-hrtime@1.0.3: @@ -10120,8 +10231,8 @@ packages: - utf-8-validate dev: true - /pure-rand@6.0.4: - resolution: {integrity: sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==} + /pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} dev: true /q@1.5.1: @@ -10133,14 +10244,14 @@ packages: resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} engines: {node: '>=0.6'} dependencies: - side-channel: 1.0.4 + side-channel: 1.0.6 dev: true - /qs@6.11.2: - resolution: {integrity: sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==} + /qs@6.12.1: + resolution: {integrity: sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==} engines: {node: '>=0.6'} dependencies: - side-channel: 1.0.4 + side-channel: 1.0.6 dev: true /queue-microtask@1.2.3: @@ -10167,8 +10278,8 @@ packages: engines: {node: '>= 0.6'} dev: true - /raw-body@2.5.1: - resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==} + /raw-body@2.5.2: + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} dependencies: bytes: 3.1.2 @@ -10177,14 +10288,14 @@ packages: unpipe: 1.0.0 dev: true - /react-colorful@5.6.1(react-dom@18.2.0)(react@18.2.0): + /react-colorful@5.6.1(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' dependencies: - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) dev: true /react-docgen-typescript@2.2.2(typescript@4.9.5): @@ -10195,13 +10306,13 @@ packages: typescript: 4.9.5 dev: true - /react-docgen@7.0.1: - resolution: {integrity: sha512-rCz0HBIT0LWbIM+///LfRrJoTKftIzzwsYDf0ns5KwaEjejMHQRtphcns+IXFHDNY9pnz6G8l/JbbI6pD4EAIA==} + /react-docgen@7.0.3: + resolution: {integrity: sha512-i8aF1nyKInZnANZ4uZrH49qn1paRgBZ7wZiCNBMnenlPzEv0mRl+ShpTVEI6wZNl8sSc79xZkivtgLKQArcanQ==} engines: {node: '>=16.14.0'} dependencies: - '@babel/core': 7.23.7 - '@babel/traverse': 7.23.7 - '@babel/types': 7.23.6 + '@babel/core': 7.24.4 + '@babel/traverse': 7.24.1 + '@babel/types': 7.24.0 '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.20.5 '@types/doctrine': 0.0.9 @@ -10213,16 +10324,16 @@ packages: - supports-color dev: true - /react-dom@18.2.0(react@18.2.0): - resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} + /react-dom@18.3.1(react@18.3.1): + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} peerDependencies: - react: ^18.2.0 + react: ^18.3.1 dependencies: loose-envify: 1.4.0 - react: 18.2.0 - scheduler: 0.23.0 + react: 18.3.1 + scheduler: 0.23.2 - /react-element-to-jsx-string@15.0.0(react-dom@18.2.0)(react@18.2.0): + /react-element-to-jsx-string@15.0.0(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-UDg4lXB6BzlobN60P8fHWVPX3Kyw8ORrTeBtClmIlGdkOOE+GYQSFvmEU5iLLpwp/6v42DINwNcwOhOLfQ//FQ==} peerDependencies: react: ^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0 @@ -10230,56 +10341,56 @@ packages: dependencies: '@base2/pretty-print-object': 1.0.1 is-plain-object: 5.0.0 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) react-is: 18.1.0 dev: true - /react-hook-form@7.49.2(react@18.2.0): - resolution: {integrity: sha512-TZcnSc17+LPPVpMRIDNVITY6w20deMdNi6iehTFLV1x8SqThXGwu93HjlUVU09pzFgZH7qZOvLMM7UYf2ShAHA==} - engines: {node: '>=18', pnpm: '8'} + /react-hook-form@7.51.3(react@18.3.1): + resolution: {integrity: sha512-cvJ/wbHdhYx8aviSWh28w9ImjmVsb5Y05n1+FW786vEZQJV5STNM0pW6ujS+oiBecb0ARBxJFyAnXj9+GHXACQ==} + engines: {node: '>=12.22.0'} peerDependencies: react: ^16.8.0 || ^17 || ^18 dependencies: - react: 18.2.0 + react: 18.3.1 dev: false - /react-infinite-scroll-component@6.1.0(react@18.2.0): + /react-infinite-scroll-component@6.1.0(react@18.3.1): resolution: {integrity: sha512-SQu5nCqy8DxQWpnUVLx7V7b7LcA37aM7tvoWjTLZp1dk6EJibM5/4EJKzOnl07/BsM1Y40sKLuqjCwwH/xV0TQ==} peerDependencies: react: '>=16.0.0' dependencies: - react: 18.2.0 + react: 18.3.1 throttle-debounce: 2.3.0 dev: false - /react-insta-stories@2.6.2(react@18.2.0): + /react-insta-stories@2.6.2(react@18.3.1): resolution: {integrity: sha512-eM1YHr92bV7WK5h9sECjyYnqZtPxnzJrZFr9IaoDcaZaAEOHVRav+pST513DIG8Hk8QjSTHtdvHHZ0Ka5HwH8w==} peerDependencies: react: '>=16.8.2' dependencies: - react: 18.2.0 + react: 18.3.1 dev: false - /react-intl@6.5.5(react@18.2.0)(typescript@4.9.5): - resolution: {integrity: sha512-cI5UKvBh4tc1zxLIziHBYGMX3dhYWDEFlvUDVN6NfT2i96zTXz/zH2AmM8+2waqgOhwkFUzd+7kK1G9q7fiC2g==} + /react-intl@6.6.5(react@18.3.1)(typescript@4.9.5): + resolution: {integrity: sha512-OErDPbGqus0QKVj77MGCC9Plbnys3CDQrq6Lw41c60pmeTdn41AhoS1SIzXG6SUlyF7qNN2AVqfrrIvHUgSyLQ==} peerDependencies: react: ^16.6.0 || 17 || 18 - typescript: '5' + typescript: ^4.7 || 5 peerDependenciesMeta: typescript: optional: true dependencies: - '@formatjs/ecma402-abstract': 1.18.0 - '@formatjs/icu-messageformat-parser': 2.7.3 - '@formatjs/intl': 2.9.9(typescript@4.9.5) - '@formatjs/intl-displaynames': 6.6.4 - '@formatjs/intl-listformat': 7.5.3 + '@formatjs/ecma402-abstract': 1.18.2 + '@formatjs/icu-messageformat-parser': 2.7.6 + '@formatjs/intl': 2.10.1(typescript@4.9.5) + '@formatjs/intl-displaynames': 6.6.6 + '@formatjs/intl-listformat': 7.5.5 '@types/hoist-non-react-statics': 3.3.5 - '@types/react': 17.0.74 + '@types/react': 17.0.80 hoist-non-react-statics: 3.3.2 - intl-messageformat: 10.5.8 - react: 18.2.0 + intl-messageformat: 10.5.11 + react: 18.3.1 tslib: 2.6.2 typescript: 4.9.5 dev: false @@ -10291,19 +10402,19 @@ packages: resolution: {integrity: sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==} dev: true - /react-is@18.2.0: - resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + /react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} dev: true - /react-loading-skeleton@3.3.1(react@18.2.0): - resolution: {integrity: sha512-NilqqwMh2v9omN7LteiDloEVpFyMIa0VGqF+ukqp0ncVlYu1sKYbYGX9JEl+GtOT9TKsh04zCHAbavnQ2USldA==} + /react-loading-skeleton@3.4.0(react@18.3.1): + resolution: {integrity: sha512-1oJEBc9+wn7BbkQQk7YodlYEIjgeR+GrRjD+QXkVjwZN7LGIcAFHrx4NhT7UHGBxNY1+zax3c+Fo6XQM4R7CgA==} peerDependencies: react: '>=16.8.0' dependencies: - react: 18.2.0 + react: 18.3.1 dev: false - /react-mentions@4.4.10(react-dom@18.2.0)(react@18.2.0): + /react-mentions@4.4.10(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-JHiQlgF1oSZR7VYPjq32wy97z1w1oE4x10EuhKjPr4WUKhVzG1uFQhQjKqjQkbVqJrmahf+ldgBTv36NrkpKpA==} peerDependencies: react: '>=16.8.3' @@ -10312,35 +10423,35 @@ packages: '@babel/runtime': 7.4.5 invariant: 2.2.4 prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - substyle: 9.4.1(react@18.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + substyle: 9.4.1(react@18.3.1) dev: false - /react-modal-sheet@2.2.0(framer-motion@10.18.0)(react@18.2.0): + /react-modal-sheet@2.2.0(framer-motion@11.1.7)(react@18.3.1): resolution: {integrity: sha512-OAIWuVWxMx3zQqrMLbYWnczadplg0WLd+AaBWmN5+ysNF5/pneqjkOV3AWaIZOCIF4TcrejiCsTduutbzCRP2Q==} engines: {node: '>=16'} peerDependencies: framer-motion: '>=6' react: '>=16' dependencies: - '@react-aria/utils': 3.17.0(react@18.2.0) - framer-motion: 10.18.0(react-dom@18.2.0)(react@18.2.0) - react: 18.2.0 + '@react-aria/utils': 3.17.0(react@18.3.1) + framer-motion: 11.1.7(react-dom@18.3.1)(react@18.3.1) + react: 18.3.1 dev: false - /react-native-uuid@2.0.1: - resolution: {integrity: sha512-cptnoIbL53GTCrWlb/+jrDC6tvb7ypIyzbXNJcpR3Vab0mkeaaVd5qnB3f0whXYzS+SMoSQLcUUB0gEWqkPC0g==} + /react-native-uuid@2.0.2: + resolution: {integrity: sha512-5ypj/hV58P+6VREdjkW0EudSibsH3WdqDERoHKnD9syFWjF+NfRWWrJb2sa3LIwI5zpzMvUiabs+DX40WHpEMw==} engines: {node: '>=10.0.0', npm: '>=6.0.0'} dev: true - /react-refresh@0.14.0: - resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==} + /react-refresh@0.14.2: + resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} dev: true - /react-remove-scroll-bar@2.3.4(@types/react@17.0.74)(react@18.2.0): - resolution: {integrity: sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==} + /react-remove-scroll-bar@2.3.6(@types/react@17.0.80)(react@18.3.1): + resolution: {integrity: sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==} engines: {node: '>=10'} peerDependencies: '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -10349,13 +10460,13 @@ packages: '@types/react': optional: true dependencies: - '@types/react': 17.0.74 - react: 18.2.0 - react-style-singleton: 2.2.1(@types/react@17.0.74)(react@18.2.0) + '@types/react': 17.0.80 + react: 18.3.1 + react-style-singleton: 2.2.1(@types/react@17.0.80)(react@18.3.1) tslib: 2.6.2 dev: true - /react-remove-scroll@2.5.5(@types/react@17.0.74)(react@18.2.0): + /react-remove-scroll@2.5.5(@types/react@17.0.80)(react@18.3.1): resolution: {integrity: sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==} engines: {node: '>=10'} peerDependencies: @@ -10365,36 +10476,36 @@ packages: '@types/react': optional: true dependencies: - '@types/react': 17.0.74 - react: 18.2.0 - react-remove-scroll-bar: 2.3.4(@types/react@17.0.74)(react@18.2.0) - react-style-singleton: 2.2.1(@types/react@17.0.74)(react@18.2.0) + '@types/react': 17.0.80 + react: 18.3.1 + react-remove-scroll-bar: 2.3.6(@types/react@17.0.80)(react@18.3.1) + react-style-singleton: 2.2.1(@types/react@17.0.80)(react@18.3.1) tslib: 2.6.2 - use-callback-ref: 1.3.1(@types/react@17.0.74)(react@18.2.0) - use-sidecar: 1.1.2(@types/react@17.0.74)(react@18.2.0) + use-callback-ref: 1.3.2(@types/react@17.0.80)(react@18.3.1) + use-sidecar: 1.1.2(@types/react@17.0.80)(react@18.3.1) dev: true - /react-router-dom@6.21.1(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-QCNrtjtDPwHDO+AO21MJd7yIcr41UetYt5jzaB9Y1UYaPTCnVuJq6S748g1dE11OQlCFIQg+RtAA1SEZIyiBeA==} + /react-router-dom@6.23.0(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-Q9YaSYvubwgbal2c9DJKfx6hTNoBp3iJDsl+Duva/DwxoJH+OTXkxGpql4iUK2sla/8z4RpjAm6EWx1qUDuopQ==} engines: {node: '>=14.0.0'} peerDependencies: react: '>=16.8' react-dom: '>=16.8' dependencies: - '@remix-run/router': 1.14.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-router: 6.21.1(react@18.2.0) + '@remix-run/router': 1.16.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-router: 6.23.0(react@18.3.1) dev: true - /react-router@6.21.1(react@18.2.0): - resolution: {integrity: sha512-W0l13YlMTm1YrpVIOpjCADJqEUpz1vm+CMo47RuFX4Ftegwm6KOYsL5G3eiE52jnJpKvzm6uB/vTKTPKM8dmkA==} + /react-router@6.23.0(react@18.3.1): + resolution: {integrity: sha512-wPMZ8S2TuPadH0sF5irFGjkNLIcRvOSaEe7v+JER8508dyJumm6XZB1u5kztlX0RVq6AzRVndzqcUh6sFIauzA==} engines: {node: '>=14.0.0'} peerDependencies: react: '>=16.8' dependencies: - '@remix-run/router': 1.14.1 - react: 18.2.0 + '@remix-run/router': 1.16.0 + react: 18.3.1 dev: true /react-sizeme@3.0.2: @@ -10406,7 +10517,7 @@ packages: throttle-debounce: 3.0.1 dev: false - /react-style-singleton@2.2.1(@types/react@17.0.74)(react@18.2.0): + /react-style-singleton@2.2.1(@types/react@17.0.80)(react@18.3.1): resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} engines: {node: '>=10'} peerDependencies: @@ -10416,46 +10527,46 @@ packages: '@types/react': optional: true dependencies: - '@types/react': 17.0.74 + '@types/react': 17.0.80 get-nonce: 1.0.1 invariant: 2.2.4 - react: 18.2.0 + react: 18.3.1 tslib: 2.6.2 dev: true - /react-textarea-autosize@8.5.3(@types/react@17.0.74)(react@18.2.0): + /react-textarea-autosize@8.5.3(@types/react@17.0.80)(react@18.3.1): resolution: {integrity: sha512-XT1024o2pqCuZSuBt9FwHlaDeNtVrtCXu0Rnz88t1jUGheCLa3PhjE1GH8Ctm2axEtvdCl5SUHYschyQ0L5QHQ==} engines: {node: '>=10'} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - '@babel/runtime': 7.23.7 - react: 18.2.0 - use-composed-ref: 1.3.0(react@18.2.0) - use-latest: 1.2.1(@types/react@17.0.74)(react@18.2.0) + '@babel/runtime': 7.24.4 + react: 18.3.1 + use-composed-ref: 1.3.0(react@18.3.1) + use-latest: 1.2.1(@types/react@17.0.80)(react@18.3.1) transitivePeerDependencies: - '@types/react' dev: false - /react-timeago@7.2.0(react@18.2.0): + /react-timeago@7.2.0(react@18.3.1): resolution: {integrity: sha512-2KsBEEs+qRhKx/kekUVNSTIpop3Jwd7SRBm0R4Eiq3mPeswRGSsftY9FpKsE/lXLdURyQFiHeHFrIUxLYskG5g==} peerDependencies: react: ^16.0.0 || ^17.0.0 || ^18.0.0 dependencies: - react: 18.2.0 + react: 18.3.1 dev: false - /react-tiny-popover@7.2.4(react-dom@18.2.0)(react@18.2.0): + /react-tiny-popover@7.2.4(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-T7ZSwXcUtPXCog3Bux9+TjoTvUeMi/+zI0Yv/TkIznZCWUg0XTt2797G0IiT5mTVeJeLivUzdOmKA1hOQdMfOQ==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) dev: false - /react-truncate-markup@5.1.2(react@18.2.0): + /react-truncate-markup@5.1.2(react@18.3.1): resolution: {integrity: sha512-eEq6T8Rs+wz98cRYzQECGFNBfXwRYraLg/kz52f6DRBKmzxqB+GYLeDkVe/zrC+2vh5AEwM6nSYFvDWEBljd0w==} peerDependencies: react: '>=16.3' @@ -10463,22 +10574,22 @@ packages: line-height: 0.3.1 memoize-one: 5.2.1 prop-types: 15.8.1 - react: 18.2.0 + react: 18.3.1 resize-observer-polyfill: 1.5.1 dev: false - /react-universal-interface@0.6.2(react@18.2.0)(tslib@2.6.2): + /react-universal-interface@0.6.2(react@18.3.1)(tslib@2.6.2): resolution: {integrity: sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw==} peerDependencies: react: '*' tslib: '*' dependencies: - react: 18.2.0 + react: 18.3.1 tslib: 2.6.2 dev: false - /react-use@17.4.2(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-1jPtmWLD8OJJNYCdYLJEH/HM+bPDfJuyGwCYeJFgPmWY8ttwpgZnW5QnzgM55CYUByUiTjHxsGOnEpLl6yQaoQ==} + /react-use@17.5.0(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-PbfwSPMwp/hoL847rLnm/qkjg3sTRCvn6YhUZiHaUa3FA6/aNoFX79ul5Xt70O1rK+9GxSVqkY0eTwMdsR/bWg==} peerDependencies: react: '*' react-dom: '*' @@ -10489,10 +10600,10 @@ packages: fast-deep-equal: 3.1.3 fast-shallow-equal: 1.0.0 js-cookie: 2.2.1 - nano-css: 5.6.1(react-dom@18.2.0)(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-universal-interface: 0.6.2(react@18.2.0)(tslib@2.6.2) + nano-css: 5.6.1(react-dom@18.3.1)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-universal-interface: 0.6.2(react@18.3.1)(tslib@2.6.2) resize-observer-polyfill: 1.5.1 screenfull: 5.2.0 set-harmonic-interval: 1.0.1 @@ -10501,8 +10612,8 @@ packages: tslib: 2.6.2 dev: false - /react@18.2.0: - resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} + /react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} dependencies: loose-envify: 1.4.0 @@ -10571,14 +10682,14 @@ packages: picomatch: 2.3.1 dev: true - /recast@0.23.4: - resolution: {integrity: sha512-qtEDqIZGVcSZCHniWwZWbRy79Dc6Wp3kT/UmDA2RJKBPg7+7k51aQBZirHmUGn5uvHf2rg8DkjizrN26k61ATw==} + /recast@0.23.6: + resolution: {integrity: sha512-9FHoNjX1yjuesMwuthAmPKabxYQdOgihFYmT5ebXfYGBcnqXZf3WOVz+5foEZ8Y83P4ZY6yQD5GMmtV+pgCCAQ==} engines: {node: '>= 4'} dependencies: - assert: 2.1.0 ast-types: 0.16.1 esprima: 4.0.1 source-map: 0.6.1 + tiny-invariant: 1.3.3 tslib: 2.6.2 dev: true @@ -10611,16 +10722,17 @@ packages: /regenerator-transform@0.15.2: resolution: {integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==} dependencies: - '@babel/runtime': 7.23.7 + '@babel/runtime': 7.24.4 dev: true - /regexp.prototype.flags@1.5.1: - resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==} + /regexp.prototype.flags@1.5.2: + resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 - set-function-name: 2.0.1 + es-errors: 1.3.0 + set-function-name: 2.0.2 dev: true /regexpu-core@5.3.2: @@ -10732,8 +10844,8 @@ packages: engines: {iojs: '>=1.0.0', node: '>=0.10.0'} dev: true - /rfdc@1.3.0: - resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==} + /rfdc@1.3.1: + resolution: {integrity: sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==} dev: true /rimraf@2.6.3: @@ -10765,33 +10877,36 @@ packages: fsevents: 2.3.3 dev: true - /rollup@4.9.4: - resolution: {integrity: sha512-2ztU7pY/lrQyXSCnnoU4ICjT/tCG9cdH3/G25ERqE3Lst6vl2BCM5hL2Nw+sslAvAf+ccKsAq1SkKQALyqhR7g==} + /rollup@4.17.1: + resolution: {integrity: sha512-0gG94inrUtg25sB2V/pApwiv1lUb0bQ25FPNuzO89Baa+B+c0ccaaBKM5zkZV/12pUUdH+lWCSm9wmHqyocuVQ==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true dependencies: '@types/estree': 1.0.5 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.9.4 - '@rollup/rollup-android-arm64': 4.9.4 - '@rollup/rollup-darwin-arm64': 4.9.4 - '@rollup/rollup-darwin-x64': 4.9.4 - '@rollup/rollup-linux-arm-gnueabihf': 4.9.4 - '@rollup/rollup-linux-arm64-gnu': 4.9.4 - '@rollup/rollup-linux-arm64-musl': 4.9.4 - '@rollup/rollup-linux-riscv64-gnu': 4.9.4 - '@rollup/rollup-linux-x64-gnu': 4.9.4 - '@rollup/rollup-linux-x64-musl': 4.9.4 - '@rollup/rollup-win32-arm64-msvc': 4.9.4 - '@rollup/rollup-win32-ia32-msvc': 4.9.4 - '@rollup/rollup-win32-x64-msvc': 4.9.4 + '@rollup/rollup-android-arm-eabi': 4.17.1 + '@rollup/rollup-android-arm64': 4.17.1 + '@rollup/rollup-darwin-arm64': 4.17.1 + '@rollup/rollup-darwin-x64': 4.17.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.17.1 + '@rollup/rollup-linux-arm-musleabihf': 4.17.1 + '@rollup/rollup-linux-arm64-gnu': 4.17.1 + '@rollup/rollup-linux-arm64-musl': 4.17.1 + '@rollup/rollup-linux-powerpc64le-gnu': 4.17.1 + '@rollup/rollup-linux-riscv64-gnu': 4.17.1 + '@rollup/rollup-linux-s390x-gnu': 4.17.1 + '@rollup/rollup-linux-x64-gnu': 4.17.1 + '@rollup/rollup-linux-x64-musl': 4.17.1 + '@rollup/rollup-win32-arm64-msvc': 4.17.1 + '@rollup/rollup-win32-ia32-msvc': 4.17.1 + '@rollup/rollup-win32-x64-msvc': 4.17.1 fsevents: 2.3.3 dev: true /rtl-css-js@1.16.1: resolution: {integrity: sha512-lRQgou1mu19e+Ya0LsTvKrVJ5TYUbqCVPAiImX3UfLTenarvPUl1QFdvu5Z3PYmHT9RCcwIfbjRQBntExyj3Zg==} dependencies: - '@babel/runtime': 7.23.7 + '@babel/runtime': 7.24.4 dev: false /run-parallel@1.2.0: @@ -10800,12 +10915,12 @@ packages: queue-microtask: 1.2.3 dev: true - /safe-array-concat@1.0.1: - resolution: {integrity: sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==} + /safe-array-concat@1.1.2: + resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} engines: {node: '>=0.4'} dependencies: - call-bind: 1.0.5 - get-intrinsic: 1.2.2 + call-bind: 1.0.7 + get-intrinsic: 1.2.4 has-symbols: 1.0.3 isarray: 2.0.5 dev: true @@ -10818,11 +10933,12 @@ packages: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} dev: true - /safe-regex-test@1.0.0: - resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} + /safe-regex-test@1.0.3: + resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} + engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 - get-intrinsic: 1.2.2 + call-bind: 1.0.7 + es-errors: 1.3.0 is-regex: 1.1.4 dev: true @@ -10830,22 +10946,22 @@ packages: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} dev: true - /sass@1.74.1: - resolution: {integrity: sha512-w0Z9p/rWZWelb88ISOLyvqTWGmtmu2QJICqDBGyNnfG4OUnPX9BBjjYIXUpXCMOOg5MQWNpqzt876la1fsTvUA==} + /sass@1.75.0: + resolution: {integrity: sha512-ShMYi3WkrDWxExyxSZPst4/okE9ts46xZmJDSawJQrnte7M1V9fScVB+uNXOVKRBt0PggHOwoZcn8mYX4trnBw==} engines: {node: '>=14.0.0'} hasBin: true dependencies: - chokidar: 3.5.3 + chokidar: 3.6.0 immutable: 4.3.5 - source-map-js: 1.0.2 + source-map-js: 1.2.0 dev: true /sax@1.3.0: resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==} dev: true - /scheduler@0.23.0: - resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} + /scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} dependencies: loose-envify: 1.4.0 @@ -10873,8 +10989,8 @@ packages: hasBin: true dev: true - /semver@7.5.4: - resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} + /semver@7.6.0: + resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==} engines: {node: '>=10'} hasBin: true dependencies: @@ -10902,8 +11018,8 @@ packages: - supports-color dev: true - /serialize-javascript@6.0.1: - resolution: {integrity: sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==} + /serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} dependencies: randombytes: 2.1.0 dev: true @@ -10920,23 +11036,26 @@ packages: - supports-color dev: true - /set-function-length@1.1.1: - resolution: {integrity: sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==} + /set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} dependencies: - define-data-property: 1.1.1 - get-intrinsic: 1.2.2 + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 gopd: 1.0.1 - has-property-descriptors: 1.0.1 + has-property-descriptors: 1.0.2 dev: true - /set-function-name@2.0.1: - resolution: {integrity: sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==} + /set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} engines: {node: '>= 0.4'} dependencies: - define-data-property: 1.1.1 + define-data-property: 1.1.4 + es-errors: 1.3.0 functions-have-names: 1.2.3 - has-property-descriptors: 1.0.1 + has-property-descriptors: 1.0.2 dev: true /set-harmonic-interval@1.0.1: @@ -10970,11 +11089,13 @@ packages: engines: {node: '>=8'} dev: true - /side-channel@1.0.4: - resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + /side-channel@1.0.6: + resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} + engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 - get-intrinsic: 1.2.2 + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 object-inspect: 1.13.1 dev: true @@ -10987,13 +11108,6 @@ packages: engines: {node: '>=14'} dev: true - /simple-update-notifier@2.0.0: - resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} - engines: {node: '>=10'} - dependencies: - semver: 7.5.4 - dev: true - /sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} dev: true @@ -11044,11 +11158,6 @@ packages: - supports-color dev: true - /source-map-js@1.0.2: - resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} - engines: {node: '>=0.10.0'} - dev: true - /source-map-js@1.2.0: resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} engines: {node: '>=0.10.0'} @@ -11102,22 +11211,22 @@ packages: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} dependencies: spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.16 + spdx-license-ids: 3.0.17 dev: true - /spdx-exceptions@2.3.0: - resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==} + /spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} dev: true /spdx-expression-parse@3.0.1: resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} dependencies: - spdx-exceptions: 2.3.0 - spdx-license-ids: 3.0.16 + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.17 dev: true - /spdx-license-ids@3.0.16: - resolution: {integrity: sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==} + /spdx-license-ids@3.0.17: + resolution: {integrity: sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==} dev: true /split2@3.2.2: @@ -11184,7 +11293,7 @@ packages: figures: 3.2.0 find-up: 5.0.0 git-semver-tags: 4.1.1 - semver: 7.5.4 + semver: 7.6.0 stringify-package: 1.0.1 yargs: 16.2.0 dev: true @@ -11194,15 +11303,15 @@ packages: engines: {node: '>= 0.8'} dev: true - /store2@2.14.2: - resolution: {integrity: sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w==} + /store2@2.14.3: + resolution: {integrity: sha512-4QcZ+yx7nzEFiV4BMLnr/pRa5HYzNITX2ri0Zh6sT9EyQHbBHacC6YigllUPU9X3D0f/22QCgfokpKs52YRrUg==} dev: true - /storybook@7.6.7: - resolution: {integrity: sha512-1Cd895dqYIT5MOUOCDlD73OTWoJubLq/sWC7AMzkMrLu76yD4Cu6f+wv1HDrRAheRaCaeT3yhYEhsMB6qHIcaA==} + /storybook@7.6.18: + resolution: {integrity: sha512-AUhWAVISi+qTsfpJlVuo65VfhqWtapkqJDXA/bK+4actBR9DpRXXwow6xJQJH5wrp8TZk0X9Pkqm3fykTQ5MCA==} hasBin: true dependencies: - '@storybook/cli': 7.6.7 + '@storybook/cli': 7.6.18 transitivePeerDependencies: - bufferutil - encoding @@ -11210,8 +11319,8 @@ packages: - utf-8-validate dev: true - /stream-shift@1.0.1: - resolution: {integrity: sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==} + /stream-shift@1.0.3: + resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==} dev: true /string-argv@0.3.2: @@ -11244,29 +11353,31 @@ packages: strip-ansi: 7.1.0 dev: true - /string.prototype.trim@1.2.8: - resolution: {integrity: sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==} + /string.prototype.trim@1.2.9: + resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 dev: true - /string.prototype.trimend@1.0.7: - resolution: {integrity: sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==} + /string.prototype.trimend@1.0.8: + resolution: {integrity: sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 + es-object-atoms: 1.0.0 dev: true - /string.prototype.trimstart@1.0.7: - resolution: {integrity: sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==} + /string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 + es-object-atoms: 1.0.0 dev: true /string_decoder@1.1.1: @@ -11338,8 +11449,8 @@ packages: engines: {node: '>=8'} dev: true - /styled-components@6.1.6(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-DgTLULSC29xpabJ24bbn1+hulU6vvGFQf4RPwBOJrm8WJFnN42yXpo5voBt3jDSJBa5tBd1L6PqswJjQ0wRKdg==} + /styled-components@6.1.8(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-PQ6Dn+QxlWyEGCKDS71NGsXoVLKfE1c3vApkvDYS5KAK+V8fNWGhbSUEo9Gg2iaID2tjLXegEW3bZDUGpofRWw==} engines: {node: '>= 16'} peerDependencies: react: '>= 16.8.0' @@ -11351,8 +11462,8 @@ packages: css-to-react-native: 3.2.0 csstype: 3.1.2 postcss: 8.4.31 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) shallowequal: 1.1.0 stylis: 4.3.1 tslib: 2.5.0 @@ -11360,6 +11471,11 @@ packages: /stylis@4.3.1: resolution: {integrity: sha512-EQepAV+wMsIaGVGX1RECzgrcqRRU/0sYOHkeLsZ3fzHaHXZy4DaOOX0vOlGQdlsjkh3mFHAIlVimpwAs4dslyQ==} + dev: true + + /stylis@4.3.2: + resolution: {integrity: sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==} + dev: false /stylus@0.62.0: resolution: {integrity: sha512-v3YCf31atbwJQIMtPNX8hcQ+okD4NQaTuKGUWfII8eaqn+3otrbttGL1zSMZAAtiPsBztQnujVBugg/cXFUpyg==} @@ -11374,14 +11490,14 @@ packages: - supports-color dev: true - /substyle@9.4.1(react@18.2.0): + /substyle@9.4.1(react@18.3.1): resolution: {integrity: sha512-VOngeq/W1/UkxiGzeqVvDbGDPM8XgUyJVWjrqeh+GgKqspEPiLYndK+XRcsKUHM5Muz/++1ctJ1QCF/OqRiKWA==} peerDependencies: react: '>=16.8.3' dependencies: '@babel/runtime': 7.4.5 invariant: 2.2.4 - react: 18.2.0 + react: 18.3.1 dev: false /sucrase@3.35.0: @@ -11389,9 +11505,9 @@ packages: engines: {node: '>=16 || 14 >=14.17'} hasBin: true dependencies: - '@jridgewell/gen-mapping': 0.3.3 + '@jridgewell/gen-mapping': 0.3.5 commander: 4.1.1 - glob: 10.3.10 + glob: 10.3.12 lines-and-columns: 1.2.4 mz: 2.7.0 pirates: 4.0.6 @@ -11424,15 +11540,15 @@ packages: engines: {node: '>= 0.4'} dev: true - /svg-url-loader@7.1.1(webpack@5.89.0): + /svg-url-loader@7.1.1(webpack@5.91.0): resolution: {integrity: sha512-NlsMCePODm7FQhU9aEZyGLPx5Xe1QRI1cSEUE6vTq5LJc9l9pStagvXoEIyZ9O3r00w6G3+Wbkimb+SC3DI/Aw==} engines: {node: '>=10'} peerDependencies: webpack: ^4.0.0 || ^5.0.0 dependencies: - file-loader: 6.2.0(webpack@5.89.0) + file-loader: 6.2.0(webpack@5.91.0) loader-utils: 2.0.4 - webpack: 5.89.0(esbuild@0.19.11) + webpack: 5.91.0(esbuild@0.19.12) dev: true /synchronous-promise@2.0.17: @@ -11464,8 +11580,8 @@ packages: readable-stream: 3.6.2 dev: true - /tar@6.2.0: - resolution: {integrity: sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==} + /tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} dependencies: chownr: 2.0.0 @@ -11505,7 +11621,7 @@ packages: unique-string: 2.0.0 dev: true - /terser-webpack-plugin@5.3.10(esbuild@0.19.11)(webpack@5.89.0): + /terser-webpack-plugin@5.3.10(esbuild@0.19.12)(webpack@5.91.0): resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -11521,21 +11637,21 @@ packages: uglify-js: optional: true dependencies: - '@jridgewell/trace-mapping': 0.3.20 - esbuild: 0.19.11 + '@jridgewell/trace-mapping': 0.3.25 + esbuild: 0.19.12 jest-worker: 27.5.1 schema-utils: 3.3.0 - serialize-javascript: 6.0.1 - terser: 5.26.0 - webpack: 5.89.0(esbuild@0.19.11) + serialize-javascript: 6.0.2 + terser: 5.30.4 + webpack: 5.91.0(esbuild@0.19.12) dev: true - /terser@5.26.0: - resolution: {integrity: sha512-dytTGoE2oHgbNV9nTzgBEPaqAWvcJNl66VZ0BkJqlvp71IjO8CxdBx/ykCNb47cLnCmCvRZ6ZR0tLkqvZCdVBQ==} + /terser@5.30.4: + resolution: {integrity: sha512-xRdd0v64a8mFK9bnsKVdoNP9GQIKUAaJPTaqEQDL4w/J8WaW4sWXXoMZ+6SimPkfT5bElreXf8m9HnmPc3E1BQ==} engines: {node: '>=10'} hasBin: true dependencies: - '@jridgewell/source-map': 0.3.5 + '@jridgewell/source-map': 0.3.6 acorn: 8.11.3 commander: 2.20.3 source-map-support: 0.5.21 @@ -11599,8 +11715,8 @@ packages: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} dev: true - /tiny-invariant@1.3.1: - resolution: {integrity: sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==} + /tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} dev: true /tmpl@1.0.5: @@ -11623,8 +11739,8 @@ packages: is-number: 7.0.0 dev: true - /tocbot@4.25.0: - resolution: {integrity: sha512-kE5wyCQJ40hqUaRVkyQ4z5+4juzYsv/eK+aqD97N62YH0TxFhzJvo22RUQQZdO3YnXAk42ZOfOpjVdy+Z0YokA==} + /tocbot@4.27.13: + resolution: {integrity: sha512-zS8GVVg14x/KBTxbvF6s3BNLltfMNZxTPaBpj+FjuwmnSv+ZK0trNN4uV5Ptw64NLFi2E30gt33+/a1Fkt3cWQ==} dev: true /toggle-selection@1.0.6: @@ -11656,9 +11772,9 @@ packages: engines: {node: '>=8'} dev: true - /ts-api-utils@1.0.3(typescript@4.9.5): - resolution: {integrity: sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==} - engines: {node: '>=16.13.0'} + /ts-api-utils@1.3.0(typescript@4.9.5): + resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} + engines: {node: '>=16'} peerDependencies: typescript: '>=4.2.0' dependencies: @@ -11678,9 +11794,9 @@ packages: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} dev: true - /ts-jest@29.1.1(@babel/core@7.23.7)(esbuild@0.19.11)(jest@29.7.0)(typescript@4.9.5): - resolution: {integrity: sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + /ts-jest@29.1.2(@babel/core@7.24.4)(esbuild@0.19.12)(jest@29.7.0)(typescript@4.9.5): + resolution: {integrity: sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==} + engines: {node: ^16.10.0 || ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@babel/core': '>=7.0.0-beta.0 <8' @@ -11699,26 +11815,26 @@ packages: esbuild: optional: true dependencies: - '@babel/core': 7.23.7 + '@babel/core': 7.24.4 bs-logger: 0.2.6 - esbuild: 0.19.11 + esbuild: 0.19.12 fast-json-stable-stringify: 2.1.0 jest: 29.7.0 jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 - semver: 7.5.4 + semver: 7.6.0 typescript: 4.9.5 yargs-parser: 21.1.1 dev: true - /tsconfck@2.1.2(typescript@4.9.5): - resolution: {integrity: sha512-ghqN1b0puy3MhhviwO2kGF8SeMDNhEbnKxjK7h6+fvY9JAxqvXi8y5NAHSQv687OVboS2uZIByzGd45/YxrRHg==} - engines: {node: ^14.13.1 || ^16 || >=18} + /tsconfck@3.0.3(typescript@4.9.5): + resolution: {integrity: sha512-4t0noZX9t6GcPTfBAbIbbIU4pfpCwh0ueq3S4O/5qXI1VwK1outmxhe9dOiEWqMz3MW2LKgDTpqWV+37IWuVbA==} + engines: {node: ^18 || >=20} hasBin: true peerDependencies: - typescript: ^4.3.5 || ^5.0.0 + typescript: ^5.0.0 peerDependenciesMeta: typescript: optional: true @@ -11755,7 +11871,7 @@ packages: /tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - /tsup@7.3.0(postcss@8.4.33)(typescript@4.9.5): + /tsup@7.3.0(postcss@8.4.38)(typescript@4.9.5): resolution: {integrity: sha512-Ja1eaSRrE+QarmATlNO5fse2aOACYMBX+IZRKy1T+gpyH+jXgRrl5l4nHIQJQ1DoDgEjHDTw8cpE085UdBZuWQ==} engines: {node: '>=18'} deprecated: Breaking node 16 @@ -11772,18 +11888,18 @@ packages: typescript: optional: true dependencies: - bundle-require: 4.0.2(esbuild@0.19.11) + bundle-require: 4.0.3(esbuild@0.19.12) cac: 6.7.14 - chokidar: 3.5.3 + chokidar: 3.6.0 debug: 4.3.4 - esbuild: 0.19.11 + esbuild: 0.19.12 execa: 5.1.1 globby: 11.1.0 joycon: 3.1.1 - postcss: 8.4.33 - postcss-load-config: 4.0.2(postcss@8.4.33) + postcss: 8.4.38 + postcss-load-config: 4.0.2(postcss@8.4.38) resolve-from: 5.0.0 - rollup: 4.9.4 + rollup: 4.17.1 source-map: 0.8.0-beta.0 sucrase: 3.35.0 tree-kill: 1.2.2 @@ -11863,42 +11979,48 @@ packages: mime-types: 2.1.35 dev: true - /typed-array-buffer@1.0.0: - resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==} + /typed-array-buffer@1.0.2: + resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 - get-intrinsic: 1.2.2 - is-typed-array: 1.1.12 + call-bind: 1.0.7 + es-errors: 1.3.0 + is-typed-array: 1.1.13 dev: true - /typed-array-byte-length@1.0.0: - resolution: {integrity: sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==} + /typed-array-byte-length@1.0.1: + resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 for-each: 0.3.3 - has-proto: 1.0.1 - is-typed-array: 1.1.12 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 dev: true - /typed-array-byte-offset@1.0.0: - resolution: {integrity: sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==} + /typed-array-byte-offset@1.0.2: + resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==} engines: {node: '>= 0.4'} dependencies: - available-typed-arrays: 1.0.5 - call-bind: 1.0.5 + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 for-each: 0.3.3 - has-proto: 1.0.1 - is-typed-array: 1.1.12 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 dev: true - /typed-array-length@1.0.4: - resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} + /typed-array-length@1.0.6: + resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} + engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 for-each: 0.3.3 - is-typed-array: 1.1.12 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + possible-typed-array-names: 1.0.0 dev: true /typedarray@0.0.6: @@ -11922,8 +12044,8 @@ packages: postcss-modules-local-by-default: 4.0.5(postcss@8.4.38) postcss-modules-scope: 3.2.0(postcss@8.4.38) reserved-words: 0.1.2 - sass: 1.74.1 - source-map-js: 1.0.2 + sass: 1.75.0 + source-map-js: 1.2.0 stylus: 0.62.0 tsconfig-paths: 4.2.0 typescript: 4.9.5 @@ -11937,8 +12059,8 @@ packages: engines: {node: '>=4.2.0'} hasBin: true - /ufo@1.3.2: - resolution: {integrity: sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==} + /ufo@1.5.3: + resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==} dev: true /uglify-js@3.17.4: @@ -11952,7 +12074,7 @@ packages: /unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 has-bigints: 1.0.2 has-symbols: 1.0.3 which-boxed-primitive: 1.0.2 @@ -12021,11 +12143,12 @@ packages: engines: {node: '>= 0.8'} dev: true - /unplugin@1.6.0: - resolution: {integrity: sha512-BfJEpWBu3aE/AyHx8VaNE/WgouoQxgH9baAiH82JjX8cqVyi3uJQstqwD5J+SZxIK326SZIhsSZlALXVBCknTQ==} + /unplugin@1.10.1: + resolution: {integrity: sha512-d6Mhq8RJeGA8UfKCu54Um4lFA0eSaRa3XxdAJg8tIdxbu1ubW0hBCZUL7yI2uGyYCRndvbK8FLHzqy2XKfeMsg==} + engines: {node: '>=14.0.0'} dependencies: acorn: 8.11.3 - chokidar: 3.5.3 + chokidar: 3.6.0 webpack-sources: 3.2.3 webpack-virtual-modules: 0.6.1 dev: true @@ -12035,14 +12158,14 @@ packages: engines: {node: '>=8'} dev: true - /update-browserslist-db@1.0.13(browserslist@4.22.2): + /update-browserslist-db@1.0.13(browserslist@4.23.0): resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' dependencies: - browserslist: 4.22.2 - escalade: 3.1.1 + browserslist: 4.23.0 + escalade: 3.1.2 picocolors: 1.0.0 dev: true @@ -12052,8 +12175,8 @@ packages: punycode: 2.3.1 dev: true - /use-callback-ref@1.3.1(@types/react@17.0.74)(react@18.2.0): - resolution: {integrity: sha512-Lg4Vx1XZQauB42Hw3kK7JM6yjVjgFmFC5/Ab797s79aARomD2nEErc4mCgM8EZrARLmmbWpi5DGCadmK50DcAQ==} + /use-callback-ref@1.3.2(@types/react@17.0.80)(react@18.3.1): + resolution: {integrity: sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==} engines: {node: '>=10'} peerDependencies: '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -12062,20 +12185,20 @@ packages: '@types/react': optional: true dependencies: - '@types/react': 17.0.74 - react: 18.2.0 + '@types/react': 17.0.80 + react: 18.3.1 tslib: 2.6.2 dev: true - /use-composed-ref@1.3.0(react@18.2.0): + /use-composed-ref@1.3.0(react@18.3.1): resolution: {integrity: sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - react: 18.2.0 + react: 18.3.1 dev: false - /use-isomorphic-layout-effect@1.1.2(@types/react@17.0.74)(react@18.2.0): + /use-isomorphic-layout-effect@1.1.2(@types/react@17.0.80)(react@18.3.1): resolution: {integrity: sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==} peerDependencies: '@types/react': '*' @@ -12084,11 +12207,11 @@ packages: '@types/react': optional: true dependencies: - '@types/react': 17.0.74 - react: 18.2.0 + '@types/react': 17.0.80 + react: 18.3.1 dev: false - /use-latest@1.2.1(@types/react@17.0.74)(react@18.2.0): + /use-latest@1.2.1(@types/react@17.0.80)(react@18.3.1): resolution: {integrity: sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw==} peerDependencies: '@types/react': '*' @@ -12097,23 +12220,23 @@ packages: '@types/react': optional: true dependencies: - '@types/react': 17.0.74 - react: 18.2.0 - use-isomorphic-layout-effect: 1.1.2(@types/react@17.0.74)(react@18.2.0) + '@types/react': 17.0.80 + react: 18.3.1 + use-isomorphic-layout-effect: 1.1.2(@types/react@17.0.80)(react@18.3.1) dev: false - /use-resize-observer@9.1.0(react-dom@18.2.0)(react@18.2.0): + /use-resize-observer@9.1.0(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-R25VqO9Wb3asSD4eqtcxk8sJalvIOYBqS8MNZlpDSQ4l4xMQxC/J7Id9HoTqPq8FwULIn0PVW+OAqF2dyYbjow==} peerDependencies: react: 16.8.0 - 18 react-dom: 16.8.0 - 18 dependencies: '@juggle/resize-observer': 3.4.0 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) dev: true - /use-sidecar@1.1.2(@types/react@17.0.74)(react@18.2.0): + /use-sidecar@1.1.2(@types/react@17.0.80)(react@18.3.1): resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} engines: {node: '>=10'} peerDependencies: @@ -12123,9 +12246,9 @@ packages: '@types/react': optional: true dependencies: - '@types/react': 17.0.74 + '@types/react': 17.0.80 detect-node-es: 1.1.0 - react: 18.2.0 + react: 18.3.1 tslib: 2.6.2 dev: true @@ -12139,8 +12262,8 @@ packages: inherits: 2.0.4 is-arguments: 1.1.1 is-generator-function: 1.0.10 - is-typed-array: 1.1.12 - which-typed-array: 1.1.13 + is-typed-array: 1.1.13 + which-typed-array: 1.1.15 dev: true /utils-merge@1.0.1: @@ -12162,7 +12285,7 @@ packages: resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==} engines: {node: '>=10.12.0'} dependencies: - '@jridgewell/trace-mapping': 0.3.20 + '@jridgewell/trace-mapping': 0.3.25 '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 dev: true @@ -12179,8 +12302,8 @@ packages: engines: {node: '>= 0.8'} dev: true - /vite-tsconfig-paths@4.2.3(typescript@4.9.5)(vite@4.5.1): - resolution: {integrity: sha512-xVsA2xe6QSlzBujtWF8q2NYexh7PAUYfzJ4C8Axpe/7d2pcERYxuxGgph9F4f0iQO36g5tyGq6eBUYIssdUrVw==} + /vite-tsconfig-paths@4.3.2(typescript@4.9.5)(vite@4.5.3): + resolution: {integrity: sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==} peerDependencies: vite: '*' peerDependenciesMeta: @@ -12189,15 +12312,15 @@ packages: dependencies: debug: 4.3.4 globrex: 0.1.2 - tsconfck: 2.1.2(typescript@4.9.5) - vite: 4.5.1 + tsconfck: 3.0.3(typescript@4.9.5) + vite: 4.5.3 transitivePeerDependencies: - supports-color - typescript dev: true - /vite@4.5.1: - resolution: {integrity: sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA==} + /vite@4.5.3: + resolution: {integrity: sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true peerDependencies: @@ -12225,7 +12348,7 @@ packages: optional: true dependencies: esbuild: 0.18.20 - postcss: 8.4.33 + postcss: 8.4.38 rollup: 3.29.4 optionalDependencies: fsevents: 2.3.3 @@ -12237,8 +12360,8 @@ packages: makeerror: 1.0.12 dev: true - /watchpack@2.4.0: - resolution: {integrity: sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==} + /watchpack@2.4.1: + resolution: {integrity: sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==} engines: {node: '>=10.13.0'} dependencies: glob-to-regexp: 0.4.1 @@ -12268,8 +12391,8 @@ packages: resolution: {integrity: sha512-poXpCylU7ExuvZK8z+On3kX+S8o/2dQ/SVYueKA0D4WEMXROXgY8Ez50/bQEUmvoSMMrWcrJqCHuhAbsiwg7Dg==} dev: true - /webpack@5.89.0(esbuild@0.19.11): - resolution: {integrity: sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==} + /webpack@5.91.0(esbuild@0.19.12): + resolution: {integrity: sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==} engines: {node: '>=10.13.0'} hasBin: true peerDependencies: @@ -12280,15 +12403,15 @@ packages: dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.5 - '@webassemblyjs/ast': 1.11.6 - '@webassemblyjs/wasm-edit': 1.11.6 - '@webassemblyjs/wasm-parser': 1.11.6 + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/wasm-edit': 1.12.1 + '@webassemblyjs/wasm-parser': 1.12.1 acorn: 8.11.3 acorn-import-assertions: 1.9.0(acorn@8.11.3) - browserslist: 4.22.2 + browserslist: 4.23.0 chrome-trace-event: 1.0.3 - enhanced-resolve: 5.15.0 - es-module-lexer: 1.4.1 + enhanced-resolve: 5.16.0 + es-module-lexer: 1.5.2 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 @@ -12299,8 +12422,8 @@ packages: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(esbuild@0.19.11)(webpack@5.89.0) - watchpack: 2.4.0 + terser-webpack-plugin: 5.3.10(esbuild@0.19.12)(webpack@5.91.0) + watchpack: 2.4.1 webpack-sources: 3.2.3 transitivePeerDependencies: - '@swc/core' @@ -12333,15 +12456,15 @@ packages: is-symbol: 1.0.4 dev: true - /which-typed-array@1.1.13: - resolution: {integrity: sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==} + /which-typed-array@1.1.15: + resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} engines: {node: '>= 0.4'} dependencies: - available-typed-arrays: 1.0.5 - call-bind: 1.0.5 + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 for-each: 0.3.3 gopd: 1.0.1 - has-tostringtag: 1.0.0 + has-tostringtag: 1.0.2 dev: true /which@2.0.2: @@ -12352,6 +12475,11 @@ packages: isexe: 2.0.0 dev: true + /word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + dev: true + /wordwrap@1.0.0: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} dev: true @@ -12434,8 +12562,8 @@ packages: optional: true dev: true - /ws@8.16.0: - resolution: {integrity: sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==} + /ws@8.17.0: + resolution: {integrity: sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -12479,9 +12607,10 @@ packages: engines: {node: '>= 14'} dev: true - /yaml@2.3.4: - resolution: {integrity: sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==} + /yaml@2.4.2: + resolution: {integrity: sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==} engines: {node: '>= 14'} + hasBin: true dev: true /yargs-parser@20.2.9: @@ -12498,7 +12627,7 @@ packages: engines: {node: '>=10'} dependencies: cliui: 7.0.4 - escalade: 3.1.1 + escalade: 3.1.2 get-caller-file: 2.0.5 require-directory: 2.1.1 string-width: 4.2.3 @@ -12511,7 +12640,7 @@ packages: engines: {node: '>=12'} dependencies: cliui: 8.0.1 - escalade: 3.1.1 + escalade: 3.1.2 get-caller-file: 2.0.5 require-directory: 2.1.1 string-width: 4.2.3 @@ -12534,6 +12663,6 @@ packages: engines: {node: '>=10'} dev: true - /zod@3.22.4: - resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} + /zod@3.23.4: + resolution: {integrity: sha512-/AtWOKbBgjzEYYQRNfoGKHObgfAZag6qUJX1VbHo2PRBgS+wfWagEY2mizjfyAPcGesrJOcx/wcl0L9WnVrHFw==} dev: false diff --git a/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ChatReadyState.tsx b/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ChatReadyState.tsx index 012c6b0ba..84eef0373 100644 --- a/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ChatReadyState.tsx +++ b/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ChatReadyState.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useRef, useState } from 'react'; -import { AmityLiveChatMessageList } from 'v4/chat/components/AmityLiveChatMessageList'; -import AmityLiveChatMessageComposeBar from 'v4/chat/components/AmityLiveChatMessageComposeBar'; +import { AmityLiveChatMessageList } from '~/v4/chat/components/AmityLiveChatMessageList'; +import AmityLiveChatMessageComposeBar from '~/v4/chat/components/AmityLiveChatMessageComposeBar'; import ReplyMessagePlaceholder from '~/v4/chat/pages/AmityLiveChatPage/ChatContainer/ReplyMessagePlaceholder'; import useConnectionStates from '~/social/hooks/useConnectionStates'; import ChatLoadingState from '~/v4/chat/pages/AmityLiveChatPage/ChatContainer/ChatLoadingState'; From 61368cc720ecbb34705cbe54fac7d079e5aff22b Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Tue, 30 Apr 2024 11:08:13 +0700 Subject: [PATCH 061/300] feat: only creator or story permission can see impression (#319) --- .../StoryViewer/Renderers/Image.tsx | 6 +++- .../StoryViewer/Renderers/Video.tsx | 4 +++ .../Renderers/Wrappers/Footer/index.tsx | 31 ++++++++++++------- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index c7af7aa34..a56ec0c8a 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -27,6 +27,7 @@ import { HyperLink } from '~/v4/social/elements/HyperLink'; import Footer from './Wrappers/Footer'; import Header from './Wrappers/Header'; import { PageTypes } from '~/social/constants'; +import useUser from '~/core/hooks/useUser'; export const renderer: CustomRenderer = ({ story, action, config }) => { const { formatMessage } = useIntl(); @@ -62,6 +63,8 @@ export const renderer: CustomRenderer = ({ story, action, config }) => { imageSize: 'small', }); + const user = useUser(); + const heading =
{community?.displayName}
; const subheading = createdAt && creator?.displayName ? ( @@ -74,7 +77,7 @@ export const renderer: CustomRenderer = ({ story, action, config }) => { ); const isOfficial = community?.isOfficial || false; - + const isCreator = creator?.userId === user?.userId; const haveStoryPermission = checkStoryPermission(client, community?.communityId); const computedStyles = { @@ -247,6 +250,7 @@ export const renderer: CustomRenderer = ({ story, action, config }) => { totalLikes={totalLikes} isLiked={isLiked} onClickComment={openCommentSheet} + showImpression={isCreator || haveStoryPermission} />
); diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx index 0a05a9881..5e9f447f6 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx @@ -26,6 +26,7 @@ import { CommentTray } from '~/v4/social/components'; import { HyperLink } from '~/v4/social/elements/HyperLink'; import Footer from './Wrappers/Footer'; import { PageTypes } from '~/social/constants'; +import useUser from '~/core/hooks/useUser'; export const renderer: CustomRenderer = ({ story, action, config, messageHandler }) => { const { formatMessage } = useIntl(); @@ -37,6 +38,7 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler const [isOpenCommentSheet, setIsOpenCommentSheet] = useState(false); const { width, height, loader, storyStyles } = config; const { client } = useSDK(); + const user = useUser(client.currentUserId); const isLiked = !!(story && story.myReactions && story.myReactions.includes(LIKE_REACTION_KEY)); const totalLikes = story?.reactions[LIKE_REACTION_KEY] || 0; @@ -76,6 +78,7 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler ); const haveStoryPermission = checkStoryPermission(client, community?.communityId); + const isCreator = creator?.userId === user?.userId; const computedStyles = { ...styles.storyContent, @@ -294,6 +297,7 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler totalLikes={totalLikes} isLiked={isLiked} onClickComment={openCommentSheet} + showImpression={isCreator || haveStoryPermission} /> ); diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx index a1e0c254a..8e0cbe87b 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx @@ -12,6 +12,7 @@ import { CommentButton, ImpressionButton, ReactButton } from '~/v4/social/elemen const Footer: React.FC< React.PropsWithChildren<{ storyId: string; + showImpression: boolean; reach: number | null; commentsCount: number; totalLikes: number; @@ -19,9 +20,16 @@ const Footer: React.FC< onClickComment: () => void; syncState?: Amity.SyncState; }> -> = ({ syncState, reach, commentsCount, totalLikes, isLiked, storyId, onClickComment }) => { - const [isActive, setIsActive] = useState(isLiked); - const [likeCount, setLikeCount] = useState(totalLikes); +> = ({ + syncState, + reach, + commentsCount, + totalLikes, + isLiked, + storyId, + onClickComment, + showImpression, +}) => { const { formatMessage } = useIntl(); const handleLike = async () => { @@ -36,11 +44,6 @@ const Footer: React.FC< } }; - useEffect(() => { - setIsActive(isLiked); - setLikeCount(totalLikes); - }, [isLiked, totalLikes]); - if (syncState === 'syncing') { return (
@@ -66,16 +69,20 @@ const Footer: React.FC< return (
-
- - {millify(reach || 0)} +
+ {showImpression && ( +
+ + {millify(reach || 0)} +
+ )}
{millify(commentsCount) || 0} - {millify(likeCount || 0)} + {millify(totalLikes || 0)}
From 3dc78939b118213149012fc7a68e2f0cf56dc3a5 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Tue, 30 Apr 2024 11:20:29 +0700 Subject: [PATCH 062/300] feat: ASC-22084 - StoryPreview component for Console (#317) * feat: story preview component for console * fix: ui * fix: story preview prop * fix: export * fix: story prop * fix: story prop * fix: trigger action * fix: trigger action * fix: trigger action * fix: trigger action * fix: action --- .github/workflows/staging.yaml | 10 +- src/index.ts | 3 + .../StoryPreview/StoryPreview.module.css | 81 +++++++++ .../StoryPreview/StoryPreview.tsx | 160 ++++++++++++++++++ .../internal-components/StoryPreview/index.ts | 1 + .../StoryPreview/ui.stories.tsx | 47 +++++ .../pages/DraftsPage/DraftsPage.module.css | 131 +++++++++++++- 7 files changed, 423 insertions(+), 10 deletions(-) create mode 100644 src/v4/social/internal-components/StoryPreview/StoryPreview.module.css create mode 100644 src/v4/social/internal-components/StoryPreview/StoryPreview.tsx create mode 100644 src/v4/social/internal-components/StoryPreview/index.ts create mode 100644 src/v4/social/internal-components/StoryPreview/ui.stories.tsx diff --git a/.github/workflows/staging.yaml b/.github/workflows/staging.yaml index f719bc5e2..9d91d85c6 100644 --- a/.github/workflows/staging.yaml +++ b/.github/workflows/staging.yaml @@ -34,12 +34,7 @@ jobs: - uses: pnpm/action-setup@v2 with: version: 8 - run_install: false - - - name: Get pnpm store directory - id: pnpm-cache - run: | - echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT + run_install: true - uses: actions/cache@v3 name: Setup pnpm cache @@ -49,9 +44,6 @@ jobs: restore-keys: | ${{ runner.os }}-pnpm-store- - - name: Install dependencies - run: pnpm install - - name: build storybook run: pnpm run storybook:build diff --git a/src/index.ts b/src/index.ts index 0c66ebd1d..5927767c4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -40,6 +40,9 @@ export type { AmityMessageActionType }; export { AmityLiveChatPage } from '~/v4/chat/pages'; +// v4 internal use only (Amity Console) +export { StoryPreview as AmityStoryPreview } from './v4/social/internal-components/StoryPreview'; + // import AmityComment from './components/Comment'; // import AmityCommentComposeBar from './components/CommentComposeBar'; // import AmityCommentLikeButton from './components/CommentLikeButton'; diff --git a/src/v4/social/internal-components/StoryPreview/StoryPreview.module.css b/src/v4/social/internal-components/StoryPreview/StoryPreview.module.css new file mode 100644 index 000000000..8baf236c1 --- /dev/null +++ b/src/v4/social/internal-components/StoryPreview/StoryPreview.module.css @@ -0,0 +1,81 @@ +.storyPreview_title { + color: var(--asc-color-white); +} + +.storyPreview_description { + color: var(--asc-color-white); +} + +.storyPreviewContainer { + width: 100%; + height: 100%; + position: relative; + } + + .headerContainer { + position: absolute; + top: 10px; + left: 10px; + right: 10px; + z-index: 1; + display: flex; + align-items: center; + padding: 0.75rem 0 0.625rem 0; + } + + .progressBar { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 0.125rem; + border-radius: 0.25rem; + background-color: rgba(255, 255, 255, 0.3); + } + + .progressFill { + height: 100%; + background-color: white; + transition: width 0.1s linear; + } + + .userInfo { + width: 100%; + display: flex; + justify-content: flex-start; + align-items: center; + gap: 0.5rem; + } + + .storyPreviewTitle { + color: white; + } + + .nameContainer { + display: flex; + align-items: center; + gap: 0.25rem; + } + + .hyperLinkContainer { + position: absolute; + bottom: 0; + z-index: 1; + display: flex; + justify-content: center; + align-items: center; + padding: 0.75rem 1rem 0.625rem 1rem; + left: 50%; + transform: translateX(-50%); + } + + .mediaContainer { + width: 100%; + height: 100%; + } + + .media { + width: 100%; + height: 100%; + object-fit: cover; + } \ No newline at end of file diff --git a/src/v4/social/internal-components/StoryPreview/StoryPreview.tsx b/src/v4/social/internal-components/StoryPreview/StoryPreview.tsx new file mode 100644 index 000000000..bb077752e --- /dev/null +++ b/src/v4/social/internal-components/StoryPreview/StoryPreview.tsx @@ -0,0 +1,160 @@ +import React, { useEffect, useRef, useState } from 'react'; +import UserAvatar from '~/v4/chat/components/UserAvatar'; +import { backgroundImage as communityBackgroundImage } from '~/icons/Community'; +import Verified from '~/v4/social/icons/verified'; +import { Typography } from '~/v4/core/components'; +import { HyperLink } from '~/v4/social/elements/HyperLink'; +import TruncateMarkup from 'react-truncate-markup'; +import styles from './StoryPreview.module.css'; + +type AmityStoryMediaType = { type: 'image'; url: string } | { type: 'video'; url: string }; + +type StoryPreviewProps = { + mediaType?: AmityStoryMediaType; + file?: File | null; + imageMode: 'fit' | 'fill'; + + hyperLink: { + data: { url: string; customText: string }; + type: Amity.StoryItemType; + }[]; + onPlay?: () => void; + onPause?: () => void; + onComplete?: () => void; + avatar: string; + title: string; + description: string; + duration?: number; + isOfficial?: boolean; +}; + +export const StoryPreview: React.FC = ({ + mediaType, + file, + hyperLink, + onPlay, + onPause, + onComplete, + avatar, + title, + duration = 5000, + isOfficial = true, +}) => { + const [isPlaying, setIsPlaying] = useState(true); + const [progress, setProgress] = useState(0); + const videoRef = useRef(null); + const imageRef = useRef(null); + + let progressInterval: NodeJS.Timeout; + + useEffect(() => { + if (isPlaying) { + progressInterval = setInterval(() => { + setProgress((prevProgress) => { + if (prevProgress >= 100) { + clearInterval(progressInterval); + handleMediaComplete(); + return 100; + } + return prevProgress + 1; + }); + }, duration / 100); + } else { + clearInterval(progressInterval); + } + + return () => { + clearInterval(progressInterval); + }; + }, [isPlaying, duration]); + + const handleMediaPlay = () => { + setIsPlaying(true); + if (onPlay) { + onPlay(); + } + }; + + const handleMediaPause = () => { + setIsPlaying(false); + if (onPause) { + onPause(); + } + }; + + const handleMediaComplete = () => { + if (mediaType?.type === 'video' && videoRef.current) { + videoRef.current.currentTime = 0; + videoRef.current.play(); + } else if (mediaType?.type === 'image') { + setIsPlaying(false); + if (onComplete) { + onComplete(); + } + } + }; + + const handlePlayPauseClick = () => { + if (isPlaying) { + handleMediaPause(); + } else { + handleMediaPlay(); + } + }; + + return ( +
+
+
+
+
+
+ + + + {title} {isOfficial && } + + +
+
+ +
+ {hyperLink[0]?.data?.url && ( + + + {hyperLink[0]?.data?.customText || hyperLink[0].data.url} + + + )} +
+ +
+ {mediaType?.type === 'video' ? ( +
+
+ ); +}; diff --git a/src/v4/social/internal-components/StoryPreview/index.ts b/src/v4/social/internal-components/StoryPreview/index.ts new file mode 100644 index 000000000..f1a01cb5e --- /dev/null +++ b/src/v4/social/internal-components/StoryPreview/index.ts @@ -0,0 +1 @@ +export { StoryPreview } from './StoryPreview'; diff --git a/src/v4/social/internal-components/StoryPreview/ui.stories.tsx b/src/v4/social/internal-components/StoryPreview/ui.stories.tsx new file mode 100644 index 000000000..4d7d7527a --- /dev/null +++ b/src/v4/social/internal-components/StoryPreview/ui.stories.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { ComponentStory, ComponentMeta } from '@storybook/react'; +import { StoryPreview } from '.'; + +export default { + title: 'v4/Social/Story Preview', + component: StoryPreview, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ( +
+ +
+); + +export const ImageStory = Template.bind({}); +ImageStory.args = { + mediaType: { type: 'image', url: 'https://picsum.photos/400/600' }, + file: null, + imageMode: 'fit', + hyperLink: [], + avatar: 'https://picsum.photos/120/120', + title: 'John Doe', + description: 'This is an image story', +}; + +export const StoryWithHyperlink = Template.bind({}); +StoryWithHyperlink.args = { + mediaType: { type: 'image', url: 'https://picsum.photos/400/600' }, + file: null, + imageMode: 'fit', + hyperLink: [ + { + data: { url: 'https://example.com', customText: 'Visit Website' }, + type: 'hyperlink' as Amity.StoryItemType, + }, + ], + avatar: 'https://picsum.photos/120/120', + title: 'Sarah Johnson', + description: 'This is a story with a hyperlink', +}; diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.module.css b/src/v4/social/pages/DraftsPage/DraftsPage.module.css index 72f273564..c2783e65d 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.module.css +++ b/src/v4/social/pages/DraftsPage/DraftsPage.module.css @@ -32,7 +32,10 @@ } .mainContainer { + width: 100%; + height: 100%; flex: 1; + position: relative; display: flex; justify-content: center; align-items: center; @@ -65,7 +68,133 @@ padding: 1rem; display: flex; justify-content: center; - bottom: 6rem; + bottom: 2rem; + left: 0; + right: 0; +} + +.playPauseButton { + position: absolute; + top: 10px; + right: 10px; + background: none; + border: none; + cursor: pointer; + color: var(--color-white); + font-size: 1.5rem; +} + +.header { + position: absolute; + display: flex; + align-items: center; + padding: 10px; + background-color: var(--color-white); + top: 0; +} + +.progressBar { + position: absolute; + top: 0; left: 0; right: 0; + height: 4px; + background-color: var(--color-gray); +} + +.progressFill { + height: 100%; + background-color: var(--color-primary); + transition: width 0.1s linear; +} + +.userInfo { + display: flex; + flex-direction: column; + margin-left: 10px; +} + +.name { + font-size: 16px; + font-weight: bold; + color: var(--color-black); +} + +.description { + font-size: 14px; + color: var(--color-gray); +} + +.storyPreview { + position: relative; + width: 100%; + height: 100%; +} + +.mainContainer { + position: relative; + width: 100%; + height: 100%; +} + +.header { + position: absolute; + top: 0; + left: 0; + right: 0; + display: flex; + align-items: center; + padding: 10px; + background-color: rgba(0, 0, 0, 0.5); + color: var(--color-white); +} + +.progressBar { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 4px; + background-color: rgba(255, 255, 255, 0.3); +} + +.progressFill { + height: 100%; + background-color: var(--color-white); + transition: width 0.1s linear; +} + +.userInfo { + display: flex; + flex-direction: column; + margin-left: 10px; +} + +.name { + font-size: 16px; + font-weight: bold; +} + +.description { + font-size: 14px; +} + +.hyperLinkContainer { + position: absolute; + bottom: 20px; + left: 20px; + right: 20px; + text-align: center; +} + +.playPauseButton { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: none; + border: none; + color: var(--color-white); + font-size: 48px; + cursor: pointer; } From 3919fe7caa776f7df1a381f73e9305d800286425 Mon Sep 17 00:00:00 2001 From: Kiattirat Sujjapongse Date: Thu, 2 May 2024 16:09:21 +0700 Subject: [PATCH 063/300] update readme (#323) --- readme.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index d56d9036a..3e4513621 100644 --- a/readme.md +++ b/readme.md @@ -2,6 +2,11 @@ ## Getting started +Before starting to work, please read the following instructions. +https://ekoapp.atlassian.net/wiki/spaces/UP/pages/2443706407/ASC+Web+UIKit+V4+Governance + +If you have any questions, please ask / discuss with the team. + ### Installation 1. `npm install --save @amityco/ui-kit` @@ -9,8 +14,8 @@ ### Documentation -Please refer to our online documentation at https://docs.amity.co or contact a Ui-Kit representative at **developers@amity.co** for support. +Please refer to our online documentation at https://docs.amity.co or contact a Ui-Kit representative at \* \*developers@amity.co** for support. ## Contributing -See [our contributing guide](https://github.com/EkoCommunications/AmityUiKitWeb/blob/develop/CONTRIBUTING.md) \ No newline at end of file +See [our contributing guide](https://github.com/EkoCommunications/AmityUiKitWeb/blob/develop/CONTRIBUTING.md) From baebc187222eccbddf9a8b21371ce052f8bbed54 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Tue, 7 May 2024 15:58:48 +0700 Subject: [PATCH 064/300] feat: ASC-20558 - swipe down gesture to close story (#321) * feat: apply framer motion * feat: add transition * fix: swipe down for video * fix: video story should pause when drag * fix: remove unused libs * fix: wrong spell function name * fix: remove unused * chore: change story component name * fix: v4 avatar component * feat: add ghost prop to button * fix: remove unused * fix: bottom sheet component * fix: use rem * fix: load more wrapper * fix: draft page * fix: load more button * fix: hyperlink * fix: css * fix: icon * fix: css * fix: css * fix: comment * fix: story comment * fix: remove unused * fix: import * fix: button dark theme * fix: custom css * fix: bottom sheet css * fix: css * fix: import * fix: css * fix: css * fix: avatar * fix: remove console.log * fix: avatar * fix: use clsx * fix: whitespace * fix: use icon from v4 * fix: icon * fix: loading indicator --- package.json | 1 + pnpm-lock.yaml | 23 +- .../core/components/Avatar/Avatar.module.css | 100 ++++ src/v4/core/components/Avatar/Avatar.tsx | 67 +++ src/v4/core/components/Avatar/index.ts | 1 + .../BottomSheet/BottomSheet.module.css | 31 ++ .../components/BottomSheet/BottomSheet.tsx | 45 +- src/v4/core/components/BottomSheet/styles.tsx | 40 -- .../core/components/Button/Button.module.css | 12 +- src/v4/core/components/Button/Button.tsx | 2 +- src/v4/core/components/InputText/index.tsx | 1 - .../components/InputText/styles.module.css | 2 +- .../LoadMoreWrapper.module.css | 43 ++ .../LoadMoreWrapper/LoadMoreWrapper.tsx | 62 +++ .../core/components/LoadMoreWrapper/index.ts | 1 + src/v4/core/components/index.ts | 2 + src/v4/core/providers/ThemeProvider.tsx | 8 +- .../CommentTray/CommentTray.module.css | 15 + .../components/CommentTray/CommentTray.tsx | 16 +- .../components/CommentTray/ui.stories.tsx | 2 + .../HyperLinkConfig.module.css | 4 +- .../HyperLinkConfig/HyperLinkConfig.tsx | 209 ++++---- .../ReactionList/ReactionList.module.css | 15 +- .../components/ReactionList/ReactionList.tsx | 8 +- .../components/StoryTab/StoryRing.module.css | 27 +- .../social/components/StoryTab/StoryRing.tsx | 12 +- .../components/StoryTab/StoryTab.module.css | 227 +++++---- .../elements/HyperLink/HyperLink.module.css | 21 + .../social/elements/HyperLink/HyperLink.tsx | 17 +- src/v4/social/elements/HyperLink/styles.tsx | 23 - src/v4/social/elements/index.ts | 1 + src/v4/social/icons/chevron_down.tsx | 20 + src/v4/social/icons/expand.tsx | 21 + src/v4/social/icons/flag.tsx | 21 + src/v4/social/icons/index.ts | 6 + src/v4/social/icons/link.tsx | 21 + src/v4/social/icons/pen.tsx | 21 + src/v4/social/icons/trash.tsx | 21 + .../Badege/Badge.module.css | 10 + .../internal-components/Badege/Badge.tsx | 6 +- .../internal-components/Badege/styles.tsx | 12 - .../Badege/{types.tsx => types.ts} | 0 .../internal-components/Badege/ui.stories.tsx | 29 ++ .../Comment/Comment.module.css | 17 + .../Comment/CommentText.module.css | 25 + .../Comment/CommentText.tsx | 12 +- .../Comment/UIComment.module.css | 148 ++++++ .../internal-components/Comment/UIComment.tsx | 143 +++--- .../internal-components/Comment/index.tsx | 152 ++---- .../internal-components/Comment/styles.tsx | 305 ----------- .../CommentComposeBar.module.css | 62 +++ .../CommentComposeBar/CommentComposeBar.tsx | 35 +- .../CommentComposeBar/{index.tsx => index.ts} | 0 .../CommentComposeBar/styles.tsx | 64 --- .../CommentList/CommentList.module.css | 28 ++ .../CommentList/CommentList.tsx | 70 +-- .../LoadingIndicator.module.css | 20 + .../LoadingIndicator/LoadingIndicator.tsx | 15 + .../LoadingIndicator/index.ts | 1 + .../Playground/Playground.tsx | 5 - .../internal-components/Playground/index.ts | 1 - .../StoryCommentComposeBar.module.css | 44 ++ .../StoryCommentComposeBar.tsx | 34 +- .../StoryCommentComposeBar/styles.tsx | 23 - .../StoryViewer/Renderers/Image.tsx | 137 +++-- .../Renderers/Renderers.module.css | 476 +++++++++--------- .../StoryViewer/Renderers/Video.tsx | 157 +++--- .../StoryViewer/Renderers/styles.tsx | 7 - .../VideoPreview/VideoPreview.tsx | 19 + .../internal-components/VideoPreview/index.ts | 1 + .../social/pages/Application/sdk.stories.tsx | 6 +- .../pages/DraftsPage/DraftsPage.module.css | 32 +- src/v4/social/pages/DraftsPage/DraftsPage.tsx | 17 +- src/v4/social/pages/DraftsPage/styles.tsx | 98 ---- .../pages/StoryPage/GlobalFeedStory.tsx | 2 +- src/v4/styles/global.css | 45 -- 76 files changed, 1786 insertions(+), 1641 deletions(-) create mode 100644 src/v4/core/components/Avatar/Avatar.module.css create mode 100644 src/v4/core/components/Avatar/Avatar.tsx create mode 100644 src/v4/core/components/Avatar/index.ts create mode 100644 src/v4/core/components/BottomSheet/BottomSheet.module.css delete mode 100644 src/v4/core/components/BottomSheet/styles.tsx create mode 100644 src/v4/core/components/LoadMoreWrapper/LoadMoreWrapper.module.css create mode 100644 src/v4/core/components/LoadMoreWrapper/LoadMoreWrapper.tsx create mode 100644 src/v4/core/components/LoadMoreWrapper/index.ts create mode 100644 src/v4/social/elements/HyperLink/HyperLink.module.css delete mode 100644 src/v4/social/elements/HyperLink/styles.tsx create mode 100644 src/v4/social/icons/chevron_down.tsx create mode 100644 src/v4/social/icons/expand.tsx create mode 100644 src/v4/social/icons/flag.tsx create mode 100644 src/v4/social/icons/link.tsx create mode 100644 src/v4/social/icons/pen.tsx create mode 100644 src/v4/social/icons/trash.tsx create mode 100644 src/v4/social/internal-components/Badege/Badge.module.css delete mode 100644 src/v4/social/internal-components/Badege/styles.tsx rename src/v4/social/internal-components/Badege/{types.tsx => types.ts} (100%) create mode 100644 src/v4/social/internal-components/Badege/ui.stories.tsx create mode 100644 src/v4/social/internal-components/Comment/Comment.module.css create mode 100644 src/v4/social/internal-components/Comment/CommentText.module.css create mode 100644 src/v4/social/internal-components/Comment/UIComment.module.css delete mode 100644 src/v4/social/internal-components/Comment/styles.tsx create mode 100644 src/v4/social/internal-components/CommentComposeBar/CommentComposeBar.module.css rename src/v4/social/internal-components/CommentComposeBar/{index.tsx => index.ts} (100%) delete mode 100644 src/v4/social/internal-components/CommentComposeBar/styles.tsx create mode 100644 src/v4/social/internal-components/CommentList/CommentList.module.css create mode 100644 src/v4/social/internal-components/LoadingIndicator/LoadingIndicator.module.css create mode 100644 src/v4/social/internal-components/LoadingIndicator/LoadingIndicator.tsx create mode 100644 src/v4/social/internal-components/LoadingIndicator/index.ts delete mode 100644 src/v4/social/internal-components/Playground/Playground.tsx delete mode 100644 src/v4/social/internal-components/Playground/index.ts create mode 100644 src/v4/social/internal-components/StoryCommentComposeBar/StoryCommentComposeBar.module.css delete mode 100644 src/v4/social/internal-components/StoryCommentComposeBar/styles.tsx create mode 100644 src/v4/social/internal-components/VideoPreview/VideoPreview.tsx create mode 100644 src/v4/social/internal-components/VideoPreview/index.ts delete mode 100644 src/v4/social/pages/DraftsPage/styles.tsx diff --git a/package.json b/package.json index df1e21b43..e6f7132fa 100644 --- a/package.json +++ b/package.json @@ -101,6 +101,7 @@ "clsx": "^2.1.0", "extract-colors": "^4.0.2", "filesize": "^9.0.11", + "framer-motion": "^11.1.7", "hls.js": "^1.4.14", "linkify-react": "^4.1.3", "linkifyjs": "^4.1.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c8f28b455..295db7885 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ dependencies: filesize: specifier: ^9.0.11 version: 9.0.11 + framer-motion: + specifier: ^11.1.7 + version: 11.1.7(react-dom@18.3.1)(react@18.3.1) hls.js: specifier: ^1.4.14 version: 1.5.8 @@ -237,7 +240,7 @@ devDependencies: version: 7.1.1(webpack@5.91.0) ts-jest: specifier: ^29.1.1 - version: 29.1.2(@babel/core@7.24.4)(esbuild@0.19.12)(jest@29.7.0)(typescript@4.9.5) + version: 29.1.2(@babel/core@7.24.4)(esbuild@0.18.20)(jest@29.7.0)(typescript@4.9.5) tsup: specifier: ^7.3.0 version: 7.3.0(postcss@8.4.38)(typescript@4.9.5) @@ -7199,7 +7202,7 @@ packages: dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0(esbuild@0.18.20) dev: true /file-system-cache@2.3.0: @@ -11548,7 +11551,7 @@ packages: dependencies: file-loader: 6.2.0(webpack@5.91.0) loader-utils: 2.0.4 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0(esbuild@0.18.20) dev: true /synchronous-promise@2.0.17: @@ -11621,7 +11624,7 @@ packages: unique-string: 2.0.0 dev: true - /terser-webpack-plugin@5.3.10(esbuild@0.19.12)(webpack@5.91.0): + /terser-webpack-plugin@5.3.10(esbuild@0.18.20)(webpack@5.91.0): resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -11638,12 +11641,12 @@ packages: optional: true dependencies: '@jridgewell/trace-mapping': 0.3.25 - esbuild: 0.19.12 + esbuild: 0.18.20 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 terser: 5.30.4 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0(esbuild@0.18.20) dev: true /terser@5.30.4: @@ -11794,7 +11797,7 @@ packages: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} dev: true - /ts-jest@29.1.2(@babel/core@7.24.4)(esbuild@0.19.12)(jest@29.7.0)(typescript@4.9.5): + /ts-jest@29.1.2(@babel/core@7.24.4)(esbuild@0.18.20)(jest@29.7.0)(typescript@4.9.5): resolution: {integrity: sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==} engines: {node: ^16.10.0 || ^18.0.0 || >=20.0.0} hasBin: true @@ -11817,7 +11820,7 @@ packages: dependencies: '@babel/core': 7.24.4 bs-logger: 0.2.6 - esbuild: 0.19.12 + esbuild: 0.18.20 fast-json-stable-stringify: 2.1.0 jest: 29.7.0 jest-util: 29.7.0 @@ -12391,7 +12394,7 @@ packages: resolution: {integrity: sha512-poXpCylU7ExuvZK8z+On3kX+S8o/2dQ/SVYueKA0D4WEMXROXgY8Ez50/bQEUmvoSMMrWcrJqCHuhAbsiwg7Dg==} dev: true - /webpack@5.91.0(esbuild@0.19.12): + /webpack@5.91.0(esbuild@0.18.20): resolution: {integrity: sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==} engines: {node: '>=10.13.0'} hasBin: true @@ -12422,7 +12425,7 @@ packages: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(esbuild@0.19.12)(webpack@5.91.0) + terser-webpack-plugin: 5.3.10(esbuild@0.18.20)(webpack@5.91.0) watchpack: 2.4.1 webpack-sources: 3.2.3 transitivePeerDependencies: diff --git a/src/v4/core/components/Avatar/Avatar.module.css b/src/v4/core/components/Avatar/Avatar.module.css new file mode 100644 index 000000000..1fa3fe8e1 --- /dev/null +++ b/src/v4/core/components/Avatar/Avatar.module.css @@ -0,0 +1,100 @@ +.avatarContainer { + position: relative; + flex-shrink: 0; + overflow: hidden; + border-radius: 50%; + background-color: var(--asc-color-base-shade3); +} + +.avatarContainer.visible .img { + opacity: 1; +} + +.avatarContainer.clickable:hover { + cursor: pointer; +} + +.avatarContainer.small { + width: 2rem; + height: 2rem; +} + +.avatarContainer.medium { + width: 3rem; + height: 3rem; +} + +.avatarContainer.large { + width: 4rem; + height: 4rem; +} + +.skeleton { + width: 100%; + height: 100%; + display: block; + border-radius: 50%; + background-color: var(--asc-color-base-shade4); +} + +.skeleton-small { + width: 2rem; + height: 2rem; +} + +.skeleton-medium { + width: 3rem; + height: 3rem; +} + +.skeleton-large { + width: 4rem; + height: 4rem; +} + +.avatarOverlay { + position: absolute; + z-index: 2; + opacity: 0.5; + background-color: #000000; + width: 100%; + height: 100%; +} + +.avatarOverlay-small { + width: 2rem; + height: 2rem; +} + +.avatarOverlay-medium { + width: 3rem; + height: 3rem; +} + +.avatarOverlay-large { + width: 4rem; + height: 4rem; +} + +.img { + height: 100%; + width: 100%; + object-fit: cover; + opacity: 0; + transition: opacity 0.3s; +} + +.img-small { + height: 2rem; + width: 2rem; +} + +.img-medium { + height: 3rem; + width: 3rem; +} + +.img-large { + height: 4rem; + width: 4rem; +} diff --git a/src/v4/core/components/Avatar/Avatar.tsx b/src/v4/core/components/Avatar/Avatar.tsx new file mode 100644 index 000000000..93b513a3c --- /dev/null +++ b/src/v4/core/components/Avatar/Avatar.tsx @@ -0,0 +1,67 @@ +import React, { useState, useCallback } from 'react'; +import clsx from 'clsx'; +import styles from './Avatar.module.css'; + +export interface AvatarProps { + className?: string; + avatar?: string | null; + showOverlay?: boolean; + onClick?: () => void; + loading?: boolean; + backgroundImage?: string | null; + size?: 'small' | 'medium' | 'large'; +} + +export const Avatar = ({ + className = '', + avatar = null, + showOverlay, + onClick, + loading, + size = 'medium', + ...props +}: AvatarProps) => { + const [visible, setVisible] = useState(false); + const onLoad = useCallback(() => setVisible(true), []); + const onError = useCallback(() => setVisible(false), []); + + return ( +
+ {loading ? ( +
+ ) : avatar ? ( + showOverlay ? ( +
+ Avatar +
+ ) : ( + Avatar + ) + ) : null} +
+ ); +}; diff --git a/src/v4/core/components/Avatar/index.ts b/src/v4/core/components/Avatar/index.ts new file mode 100644 index 000000000..d3fb6dfa7 --- /dev/null +++ b/src/v4/core/components/Avatar/index.ts @@ -0,0 +1 @@ +export { Avatar } from './Avatar'; diff --git a/src/v4/core/components/BottomSheet/BottomSheet.module.css b/src/v4/core/components/BottomSheet/BottomSheet.module.css new file mode 100644 index 000000000..50dfbf72a --- /dev/null +++ b/src/v4/core/components/BottomSheet/BottomSheet.module.css @@ -0,0 +1,31 @@ +/* +You can add your own styles or override the default sheet styles via the exposed class names. +Note that you might need to use !important for style overrides since the inner styles are applied as inline styles +which have higher specificity. +*/ + +.react-modal-sheet-container { + border-top-left-radius: var(--asc-spacing-m2) !important; + border-top-right-radius: var(--asc-spacing-m2) !important; + background-color: var(--asc-color-base-background); + color: var(--asc-color-base-default); +} + +.react-modal-sheet-backdrop { + background-color: var(--asc-color-base-inverse, rgba(0, 0, 0, 0.5)); +} + +.react-modal-sheet-header { + background-color: var(--asc-color-base-background); + color: var(--asc-color-base-default); + text-align: center; + border-bottom: 1px solid var(--asc-color-base-shade4); + padding-bottom: 0.5rem; + background: var(--asc-color-base-background); +} + +.react-modal-sheet-content { + background-color: var(--asc-color-base-background); + padding: 1rem; + color: var(--asc-color-base-default); +} diff --git a/src/v4/core/components/BottomSheet/BottomSheet.tsx b/src/v4/core/components/BottomSheet/BottomSheet.tsx index 3c23343f1..0ddd38cc6 100644 --- a/src/v4/core/components/BottomSheet/BottomSheet.tsx +++ b/src/v4/core/components/BottomSheet/BottomSheet.tsx @@ -1,5 +1,8 @@ import React from 'react'; -import { MobileSheet } from './styles'; +import Sheet from 'react-modal-sheet'; +import { Typography } from '~/v4/core/components/Typography'; + +import styles from './BottomSheet.module.css'; interface BottomSheetProps { isOpen: boolean; @@ -8,28 +11,28 @@ interface BottomSheetProps { rootId?: string; mountPoint?: HTMLElement; detent?: 'content-height' | 'full-height'; - 'data-qa-anchor'?: string; + headerTitle?: string; + cancelText?: string; + okText?: string; } -export const BottomSheet = ({ - isOpen = false, - onClose = () => {}, - detent = 'content-height', - rootId, - children, - mountPoint, - ...props -}: BottomSheetProps) => { +export const BottomSheet = ({ children, headerTitle, ...props }: BottomSheetProps) => { return ( - - {children} - + + + + {headerTitle && ( + + {headerTitle} + + )} + {children} + + + ); }; diff --git a/src/v4/core/components/BottomSheet/styles.tsx b/src/v4/core/components/BottomSheet/styles.tsx deleted file mode 100644 index aacb273c5..000000000 --- a/src/v4/core/components/BottomSheet/styles.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import styled from 'styled-components'; -import Sheet from 'react-modal-sheet'; -import { SecondaryButton } from '~/core/components/Button'; - -export const MobileSheet = styled(Sheet)` - margin: 0 auto; - width: 100%; - z-index: 9999; -`; - -export const MobileSheetNestedBackDrop = styled(MobileSheet.Backdrop)` - background-color: rgba(0, 0, 0, 0.5); -`; - -export const MobileSheetContainer = styled(MobileSheet.Container)` - z-index: 101; -`; - -export const MobileSheetHeader = styled(MobileSheet.Header)` - ${({ theme }) => theme.typography.title}; - color: ${({ theme }) => theme.palette.base.default}; - text-align: center; - border-bottom: 1px solid #e3e4e8; - padding-bottom: 0.5rem; - z-index: 100; - border-top-left-radius: 0.5rem; - border-top-right-radius: 0.5rem; -`; - -export const MobileSheetContent = styled(MobileSheet.Content)` - z-index: 100; -`; - -export const MobileSheetButton = styled(SecondaryButton)` - display: flex; - justify-content: flex-start; - align-items: center; - gap: 0.5rem; - width: 100%; -`; diff --git a/src/v4/core/components/Button/Button.module.css b/src/v4/core/components/Button/Button.module.css index adb2c9e8b..f0c9688d3 100644 --- a/src/v4/core/components/Button/Button.module.css +++ b/src/v4/core/components/Button/Button.module.css @@ -35,6 +35,16 @@ background-color: var(--asc-color-base-shade4); } +.ghost { + background-color: transparent; + color: var(--asc-color-primary-default); + border: 1px solid var(--asc-color-primary-default); +} + +.ghost:hover:not(.disabled) { + background-color: var(--asc-color-base-shade4); +} + .small { font-size: var(--asc-text-font-size-sm); padding: var(--asc-spacing-xxs2) var(--asc-spacing-s1); @@ -52,4 +62,4 @@ .icon { margin-right: var(--asc-spacing-s1); -} +} \ No newline at end of file diff --git a/src/v4/core/components/Button/Button.tsx b/src/v4/core/components/Button/Button.tsx index 30c161aec..95f24f2b2 100644 --- a/src/v4/core/components/Button/Button.tsx +++ b/src/v4/core/components/Button/Button.tsx @@ -4,7 +4,7 @@ import styles from './Button.module.css'; interface ButtonProps extends React.ButtonHTMLAttributes { children: React.ReactNode; - variant?: 'primary' | 'secondary'; + variant?: 'primary' | 'secondary' | 'ghost'; size?: 'small' | 'medium' | 'large'; icon?: React.ReactNode; } diff --git a/src/v4/core/components/InputText/index.tsx b/src/v4/core/components/InputText/index.tsx index b1786c1d5..0274cf713 100644 --- a/src/v4/core/components/InputText/index.tsx +++ b/src/v4/core/components/InputText/index.tsx @@ -1,5 +1,4 @@ import React, { forwardRef } from 'react'; - import InsideInputText from './InsideInputText'; import { QueryMentioneesFnType } from '~/v4/chat/hooks/useMention'; diff --git a/src/v4/core/components/InputText/styles.module.css b/src/v4/core/components/InputText/styles.module.css index 5814ee3f7..4f93ad650 100644 --- a/src/v4/core/components/InputText/styles.module.css +++ b/src/v4/core/components/InputText/styles.module.css @@ -6,7 +6,7 @@ background: var(--asc-color-base-shade4); border: 1px solid var(--asc-color-base-shade4); - border-radius: var(--asc-border-radius-sm); + border-radius: 1.25rem; transition: background 0.2s, border-color 0.2s; &.invalid { diff --git a/src/v4/core/components/LoadMoreWrapper/LoadMoreWrapper.module.css b/src/v4/core/components/LoadMoreWrapper/LoadMoreWrapper.module.css new file mode 100644 index 000000000..cc3eb1311 --- /dev/null +++ b/src/v4/core/components/LoadMoreWrapper/LoadMoreWrapper.module.css @@ -0,0 +1,43 @@ +.loadMoreButton { + display: inline-flex; + justify-items: center; + background: var(--asc-color-base-background); + color: var(--asc-color-base-shade2); + border: 1px solid var(--asc-color-base-shade4); + padding: 0.3125rem 0.75rem 0.3125rem 0.5rem; + border-radius: 0.25rem; + margin-left: 3rem; +} + +.loadMoreButton.textCenter { + justify-content: center; +} + +.loadMoreButton.noBorder { + border: none; +} + +.loadMoreButton.commentsButton { + justify-content: flex-start; + color: var(--asc-color-base-default); + border: none; + margin-top: 1rem; + padding: 0 0 1rem 0; + border-bottom: 1px solid #e3e4e8; +} + +.loadMoreButton.replyButton { + width: fit-content; + background-color: var(--asc-color-base-shade4); + color: var(--asc-color-base-shade1); + margin: 0.75rem 0 1rem 3rem; + padding: 0.3125rem 0.75rem; + border-radius: 0.5rem; + font-size: var(--asc-text-font-size-xs); + font-weight: var(--asc-text-font-weight-bold); + line-height: var(--asc-line-height-xs); +} + +.chevronDownIcon { + margin-left: 0.3125rem; +} diff --git a/src/v4/core/components/LoadMoreWrapper/LoadMoreWrapper.tsx b/src/v4/core/components/LoadMoreWrapper/LoadMoreWrapper.tsx new file mode 100644 index 000000000..fbb7328f3 --- /dev/null +++ b/src/v4/core/components/LoadMoreWrapper/LoadMoreWrapper.tsx @@ -0,0 +1,62 @@ +import React, { useState, useEffect, ReactNode } from 'react'; +import { useIntl } from 'react-intl'; +import clsx from 'clsx'; +import { Button } from '~/v4/core/components'; +import styles from './LoadMoreWrapper.module.css'; +import { ChevronDownIcon } from '~/v4/social/icons'; + +interface LoadMoreWrapperProps { + hasMore?: boolean; + loadMore?: () => void; + text?: string; + contentSlot: ReactNode; + className?: string; + prependIcon?: ReactNode; + appendIcon?: ReactNode; + isExpanded?: boolean; +} + +export const LoadMoreWrapper = ({ + hasMore, + loadMore, + text = '', + contentSlot, + className = '', + prependIcon = null, + appendIcon = , + isExpanded = true, +}: LoadMoreWrapperProps) => { + const { formatMessage } = useIntl(); + const [expanded, setExpanded] = useState(isExpanded); + + useEffect(() => setExpanded(isExpanded), [isExpanded]); + + if (expanded) { + return ( + <> + {contentSlot} + {hasMore && ( + + )} + + ); + } + + return !expanded ? ( + <> + + + ) : null; +}; diff --git a/src/v4/core/components/LoadMoreWrapper/index.ts b/src/v4/core/components/LoadMoreWrapper/index.ts new file mode 100644 index 000000000..aac7b91d4 --- /dev/null +++ b/src/v4/core/components/LoadMoreWrapper/index.ts @@ -0,0 +1 @@ +export { LoadMoreWrapper } from './LoadMoreWrapper'; diff --git a/src/v4/core/components/index.ts b/src/v4/core/components/index.ts index 8596d8256..cb0434874 100644 --- a/src/v4/core/components/index.ts +++ b/src/v4/core/components/index.ts @@ -1,3 +1,5 @@ export { BottomSheet } from './BottomSheet'; export { Icon } from './Icon'; export { Typography } from './Typography'; +export { Avatar } from './Avatar'; +export { Button } from './Button'; diff --git a/src/v4/core/providers/ThemeProvider.tsx b/src/v4/core/providers/ThemeProvider.tsx index 6e6a7489d..472898a5c 100644 --- a/src/v4/core/providers/ThemeProvider.tsx +++ b/src/v4/core/providers/ThemeProvider.tsx @@ -23,7 +23,7 @@ const generateShades = (hexColor: string, isDarkMode = false): string[] => { return shades; }; -const generatePalleteByConfig = ({ +const generatePaletteByConfig = ({ themeConfig, configId, isDarkMode, @@ -78,7 +78,7 @@ const generateComponentPalette = (config: Config, currentTheme: 'light' | 'dark' const configId = configurable.pageId.replace('_', '-'); if (themeToGenerate) { - generatePalleteByConfig({ + generatePaletteByConfig({ themeConfig: themeToGenerate, configId, isDarkMode: currentTheme === 'dark', @@ -100,7 +100,7 @@ const generateComponentPalette = (config: Config, currentTheme: 'light' | 'dark' configurable.pageId.replace('_', '-') + '-' + componentId.replace('_', '-'); if (themeToGenerate) { - generatePalleteByConfig({ + generatePaletteByConfig({ themeConfig: themeToGenerate, configId, isDarkMode: currentTheme === 'dark', @@ -136,7 +136,7 @@ export const ThemeProvider: React.FC = ({ children }) => { const themeToGenerate = currentTheme === 'light' ? config.theme?.light : config.theme?.dark; if (themeToGenerate) { - generatePalleteByConfig({ + generatePaletteByConfig({ themeConfig: themeToGenerate, isDarkMode: currentTheme === 'dark', }); diff --git a/src/v4/social/components/CommentTray/CommentTray.module.css b/src/v4/social/components/CommentTray/CommentTray.module.css index dedaa992d..72a5395b5 100644 --- a/src/v4/social/components/CommentTray/CommentTray.module.css +++ b/src/v4/social/components/CommentTray/CommentTray.module.css @@ -48,3 +48,18 @@ background-color: rgba(0, 0, 0, 0.5); z-index: -1; } + +.commentTrayContainer { + height: 100%; + display: flex; + flex-direction: column; +} + +.commentListContainer { + overflow-y: auto; + flex-grow: 1; +} + +.mobileSheetComposeBarContainer { + width: 100%; +} diff --git a/src/v4/social/components/CommentTray/CommentTray.tsx b/src/v4/social/components/CommentTray/CommentTray.tsx index 93e33403e..5dd1d940d 100644 --- a/src/v4/social/components/CommentTray/CommentTray.tsx +++ b/src/v4/social/components/CommentTray/CommentTray.tsx @@ -1,8 +1,7 @@ import React, { useState } from 'react'; - -import { CommentList } from '../../internal-components/CommentList'; -import { MobileSheetComposeBarContainer } from '../../internal-components/StoryViewer/styles'; -import { StoryCommentComposeBar } from '../../internal-components/StoryCommentComposeBar'; +import { CommentList } from '~/v4/social/internal-components/CommentList'; +import { StoryCommentComposeBar } from '~/v4/social/internal-components/StoryCommentComposeBar'; +import styles from './CommentTray.module.css'; const REPLIES_PER_PAGE = 5; @@ -35,8 +34,8 @@ export const CommentTray = ({ }; return ( -
-
+
+
- +
- +
); }; diff --git a/src/v4/social/components/CommentTray/ui.stories.tsx b/src/v4/social/components/CommentTray/ui.stories.tsx index 0fb5a9611..fd6c64553 100644 --- a/src/v4/social/components/CommentTray/ui.stories.tsx +++ b/src/v4/social/components/CommentTray/ui.stories.tsx @@ -26,6 +26,7 @@ export default { base_shade4_color: '#000000', alert_color: '#000000', background_color: '#FFFFFF', + base_inverse_color: '#FFFFFF', }, dark: { primary_color: '#FF0000', @@ -37,6 +38,7 @@ export default { base_shade4_color: '#000000', alert_color: '#000000', background_color: '#000000', + base_inverse_color: '#FFFFFF', }, }, excludes: [], diff --git a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css index a1bb228d7..5e40478f4 100644 --- a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css +++ b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css @@ -1,5 +1,4 @@ .hyperlinkFormContainer { - padding: var(--asc-spacing-m1); border-radius: var(--asc-border-radius-md); } @@ -62,7 +61,6 @@ display: flex; align-items: center; justify-content: space-between; - padding: 0 var(--asc-spacing-m1); } .labelContainer { @@ -76,7 +74,9 @@ } .styledSecondaryButton { + border: none; color: var(--asc-color-base-default); + background: var(--asc-color-base-background) } .removeIcon { diff --git a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx index dad3c7fac..72483ff46 100644 --- a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx +++ b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx @@ -3,21 +3,15 @@ import { useIntl } from 'react-intl'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; -import { SecondaryButton } from '~/core/components/Button'; +import clsx from 'clsx'; import useSDK from '~/core/hooks/useSDK'; import { BottomSheet, Typography } from '~/v4/core/components'; -import { - MobileSheet, - MobileSheetContainer, - MobileSheetContent, - MobileSheetHeader, -} from '~/v4/core/components/BottomSheet/styles'; import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; import { Trash2Icon } from '~/icons'; import styles from './HyperLinkConfig.module.css'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; -import Button from '~/v4/core/components/Button/Button'; +import { Button } from '~/v4/core/components/Button'; interface HyperLinkConfigProps { pageId: '*'; @@ -105,123 +99,102 @@ export const HyperLinkConfig = ({ isOpen={isOpen} onClose={onClose} > - - - + + + {formatMessage({ id: 'storyCreation.hyperlink.bottomSheet.title' })} + + +
+
+
+
+
- {formatMessage({ id: 'storyCreation.hyperlink.bottomSheet.title' })} + - - {doneButtonConfig?.done_button_text || - formatMessage({ id: 'storyCreation.hyperlink.bottomSheet.submit' })} - {doneButtonConfig?.done_icon && ( - - )} - -
- - -
- -
- - - - - {errors?.url && {errors?.url?.message}} -
-
-
- - - -
- {watch('customText')?.length} / {MAX_LENGTH} -
-
- - {errors?.customText && ( - {errors?.customText?.message} - )} -
+
+
+ + + +
+ {watch('customText')?.length || 0} / {MAX_LENGTH}
- {isHaveHyperLink && ( -
- -
-
- )} - +
+ + {errors?.customText && ( + {errors?.customText?.message} + )} + + +
- - + {isHaveHyperLink && ( +
+ +
+
+ )} + +
); }; diff --git a/src/v4/social/components/ReactionList/ReactionList.module.css b/src/v4/social/components/ReactionList/ReactionList.module.css index 16886317c..cf31c26be 100644 --- a/src/v4/social/components/ReactionList/ReactionList.module.css +++ b/src/v4/social/components/ReactionList/ReactionList.module.css @@ -7,7 +7,7 @@ .tabList { display: flex; gap: var(--asc-spacing-s1); - border-bottom: 1px solid var(--asc-color-base-shade3); + border-bottom: 1px solid var(--asc-color-base-shade4); padding-bottom: var(--asc-spacing-s1); } @@ -15,25 +15,25 @@ cursor: pointer; padding: var(--asc-spacing-xxs2) var(--asc-spacing-s1); border-radius: var(--asc-border-radius-sm); - background-color: var(--asc-color-base-inverse); + background: var(--asc-color-base-background); color: var(--asc-color-base-shade6); } .tabItem.active { background-color: var(--asc-color-base-shade4); - color: var(--asc-color-base-inverse); + color: var(--asc-color-primary-default); } .reactionEmoji { display: flex; align-items: center; - gap: var(--asc-spacing-xxs); + gap: var(--asc-spacing-s1); } .tabCount { font-size: 0.8rem; - background-color: var(--asc-color-base-shade4); - color: var(--asc-color-base-inverse); + background: var(--asc-color-base-background); + color: var(--asc-color-base-shade1); padding: 0.1rem 0.3rem; border-radius: var(--asc-border-radius-sm); } @@ -48,7 +48,7 @@ display: flex; align-items: center; gap: var(--asc-spacing-s1); - background-color: var(--asc-color-base-inverse); + background: var(--asc-color-base-background); padding: var(--asc-spacing-s1); border-radius: var(--asc-border-radius-sm); width: 100%; @@ -58,4 +58,5 @@ display: flex; align-items: center; gap: var(--asc-spacing-s1); + color: var(--asc-color-base-default); } diff --git a/src/v4/social/components/ReactionList/ReactionList.tsx b/src/v4/social/components/ReactionList/ReactionList.tsx index 8100f03dd..c653c3b66 100644 --- a/src/v4/social/components/ReactionList/ReactionList.tsx +++ b/src/v4/social/components/ReactionList/ReactionList.tsx @@ -1,9 +1,9 @@ import React, { Fragment, useState } from 'react'; -import Avatar from '~/core/components/Avatar'; + import { FireIcon, HeartIcon, LikedIcon } from '~/icons'; import styles from './ReactionList.module.css'; -import { useReactionsCollection } from '../../hooks'; -import { Typography } from '~/v4/core/components'; +import { useReactionsCollection } from '~/v4/social/hooks/collections/useReactionsCollection'; +import { Avatar, Typography } from '~/v4/core/components'; interface ReactionListProps { referenceId: string; @@ -83,7 +83,7 @@ export const ReactionList = ({ referenceId, referenceType }: ReactionListProps)
- {reaction.user?.displayName} + {reaction.user?.displayName}
diff --git a/src/v4/social/components/StoryTab/StoryRing.module.css b/src/v4/social/components/StoryTab/StoryRing.module.css index 673a987e4..dc031eac5 100644 --- a/src/v4/social/components/StoryTab/StoryRing.module.css +++ b/src/v4/social/components/StoryTab/StoryRing.module.css @@ -1,14 +1,25 @@ -@keyframes animateRing { +.emptyStateRing { + stroke: var(--asc-color-base-shade4); + stroke-width: 2; + fill: none; +} + +.errorRing { + stroke: var(--asc-color-alert); +} + +.uploadingProgressRing { + stroke-dasharray: 339; + stroke-dashoffset: 339; + transform: rotate(-90deg); + animation: progress 2s linear infinite; +} + +@keyframes progress { 0% { stroke-dashoffset: 339; } 100% { stroke-dashoffset: 0; } -} - -.uploadingProgressRing { - animation: animateRing 2s linear 0s infinite; - -webkit-animation: animateRing 2s linear 0s infinite; - -moz-animation: animateRing 2s linear 0s infinite; -} +} \ No newline at end of file diff --git a/src/v4/social/components/StoryTab/StoryRing.tsx b/src/v4/social/components/StoryTab/StoryRing.tsx index 95b94ee71..c99a69c5d 100644 --- a/src/v4/social/components/StoryTab/StoryRing.tsx +++ b/src/v4/social/components/StoryTab/StoryRing.tsx @@ -45,16 +45,7 @@ const EmptyStateRingSvg = ({ - + ); }; @@ -121,6 +112,7 @@ const UploadingRingSvg = ({ }) => { const { getConfig } = useCustomization(); const elementConfig = getConfig(`${pageId}/${componentId}/${elementId}`); + return ( { href: string; - icon?: React.ReactNode; } -export const HyperLink: React.FC = ({ - href, - children, - icon = , - ...rest -}) => { +export const HyperLink: React.FC = ({ href, children, ...rest }) => { return ( - - {icon && icon} + + {children} - + ); }; diff --git a/src/v4/social/elements/HyperLink/styles.tsx b/src/v4/social/elements/HyperLink/styles.tsx deleted file mode 100644 index 5b295869e..000000000 --- a/src/v4/social/elements/HyperLink/styles.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import styled from 'styled-components'; -import { Icon } from '~/v4/core/components/Icon'; - -export const StyledLink = styled.a` - border: ${({ theme }) => `1px solid ${theme.v4.colors.base.shade4}`}; - color: ${({ theme }) => theme.v4.colors.secondary.default}; - background: ${({ theme }) => theme.v4.colors.hyperlink.default}; - display: inline-flex; - align-items: center; - padding: 0.625rem 1rem 0.625rem 0.75rem; - border-radius: 1.5rem; - transition: background-color 0.3s, color 0.3s; - font-size: 0.9375rem; - font-style: normal; - font-weight: 600; - line-height: 1.25rem; - letter-spacing: -0.015rem; - gap: 0.5rem; -`; - -export const StyledLinkIcon = styled(Icon)` - fill: ${({ theme }) => theme.v4.colors.primary.default}; -`; diff --git a/src/v4/social/elements/index.ts b/src/v4/social/elements/index.ts index e9882c4fe..db4ec65b3 100644 --- a/src/v4/social/elements/index.ts +++ b/src/v4/social/elements/index.ts @@ -11,3 +11,4 @@ export { ReactButton } from './ReactButton'; export { SaveButton } from './SaveButton'; export { ShareStoryButton } from './ShareStoryButton/ShareStoryButton'; export { SpeakerButton } from './SpeakerButton'; +export { HyperLink } from './HyperLink'; diff --git a/src/v4/social/icons/chevron_down.tsx b/src/v4/social/icons/chevron_down.tsx new file mode 100644 index 000000000..4c50049e9 --- /dev/null +++ b/src/v4/social/icons/chevron_down.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +const ChevronDown = (props: React.SVGProps) => ( + + + +); + +export default ChevronDown; diff --git a/src/v4/social/icons/expand.tsx b/src/v4/social/icons/expand.tsx new file mode 100644 index 000000000..865e3a81f --- /dev/null +++ b/src/v4/social/icons/expand.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +function Expand(props: React.SVGProps) { + return ( + + + + ); +} + +export default Expand; diff --git a/src/v4/social/icons/flag.tsx b/src/v4/social/icons/flag.tsx new file mode 100644 index 000000000..dd706f400 --- /dev/null +++ b/src/v4/social/icons/flag.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +function Flag(props: React.SVGProps) { + return ( + + + + ); +} + +export default Flag; diff --git a/src/v4/social/icons/index.ts b/src/v4/social/icons/index.ts index c62acf949..ee745d9a9 100644 --- a/src/v4/social/icons/index.ts +++ b/src/v4/social/icons/index.ts @@ -1,2 +1,8 @@ export { default as AspectRatioIcon } from './aspect_ratio'; export { default as VerifiedIcon } from './verified'; +export { default as ExpandIcon } from './expand'; +export { default as PenIcon } from './pen'; +export { default as TrashIcon } from './trash'; +export { default as FlagIcon } from './flag'; +export { default as ChevronDownIcon } from './chevron_down'; +export { default as LinkIcon } from './link'; diff --git a/src/v4/social/icons/link.tsx b/src/v4/social/icons/link.tsx new file mode 100644 index 000000000..1729ae218 --- /dev/null +++ b/src/v4/social/icons/link.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +function Link(props: React.SVGProps) { + return ( + + + + ); +} + +export default Link; diff --git a/src/v4/social/icons/pen.tsx b/src/v4/social/icons/pen.tsx new file mode 100644 index 000000000..8d21fb6c8 --- /dev/null +++ b/src/v4/social/icons/pen.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +function Pen(props: React.SVGProps) { + return ( + + + + ); +} + +export default Pen; diff --git a/src/v4/social/icons/trash.tsx b/src/v4/social/icons/trash.tsx new file mode 100644 index 000000000..5c9cb6065 --- /dev/null +++ b/src/v4/social/icons/trash.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +function Trash(props: React.SVGProps) { + return ( + + + + ); +} + +export default Trash; diff --git a/src/v4/social/internal-components/Badege/Badge.module.css b/src/v4/social/internal-components/Badege/Badge.module.css new file mode 100644 index 000000000..fdf079b3e --- /dev/null +++ b/src/v4/social/internal-components/Badege/Badge.module.css @@ -0,0 +1,10 @@ +.badge { + display: inline-flex; + justify-content: center; + align-items: center; + gap: 3px; + background-color: var(--asc-color-primary-shade3); + color: var(--asc-color-primary-main); + border-radius: 20px; + padding: 0px 6px 0px 4px; +} \ No newline at end of file diff --git a/src/v4/social/internal-components/Badege/Badge.tsx b/src/v4/social/internal-components/Badege/Badge.tsx index c2fcacea5..d70e5cf73 100644 --- a/src/v4/social/internal-components/Badege/Badge.tsx +++ b/src/v4/social/internal-components/Badege/Badge.tsx @@ -1,12 +1,12 @@ import React from 'react'; -import { StyledBadge } from './styles'; +import styles from './Badge.module.css'; import { BadgeProps } from './types'; export const Badge = ({ icon, communityRole }: BadgeProps) => { return ( - +
{icon} {communityRole} - +
); }; diff --git a/src/v4/social/internal-components/Badege/styles.tsx b/src/v4/social/internal-components/Badege/styles.tsx deleted file mode 100644 index a05c21b4e..000000000 --- a/src/v4/social/internal-components/Badege/styles.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import styled from 'styled-components'; - -export const StyledBadge = styled.div` - display: inline-flex; - justify-content: center; - align-items: center; - gap: 3px; - background-color: ${({ theme }) => theme.palette.primary.shade3}; - color: ${({ theme }) => theme.palette.primary.main}; - border-radius: 20px; - padding: 0px 6px 0px 4px; -`; diff --git a/src/v4/social/internal-components/Badege/types.tsx b/src/v4/social/internal-components/Badege/types.ts similarity index 100% rename from src/v4/social/internal-components/Badege/types.tsx rename to src/v4/social/internal-components/Badege/types.ts diff --git a/src/v4/social/internal-components/Badege/ui.stories.tsx b/src/v4/social/internal-components/Badege/ui.stories.tsx new file mode 100644 index 000000000..4c347bc5c --- /dev/null +++ b/src/v4/social/internal-components/Badege/ui.stories.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { Meta, Story } from '@storybook/react'; +import { Badge } from './Badge'; +import { BadgeProps } from './types'; +import { ModeratorBadgeIcon } from '~/icons'; + +export default { + title: 'V4/Components/Badge', + component: Badge, + argTypes: { + icon: { + control: { + type: 'select', + options: ['crown', 'star', 'heart'], // Adjust the options based on your available icons + }, + }, + communityRole: { + control: 'text', + }, + }, +} as Meta; + +const Template: Story = (args) => ; + +export const Default = Template.bind({}); +Default.args = { + icon: , + communityRole: 'Moderator', +}; diff --git a/src/v4/social/internal-components/Comment/Comment.module.css b/src/v4/social/internal-components/Comment/Comment.module.css new file mode 100644 index 000000000..c847277c9 --- /dev/null +++ b/src/v4/social/internal-components/Comment/Comment.module.css @@ -0,0 +1,17 @@ +.actionButton { + display: inline-flex; + justify-content: flex-start; + align-items: center; + gap: 0.25rem; + border: none; + color: var(--asc-color-base-default); + background: var(--asc-color-base-background); + text-align: start; + } + + .actionIcon { + width: 1.25rem; + height: 1.25rem; + fill: var(--asc-color-base-default); + color: var(--asc-color-base-default); + } \ No newline at end of file diff --git a/src/v4/social/internal-components/Comment/CommentText.module.css b/src/v4/social/internal-components/Comment/CommentText.module.css new file mode 100644 index 000000000..f56f9b394 --- /dev/null +++ b/src/v4/social/internal-components/Comment/CommentText.module.css @@ -0,0 +1,25 @@ +.commentContent { + overflow-wrap: anywhere !important; + word-break: break-word; + color: var(--asc-color-base-default); + background-color: var(--asc-color-base-shade4); + border-radius: 0 0.75rem 0.75rem 0.75rem; + padding: 0.75rem; + display: inline-block; + white-space: pre-wrap; + font-size: var(--asc-text-font-size-md); + font-weight: var(--asc-text-font-weight-normal); + line-height: var(--asc-line-height-lg); +} + +.readMoreButton { + color: var(--asc-color-primary-default); + padding: 0 0 0 0.25rem; + background: none; + border: none; + cursor: pointer; +} + +.readMoreButton:hover { + text-decoration: underline; +} diff --git a/src/v4/social/internal-components/Comment/CommentText.tsx b/src/v4/social/internal-components/Comment/CommentText.tsx index 6baca788a..693df78e6 100644 --- a/src/v4/social/internal-components/Comment/CommentText.tsx +++ b/src/v4/social/internal-components/Comment/CommentText.tsx @@ -1,11 +1,12 @@ import React, { useMemo, useState } from 'react'; import { FormattedMessage } from 'react-intl'; import Truncate from 'react-truncate-markup'; +import clsx from 'clsx'; import { findChunks, Mentioned } from '~/v4/helpers/utils'; import MentionHighlightTag from '~/core/components/MentionHighlightTag'; import { processChunks } from '~/core/components/ChunkHighlighter'; import Linkify from '~/core/components/Linkify'; -import { CommentContent, ReadMoreButton } from './styles'; +import styles from './CommentText.module.css'; const COMMENT_MAX_LINES = 8; @@ -31,7 +32,7 @@ const CommentText = ({ const expand = () => setIsExpanded(true); const textContent = text ? ( - +
{chunks.map((chunk) => { const key = `${text}-${chunk.start}-${chunk.end}`; @@ -45,13 +46,12 @@ const CommentText = ({ ); } - return {sub}; } return {sub}; })} - +
) : null; if (isExpanded) { @@ -62,9 +62,9 @@ const CommentText = ({ + } > {textContent} diff --git a/src/v4/social/internal-components/Comment/UIComment.module.css b/src/v4/social/internal-components/Comment/UIComment.module.css new file mode 100644 index 000000000..6e73ad1e3 --- /dev/null +++ b/src/v4/social/internal-components/Comment/UIComment.module.css @@ -0,0 +1,148 @@ +.avatar { + margin-right: 0.5rem; +} + +.container { + display: flex; + gap: 1rem; + width: 100%; +} + +.banIcon { + margin-left: 0.265rem; + margin-top: 1px; +} + +.commentDate { + color: var(--asc-color-base-shade2); +} + +.editedMark { + margin-left: 0.3125rem; + color: var(--asc-color-neutral-shade1); + font-size: var(--asc-text-font-size-sm); + font-weight: var(--asc-text-font-weight-normal); + line-height: var(--asc-line-height-sm); +} + +.editedMark:before { + content: '('; +} + +.editedMark:after { + content: ')'; +} + +.interactionWrapper { + display: flex; + justify-content: flex-start; + align-items: center; + gap: 0.75rem; +} + +.reactionsWrapper { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.interactionBar { + width: 100%; + display: flex; + justify-content: space-between; + width: 100%; + gap: 0.5rem; + align-items: center; + padding: 0.125rem 0; +} + +.commentEditContainer { + display: flex; + flex-direction: column; +} + +.buttonContainer { + display: flex; + justify-content: flex-end; + margin-top: 0.5rem; +} + +.buttonContainer > * { + margin-left: 0.5rem; +} + +.commentEditTextarea { + font-size: var(--asc-text-font-size-md); + font-weight: var(--asc-text-font-weight-normal); + line-height: var(--asc-line-height-lg); + border-radius: 0 0.75rem 0.75rem 0.75rem; + height: 7.5rem; + padding: 0.75rem; +} + +.reactionListButtonContainer { + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + color: var(--asc-color-base-shade2); +} + +.reactionsListButtonWrapper { + display: flex; + align-items: center; + flex-shrink: 1; +} + +.reactionIcon { + display: flex; + align-items: center; + justify-content: center; + width: 1.25rem; + height: 1.25rem; + border-radius: 50%; + margin-left: -0.25rem; +} + +.reactionIcon:first-child { + margin-left: 0; +} + +.commentInteractionButton { + padding: 0.25rem; + display: flex; + align-items: center; + gap: 0.5rem; + color: var(--asc-color-base-shade2); + cursor: pointer; + border: none; +} + +.likeButton { + padding: 0.25rem; + color: var(--asc-color-base-shade2); + border: none; +} + +.liked { + padding: 0.25rem; + color: var(--asc-color-primary-default); + border: none; +} + +.reactionListButton { + border: none; + color: var(--asc-color-primary-default); +} + +.content { + width: 100%; +} + +.commentHeader { + display: flex; + justify-content: space-between; + align-items: center; + gap: 0.5rem; + color: var(--asc-color-base-default); +} diff --git a/src/v4/social/internal-components/Comment/UIComment.tsx b/src/v4/social/internal-components/Comment/UIComment.tsx index de6c98b97..4a81267f4 100644 --- a/src/v4/social/internal-components/Comment/UIComment.tsx +++ b/src/v4/social/internal-components/Comment/UIComment.tsx @@ -7,33 +7,18 @@ import CommentText from './CommentText'; import { backgroundImage as UserImage } from '~/icons/User'; import BanIcon from '~/icons/Ban'; -import { - AuthorName, - Avatar, - ButtonContainer, - CommentDate, - CommentEditContainer, - CommentEditTextarea, - CommentHeader, - CommentInteractionButton, - Content, - EditedMark, - InteractionBar, - InteractionWrapper, - LikeButton, - ReactionIcon, - ReactionListButtonContainer, - ReactionsListButtonWrapper, -} from './styles'; - import { Mentioned, Metadata } from '~/v4/helpers/utils'; import { QueryMentioneesFnType } from '~/v4/chat/hooks/useMention'; import { formatTimeAgo } from '~/utils'; import { EllipsisH, FireIcon, HeartIcon, LikedIcon } from '~/icons'; -import { PrimaryButton, SecondaryButton } from '~/core/components/Button'; import millify from 'millify'; import { FIRE_REACTION_KEY, LIKE_REACTION_KEY, LOVE_REACTION_KEY } from '~/constants'; +import styles from './UIComment.module.css'; +import InputText from '~/v4/core/components/InputText'; +import { Avatar, Typography } from '~/v4/core/components'; +import Button from '~/v4/core/components/Button/Button'; +import clsx from 'clsx'; interface StyledCommentProps { commentId?: string; @@ -120,28 +105,20 @@ const UIComment = ({ onClickReactionList, }: StyledCommentProps) => { return ( - <> - - - - {authorName} +
+ +
+
+ {authorName} - <> - {isBanned && ( - - )} - + <>{isBanned && } - +
{isEditing ? ( - - + onChange?.(data)} /> - - + +
+
) : ( )} {!isEditing && (canLike || canReply || options.length > 0) && ( - - - +
+
+
{formatTimeAgo(createdAt)} {(editedAt?.getTime() || 0) - (createdAt?.getTime() || 0) > 0 && ( - + - + )} - +
{canLike && ( - + )} {canReply && ( - onClickReply?.(authorName, referenceType, referenceId, commentId)} > - + )} - + +
+ {reactionsCount > 0 && ( + + )} +
)} -
- +
+
); }; diff --git a/src/v4/social/internal-components/Comment/index.tsx b/src/v4/social/internal-components/Comment/index.tsx index b93888388..f22158cc8 100644 --- a/src/v4/social/internal-components/Comment/index.tsx +++ b/src/v4/social/internal-components/Comment/index.tsx @@ -1,26 +1,9 @@ -import React, { memo, useState } from 'react'; +import React, { useState } from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; import useComment from '~/social/hooks/useComment'; - import useMention from '~/v4/chat/hooks/useMention'; -import { - CommentBlock, - CommentContainer, - DeletedCommentContainer, - DeletedIcon, - DeletedReplyContainer, - IconContainer, - MessageContainer, - MobileSheet, - MobileSheetButton, - MobileSheetContent, - MobileSheetHeader, - MobileSheetNestedBackDrop, - ReplyContainer, - Text, -} from './styles'; import { extractMetadata, isNonNullable, @@ -29,61 +12,32 @@ import { Metadata, parseMentionsMarkup, } from '~/v4/helpers/utils'; -import { LoadingIndicator } from '~/core/components/ProgressBar/styles'; + import useSDK from '~/core/hooks/useSDK'; import useUser from '~/core/hooks/useUser'; import { CommentRepository, ReactionRepository } from '@amityco/ts-sdk'; -import { useCustomComponent } from '~/core/providers/CustomComponentsProvider'; + import useCommentFlaggedByMe from '~/social/hooks/useCommentFlaggedByMe'; import useCommentPermission from '~/social/hooks/useCommentPermission'; import useCommentSubscription from '~/social/hooks/useCommentSubscription'; - import useImage from '~/core/hooks/useImage'; -import { FlagIcon, Pencil2Icon, Trash2Icon } from '~/icons'; import UIComment from './UIComment'; import { LIKE_REACTION_KEY } from '~/constants'; import { CommentList } from '~/v4/social/internal-components/CommentList'; import { ReactionList } from '~/v4/social/components/ReactionList'; -import { useTheme } from 'styled-components'; import useGetStoryByStoryId from '../../hooks/useGetStoryByStoryId'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; import { useNotifications } from '~/v4/core/providers/NotificationProvider'; -const REPLIES_PER_PAGE = 5; +import { Button, BottomSheet, Typography } from '~/v4/core/components'; -const DeletedComment = () => { - return ( - - - - - - - - - - - ); -}; +import styles from './Comment.module.css'; +import { TrashIcon, PenIcon, FlagIcon } from '~/v4/social/icons'; +import { LoadingIndicator } from '~/v4/social/internal-components/LoadingIndicator'; -const DeletedReply = () => { - return ( -
- - - - - - - - - - -
- ); -}; +const REPLIES_PER_PAGE = 5; function getCommentData(comment: Amity.Comment | null) { if (comment == null) return ''; @@ -103,18 +57,10 @@ interface CommentProps { commentId: Amity.Comment['commentId'], ) => void; style?: React.CSSProperties; - onClickReactionList: (commentId: string) => void; shouldAllowInteraction?: boolean; } -const Comment = ({ - commentId, - readonly, - onClickReply, - style, - shouldAllowInteraction, -}: CommentProps) => { - const theme = useTheme(); +export const Comment = ({ commentId, readonly, onClickReply }: CommentProps) => { const comment = useComment(commentId); const story = useGetStoryByStoryId(comment?.referenceId); const [bottomSheet, setBottomSheet] = useState(false); @@ -241,7 +187,7 @@ const Comment = ({ ? formatMessage({ id: 'reply.edit' }) : formatMessage({ id: 'comment.edit' }), action: startEditing, - icon: , + icon: , } : null, canReport @@ -250,7 +196,7 @@ const Comment = ({ ? formatMessage({ id: 'report.undoReport' }) : formatMessage({ id: 'report.doReport' }), action: handleReportComment, - icon: , + icon: , } : null, canDelete @@ -259,23 +205,13 @@ const Comment = ({ ? formatMessage({ id: 'reply.delete' }) : formatMessage({ id: 'comment.delete' }), action: deleteComment, - icon: , + icon: , } : null, ].filter(isNonNullable); if (comment == null) return null; - if (comment?.isDeleted) { - return isReplyComment ? ( - - ) : ( - - - - ); - } - const renderedComment = ( {isReplyComment ? ( - {renderedComment} +
{renderedComment}
) : ( - - {renderedComment} +
+
{renderedComment}
{comment.children.length > 0 && ( )} - +
)} - - - - - {options.map((bottomSheetAction) => ( - { - bottomSheetAction.action(); - setBottomSheet(false); - }} - > - {bottomSheetAction?.icon} - {bottomSheetAction.name} - - ))} - - - - - ( + + ))} + + setSelectedCommentId('')} mountPoint={document.getElementById('asc-uikit-stories-viewer') as HTMLElement} detent="full-height" > - - - - - - - - - setSelectedCommentId('')} /> - + + ); }; - -export default memo((props: CommentProps) => { - const CustomComponentFn = useCustomComponent('Comment'); - - if (CustomComponentFn) return CustomComponentFn(props); - - return ; -}); diff --git a/src/v4/social/internal-components/Comment/styles.tsx b/src/v4/social/internal-components/Comment/styles.tsx deleted file mode 100644 index 8967647c3..000000000 --- a/src/v4/social/internal-components/Comment/styles.tsx +++ /dev/null @@ -1,305 +0,0 @@ -import React, { ReactNode } from 'react'; -import InputText from '~/core/components/InputText'; -import UICommentComposeBar from '~/social/components/CommentComposeBar'; -import { SecondaryButton } from '~/core/components/Button'; -import { MinusCircle, Reply } from '~/icons'; -import styled, { DefaultTheme } from 'styled-components'; -import UIAvatar from '~/core/components/Avatar'; -import Sheet from 'react-modal-sheet'; - -export const Avatar = styled(UIAvatar)` - margin-right: 0.5rem; -`; - -export const TimeContainer = styled.div` - color: ${({ theme }) => theme.palette.base.shade2}; -`; - -export const EngagementButton = styled(SecondaryButton)` - padding: 0.5rem; - color: ${({ theme }) => theme.palette.base.shade2}; -`; - -export const LikeButton = styled(SecondaryButton)<{ isLiked?: boolean }>` - padding: 0.25rem; - color: ${({ theme, isLiked }) => - isLiked ? theme.palette.primary.main : theme.palette.neutral.shade2}; -`; - -export const CommentInteractionButton = styled(SecondaryButton)` - color: ${({ theme }) => theme.palette.neutral.shade2}; - padding: 0.25rem; -`; - -export const MobileSheet = styled(Sheet)` - margin: 0 auto; - width: 100%; -`; - -export const MobileSheetNestedBackDrop = styled(MobileSheet.Backdrop)` - background-color: rgba(0, 0, 0, 0.5); -`; - -export const MobileSheetContainer = styled(MobileSheet.Container)` - z-index: 100; -`; - -export const MobileSheetHeader = styled(MobileSheet.Header)` - z-index: 100; -`; - -export const MobileSheetContent = styled(MobileSheet.Content)` - z-index: 100; - padding: 0rem 1rem; -`; - -export const MobileSheetButton = styled(SecondaryButton)` - display: flex; - justify-content: flex-start; - align-items: center; - gap: 0.5rem; - width: 100%; - margin-bottom: 0.5rem; -`; - -export const CommentBlock = styled.div` - width: 100%; - display: flex; - flex-direction: column; - gap: 0.5rem; - align-items: start; - justify-content: flex-start; - overflow: hidden; -`; - -export const AuthorName = styled.div` - display: inline !important; - ${({ theme }) => theme.typography.captionBold} -`; - -export const CommentBubble = styled.div` - display: flex; - flex-direction: column; - gap: 0.25rem; - padding: 0.75rem; - background-color: #f0f0f0; - border-top-right-radius: 0.75rem; - border-bottom-right-radius: 0.75rem; - border-bottom-left-radius: 0.75rem; -`; - -const encodeHexColor = (hex: string) => hex.replace('#', '%23'); - -const getCommentComposeBarBackground = (theme: DefaultTheme) => - ` - -`; - -export const CommentContainer = styled.div` - display: flex; - width: 100%; - color: black; - padding-top: 1rem; -`; - -export const ReplyContainer = styled.div` - display: flex; - color: black; - padding-top: 1rem; - padding-left: 2.5rem; -`; - -export const CommentComposeBar = styled(UICommentComposeBar)` - border: none; - padding: 0.5rem 0 1rem; - background-repeat: no-repeat; - background-image: ${({ theme }) => - `url('data:image/svg+xml;utf8,${getCommentComposeBarBackground(theme)}')`}; -`; - -export const Content = styled.div` - width: 100%; -`; - -export const CommentHeader = styled.div` - word-break: break-all; - margin-bottom: 0.3125rem; -`; - -export const CommentContent = styled.div` - overflow-wrap: anywhere !important; - word-break: break-word; - color: ${({ theme }) => theme.palette.neutral.main}; - background-color: ${({ theme }) => theme.palette.base.shade4}; - border-radius: 0 0.75rem 0.75rem 0.75rem; - padding: 0.75rem; - display: inline-block; - white-space: pre-wrap; - ${({ theme }) => theme.typography.body} -`; - -export const CommentInfo = styled.div` - margin-left: 0.5rem; -`; - -export const CommentDate = styled.span` - margin-left: 0.3125rem; - color: ${({ theme }) => theme.palette.neutral.shade1}; - ${({ theme }) => theme.typography.subTitle} -`; - -export const EditedMark = styled.span` - margin-left: 0.3125rem; - color: ${({ theme }) => theme.palette.neutral.shade1}; - ${({ theme }) => theme.typography.subTitle} - - &:before { - content: '('; - } - - &:after { - content: ')'; - } -`; - -export const ReadMoreButton = styled(SecondaryButton)` - color: ${({ theme }) => theme.palette.primary.main}; - padding: 0 0 0 0.25rem; - - &:hover { - background: none; - text-decoration: underline; - } -`; - -export const InteractionWrapper = styled.div` - display: flex; - align-items: center; - gap: 0.5rem; -`; - -export const ReactionsWrapper = styled.div` - display: flex; - align-items: center; - gap: 0.5rem; -`; - -export const InteractionBar = styled.div` - display: flex; - justify-content: space-between; - width: 100%; - gap: 0.5rem; - align-items: center; - padding: 0.125rem 0; -`; - -export const DeletedCommentContainer = styled.div` - display: flex; - align-items: center; - color: ${({ theme }) => theme.palette.base.shade3}; - padding: 1rem 0; - - &.reply { - display: inline-flex; - margin-left: 2.5rem; - background: ${({ theme }) => theme.palette.base.shade4}; - color: ${({ theme }) => theme.palette.base.shade2}; - border-radius: 0.25rem; - margin: 0.875rem 0; - padding: 0.25rem 0.5rem 0.125rem 0; - } -`; - -export const DeletedReplyContainer = styled.div` - display: inline-flex; - align-items: center; - margin: 0.4375rem 0 0.4375rem 2.5rem; - background: ${({ theme }) => theme.palette.base.shade4}; - color: ${({ theme }) => theme.palette.base.shade2}; - border-radius: 0.25rem; - padding: 0.25rem 0.5rem 0.125rem 0; -`; - -export const DeletedIcon = styled(MinusCircle)<{ icon?: ReactNode }>` - font-size: 1.125rem; - width: 1.25rem; - height: 1.25rem; -`; - -export const IconContainer = styled.div` - display: flex; - padding-right: 0.625rem; - - &.reply { - padding: 0.25rem 0.625rem 0.25rem 0.25rem; - } -`; - -export const MessageContainer = styled.div` - display: flex; - align-items: center; -`; - -export const Text = styled.span` - font-size: 0.875rem; -`; - -export const ReplyIcon = styled(Reply)<{ icon?: ReactNode }>` - font-size: 1rem; - margin-right: 0.3125rem; -`; - -export const ReplyButton = styled(SecondaryButton)``; - -export const CommentEditContainer = styled.div` - display: flex; - flex-direction: column; -`; - -export const ButtonContainer = styled.div` - display: flex; - justify-content: flex-end; - margin-top: 0.5rem; - - > * { - margin-left: 0.5rem; - } -`; - -export const CommentEditTextarea = styled(InputText).attrs({ - rows: 1, - maxRows: 15, -})` - ${({ theme }) => theme.typography.body} - border-radius: 0 0.75rem 0.75rem 0.75rem; - height: 7.5rem; - padding: 0.75rem; -`; - -export const ReactionListButtonContainer = styled.div` - display: flex; - align-items: center; - justify-content: center; - gap: 0.5rem; - color: ${({ theme }) => theme.v4.colors.base.shade2}; -`; - -export const ReactionsListButtonWrapper = styled.div` - display: flex; - align-items: center; - flex-shrink: 1; -`; - -export const ReactionIcon = styled.div` - display: flex; - align-items: center; - justify-content: center; - width: 1.25rem; - height: 1.25rem; - border-radius: 50%; - margin-left: -0.25rem; - - &:first-child { - margin-left: 0; - } -`; diff --git a/src/v4/social/internal-components/CommentComposeBar/CommentComposeBar.module.css b/src/v4/social/internal-components/CommentComposeBar/CommentComposeBar.module.css new file mode 100644 index 000000000..df0923e47 --- /dev/null +++ b/src/v4/social/internal-components/CommentComposeBar/CommentComposeBar.module.css @@ -0,0 +1,62 @@ +.avatar { + margin-right: 8px; +} + +.replyContainer { + display: flex; + padding: 10px 12px 10px 16px; + align-items: center; + justify-content: space-between; + gap: 12px; + background: var(--asc-color-base-shade4); + transform: translateY(100%); + transition: transform 0.5s ease-in-out; +} + +.replyingToText { + color: var(--asc-color-base-shade2); + font-size: var(--asc-text-font-size-sm); + font-weight: var(--asc-text-font-weight-normal); + line-height: var(--asc-line-height-sm); +} + +.replyingToUsername { + color: var(--asc-color-base-shade2); + font-size: var(--asc-text-font-size-sm); + font-weight: var(--asc-text-font-weight-bold); + line-height: var(--asc-line-height-sm); +} + +.animatedReplyContainer { + transform: translateY(0%); +} + +.commentComposeBarContainer { + width: 100%; + background: var(--asc-color-base-background); + display: flex; + gap: var(--asc-spacing-s1); + align-items: center; +} + +.commentComposeBarInput { + outline: none; + flex-grow: 1; + font: inherit; + font-size: 14px; + resize: vertical; + border-radius: 1.25rem; + color: var(--asc-color-base-shade2); + background-color: var(--asc-color-base-shade4); + border-radius: 20px; +} + +.addCommentButton { + color: var(--asc-color-primary-default) !important; + cursor: pointer !important; + padding: 0.625rem; + background-color: transparent; + border: none; + font-size: var(--asc-text-font-size-sm); + font-weight: var(--asc-text-font-weight-bold); +} \ No newline at end of file diff --git a/src/v4/social/internal-components/CommentComposeBar/CommentComposeBar.tsx b/src/v4/social/internal-components/CommentComposeBar/CommentComposeBar.tsx index fb353edfd..73aa98d1e 100644 --- a/src/v4/social/internal-components/CommentComposeBar/CommentComposeBar.tsx +++ b/src/v4/social/internal-components/CommentComposeBar/CommentComposeBar.tsx @@ -4,19 +4,17 @@ import useMention from '~/v4/chat/hooks/useMention'; import { Mentionees, Metadata } from '~/v4/helpers/utils'; import useSDK from '~/core/hooks/useSDK'; -import { LoadingIndicator } from '~/core/components/ProgressBar/styles'; -import { FormattedMessage, useIntl } from 'react-intl'; -import { - AddCommentButton, - Avatar, - CommentComposeBarContainer, - CommentComposeBarInput, -} from './styles'; +import { FormattedMessage, useIntl } from 'react-intl'; +import styles from './CommentComposeBar.module.css'; import { backgroundImage as UserImage } from '~/icons/User'; import useImage from '~/core/hooks/useImage'; import { useConfirmContext } from '~/core/providers/ConfirmProvider'; +import InputText from '~/v4/core/components/InputText'; +import { Avatar } from '~/v4/core/components'; +import Button from '~/v4/core/components/Button/Button'; +import { LoadingIndicator } from '~/v4/social/internal-components/LoadingIndicator'; const TOTAL_MENTIONEES_LIMIT = 30; const COMMENT_LENGTH_LIMIT = 50000; @@ -28,15 +26,9 @@ interface CommentComposeBarProps { onSubmit: (text: string, mentionees: Mentionees, metadata: Metadata) => void; onCancelReply?: () => void; isReplying?: boolean; - style?: React.CSSProperties; } -export const CommentComposeBar = ({ - style, - userToReply, - onSubmit, - targetId, -}: CommentComposeBarProps) => { +export const CommentComposeBar = ({ userToReply, onSubmit, targetId }: CommentComposeBarProps) => { const { currentUserId } = useSDK(); const user = useUser(currentUserId); const avatarFileUrl = useImage({ fileId: user?.avatarFileId, imageSize: 'small' }); @@ -86,9 +78,9 @@ export const CommentComposeBar = ({ : formatMessage({ id: 'CommentComposeBar.saySomething' }); return ( - +
- onChange?.(data)} onKeyPress={(e) => e.key === 'Enter' && addComment()} + className={styles.commentComposeBarInput} /> - {formatMessage({ id: 'storyViewer.commentComposeBar.submit' })} - - + +
); }; diff --git a/src/v4/social/internal-components/CommentComposeBar/index.tsx b/src/v4/social/internal-components/CommentComposeBar/index.ts similarity index 100% rename from src/v4/social/internal-components/CommentComposeBar/index.tsx rename to src/v4/social/internal-components/CommentComposeBar/index.ts diff --git a/src/v4/social/internal-components/CommentComposeBar/styles.tsx b/src/v4/social/internal-components/CommentComposeBar/styles.tsx deleted file mode 100644 index 616f0b310..000000000 --- a/src/v4/social/internal-components/CommentComposeBar/styles.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import styled from 'styled-components'; -import InputText from '~/core/components/InputText'; -import { SecondaryButton } from '~/core/components/Button'; - -import UIAvatar from '~/core/components/Avatar'; - -export const Avatar = styled(UIAvatar)` - margin-right: 8px; -`; - -export const ReplyContainer = styled.div` - display: flex; - padding: 10px 12px 10px 16px; - align-items: center; - justify-content: space-between; - gap: 12px; - background: ${({ theme }) => theme.palette.base.shade4}; - transform: translateY(100%); - transition: transform 0.5s ease-in-out; /* Adjust the duration and timing function as needed */ -`; - -export const ReplyingToText = styled.span` - ${({ theme }) => theme.typography.body}; - color: ${({ theme }) => theme.palette.base.shade2}; -`; - -export const ReplyingToUsername = styled.span` - ${({ theme }) => theme.typography.bodyBold}; - color: ${({ theme }) => theme.palette.base.shade2}; -`; - -export const AnimatedReplyContainer = styled(ReplyContainer)` - transform: translateY(0%); -`; - -export const CommentComposeBarContainer = styled.div` - width: 100%; - padding: 1rem; - background: ${({ theme }) => theme.palette.system.background}; - display: flex; - align-items: center; -`; - -export const CommentComposeBarInput = styled(InputText).attrs<{ rows?: number; maxRows?: number }>({ - rows: 1, - maxRows: 15, -})` - outline: none; - flex-grow: 1; - font: inherit; - font-size: 14px; - resize: vertical; - border-radius: 1.25rem; - color: ${({ theme }) => theme.palette.base.shade2}; - background-color: ${({ theme }) => theme.palette.base.shade4}; - border-radius: 20px; -`; - -export const AddCommentButton = styled(SecondaryButton)` - color: ${({ theme }) => theme.palette.primary.main} !important; - cursor: pointer !important; - padding: 0.625rem; - margin-left: 0.625rem; -`; diff --git a/src/v4/social/internal-components/CommentList/CommentList.module.css b/src/v4/social/internal-components/CommentList/CommentList.module.css new file mode 100644 index 000000000..1b8bce01b --- /dev/null +++ b/src/v4/social/internal-components/CommentList/CommentList.module.css @@ -0,0 +1,28 @@ +.commentListContainer { + max-height: calc(100vh - 16rem); + overflow-y: auto; + overflow: hidden; +} + +.tabIcon { + fill: var(--asc-color-base-shade2); + height: 1rem; + width: 1rem; +} + +.tabIconContainer { + display: flex; + margin-right: 8px; +} + +.noCommentsContainer { + font-size: var(--asc-text-font-size-md); + font-weight: var(--asc-text-font-weight-normal); + line-height: var(--asc-line-height-lg); + color: var(--asc-color-base-shade2); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; +} diff --git a/src/v4/social/internal-components/CommentList/CommentList.tsx b/src/v4/social/internal-components/CommentList/CommentList.tsx index e01740ac8..efa7266ca 100644 --- a/src/v4/social/internal-components/CommentList/CommentList.tsx +++ b/src/v4/social/internal-components/CommentList/CommentList.tsx @@ -1,29 +1,21 @@ import React, { memo, useState } from 'react'; import { useIntl } from 'react-intl'; - import useCommentsCollection from '~/social/hooks/collections/useCommentsCollection'; -import LoadMoreWrapper from '~/social/components/LoadMoreWrapper'; -import { - NoCommentsContainer, - TabIcon, - TabIconContainer, -} from '~/social/components/CommentList/styles'; -import { MobileSheet } from '../Comment/styles'; -import Comment from '../Comment'; -import { ReactionList } from '../../components'; +import { Comment } from '../Comment'; +import styles from './CommentList.module.css'; +import { LoadMoreWrapper } from '~/v4/core/components/LoadMoreWrapper'; +import { ExpandIcon } from '~/v4/social/icons'; interface CommentListProps { parentId?: string; referenceId?: string; referenceType: Amity.CommentReferenceType; - // filterByParentId?: boolean; readonly?: boolean; isExpanded?: boolean; limit?: number; onClickReply?: (comment: Amity.Comment) => void; style?: React.CSSProperties; - onClickReaction?: (commentId: string) => void; shouldAllowInteraction?: boolean; } @@ -32,68 +24,45 @@ export const CommentList = ({ referenceId, referenceType, limit = 5, - // TODO: breaking change - // filterByParentId = false, readonly = false, isExpanded = true, onClickReply, style, shouldAllowInteraction, }: CommentListProps) => { - const [selectedCommentId, setSelectedCommentId] = useState(''); const { comments, hasMore, loadMore } = useCommentsCollection({ parentId, referenceId, referenceType, limit, }); - const { formatMessage } = useIntl(); - const handleReactionClick = (commentId: string) => { - setSelectedCommentId(commentId); - }; - - const handleReactionListClose = () => { - setSelectedCommentId(''); - }; - const isReplyComment = !!parentId; - const commentCount = comments?.length; const loadMoreText = isReplyComment - ? formatMessage( - { - id: 'collapsible.viewMoreReplies', - }, - { count: commentCount }, - ) + ? formatMessage({ id: 'collapsible.viewMoreReplies' }, { count: commentCount }) : formatMessage({ id: 'collapsible.viewMoreComments' }); const prependIcon = isReplyComment ? ( - - - +
+ +
) : null; if (comments?.length === 0 && referenceType === 'story') { return ( - +
{formatMessage({ id: 'storyViewer.commentSheet.empty' })} - +
); } if (comments?.length === 0) return null; return ( -
+
onClickReply?.(comment as Amity.Comment)} style={style} - onClickReactionList={() => handleReactionClick(comment.commentId)} shouldAllowInteraction={shouldAllowInteraction} /> ); })} /> - {selectedCommentId && ( - - - - - - - - - )}
); }; diff --git a/src/v4/social/internal-components/LoadingIndicator/LoadingIndicator.module.css b/src/v4/social/internal-components/LoadingIndicator/LoadingIndicator.module.css new file mode 100644 index 000000000..ad350cfd2 --- /dev/null +++ b/src/v4/social/internal-components/LoadingIndicator/LoadingIndicator.module.css @@ -0,0 +1,20 @@ +.loadingIndicator { + position: absolute; + left: 0; + top: 0; + bottom: 0; + z-index: 1; + opacity: 0.75; + transition: width 0.4s ease; + animation: glow 0.8s alternate infinite linear; + background: var(--asc-color-base-shade4); +} + +@keyframes glow { + from { + opacity: 0.5; + } + to { + opacity: 0.75; + } +} diff --git a/src/v4/social/internal-components/LoadingIndicator/LoadingIndicator.tsx b/src/v4/social/internal-components/LoadingIndicator/LoadingIndicator.tsx new file mode 100644 index 000000000..64d93ce2d --- /dev/null +++ b/src/v4/social/internal-components/LoadingIndicator/LoadingIndicator.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import clsx from 'clsx'; +import styles from './LoadingIndicator.module.css'; + +interface LoadingIndicatorProps { + progress?: number; +} + +export const LoadingIndicator: React.FC = ({ progress }) => { + const indicatorStyle = { + width: `${progress || 0}%`, + }; + + return
; +}; diff --git a/src/v4/social/internal-components/LoadingIndicator/index.ts b/src/v4/social/internal-components/LoadingIndicator/index.ts new file mode 100644 index 000000000..7b559ef06 --- /dev/null +++ b/src/v4/social/internal-components/LoadingIndicator/index.ts @@ -0,0 +1 @@ +export { LoadingIndicator } from './LoadingIndicator'; diff --git a/src/v4/social/internal-components/Playground/Playground.tsx b/src/v4/social/internal-components/Playground/Playground.tsx deleted file mode 100644 index f11e65db1..000000000 --- a/src/v4/social/internal-components/Playground/Playground.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import React from 'react'; - -export const Playground = () => { - return
Playground
; -}; diff --git a/src/v4/social/internal-components/Playground/index.ts b/src/v4/social/internal-components/Playground/index.ts deleted file mode 100644 index 670f784fa..000000000 --- a/src/v4/social/internal-components/Playground/index.ts +++ /dev/null @@ -1 +0,0 @@ -import { Playground } from './Playground'; diff --git a/src/v4/social/internal-components/StoryCommentComposeBar/StoryCommentComposeBar.module.css b/src/v4/social/internal-components/StoryCommentComposeBar/StoryCommentComposeBar.module.css new file mode 100644 index 000000000..df442ae37 --- /dev/null +++ b/src/v4/social/internal-components/StoryCommentComposeBar/StoryCommentComposeBar.module.css @@ -0,0 +1,44 @@ +.storyCommentComposerBarContainer { + padding: 0 1rem; + } + + .disabledCommentComposerBarContainer { + display: flex; + justify-content: center; + align-items: center; + gap: 0.5rem; + padding: 0.625rem 1rem; + border-top: 1px solid #e3e4e8; + color: var(--asc-color-base-shade2); + } + + .replyingBlock { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.625rem 1rem; + background-color: var(--asc-color-base-shade4); + margin-bottom: 1rem; + } + + .replyingToText { + font-size: var(--asc-text-font-size-sm); + font-weight: var(--asc-text-font-weight-normal); + line-height: var(--asc-line-height-sm); + color: var(--asc-color-base-shade1); + } + + .replyingToUsername { + font-weight: var(--asc-text-font-weight-bold); + } + + .closeButton { + fill: var(--asc-color-base-shade2); + width: 1.25rem; + height: 1.25rem; + cursor: pointer; + } + + .commentComposeBar { + background: red; + } \ No newline at end of file diff --git a/src/v4/social/internal-components/StoryCommentComposeBar/StoryCommentComposeBar.tsx b/src/v4/social/internal-components/StoryCommentComposeBar/StoryCommentComposeBar.tsx index 79b3aecfc..3b3c5878d 100644 --- a/src/v4/social/internal-components/StoryCommentComposeBar/StoryCommentComposeBar.tsx +++ b/src/v4/social/internal-components/StoryCommentComposeBar/StoryCommentComposeBar.tsx @@ -3,10 +3,8 @@ import React from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; import { Mentionees, Metadata } from '~/v4/helpers/utils'; import { Close, Lock2Icon } from '~/icons'; -import { ReplyingBlock } from '../StoryViewer/styles'; -import { ReplyingToText, ReplyingToUsername } from '../CommentComposeBar/styles'; import { CommentComposeBar } from '../CommentComposeBar'; -import { StoryDisabledCommentComposerBarContainer } from './styles'; +import styles from './StoryCommentComposeBar.module.css'; interface StoryCommentComposeBarProps { communityId: string; @@ -16,7 +14,6 @@ interface StoryCommentComposeBarProps { shouldAllowCreation?: boolean; replyTo?: Amity.Comment | null; onCancelReply?: () => void; - referenceType?: string; referenceId?: string; style?: React.CSSProperties; } @@ -28,11 +25,10 @@ export const StoryCommentComposeBar = ({ isReplying, replyTo, onCancelReply, - referenceType, referenceId, - style, }: StoryCommentComposeBarProps) => { const { formatMessage } = useIntl(); + const handleAddComment = async ( commentText: string, mentionees: Mentionees, @@ -57,9 +53,7 @@ export const StoryCommentComposeBar = ({ await CommentRepository.createComment({ referenceType: replyTo?.referenceType as Amity.CommentReferenceType, referenceId: replyTo?.referenceId as string, - data: { - text: replyCommentText, - }, + data: { text: replyCommentText }, parentId: replyTo?.commentId, metadata, mentionees: mentionees as Amity.UserMention[], @@ -70,20 +64,18 @@ export const StoryCommentComposeBar = ({ return ( <> {isReplying && ( - - +
+
{' '} - {replyTo?.userId} - - - + {replyTo?.userId} +
+ +
)} - {!isReplying ? ( handleAddComment(text, mentionees, metadata)} - style={style} /> ) : ( )} @@ -102,9 +93,10 @@ export const StoryCommentComposeBar = ({ if (isJoined && shouldAllowCreation) { return ( - - {formatMessage({ id: 'storyViewer.commentSheet.disabled' })} - +
+ + {formatMessage({ id: 'storyViewer.commentSheet.disabled' })} +
); } diff --git a/src/v4/social/internal-components/StoryCommentComposeBar/styles.tsx b/src/v4/social/internal-components/StoryCommentComposeBar/styles.tsx deleted file mode 100644 index ae8d01998..000000000 --- a/src/v4/social/internal-components/StoryCommentComposeBar/styles.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import styled from 'styled-components'; -import { Close } from '~/icons'; - -export const StoryCommentComposerBarContainer = styled.div` - padding: 0 1rem; -`; - -export const StoryDisabledCommentComposerBarContainer = styled.div` - ${({ theme }) => theme.typography.body}; - color: ${({ theme }) => theme.palette.base.shade2}; - display: flex; - justify-content: center; - align-items: center; - gap: 0.5rem; - padding: 0.625rem 1rem; - border-top: 1px solid #e3e4e8; -`; - -export const CloseButton = styled(Close)` - fill: ${({ theme }) => theme.palette.base.shade2}; - width: 20px; - height: 20px; -`; diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index a56ec0c8a..7372cfdbc 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -4,30 +4,27 @@ import { Tester } from 'react-insta-stories/dist/interfaces'; import styles from './Renderers.module.css'; import { useNavigation } from '~/social/providers/NavigationProvider'; import useImage from '~/core/hooks/useImage'; + import { checkStoryPermission, formatTimeAgo } from '~/utils'; import useSDK from '~/core/hooks/useSDK'; -import { FormattedMessage, useIntl } from 'react-intl'; +import { useIntl } from 'react-intl'; import { LIKE_REACTION_KEY } from '~/constants'; import Truncate from 'react-truncate-markup'; -import { HyperLinkButtonContainer } from './styles'; import { CustomRenderer } from './types'; -import { BottomSheet } from '~/v4/core/components'; -import { MobileSheet } from '~/v4/core/components/BottomSheet/styles'; -import { - MobileActionSheetContent, - MobileSheetHeader, - StoryActionItem, - StoryActionItemText, -} from '../styles'; + import { CommentTray } from '~/v4/social/components'; import { HyperLink } from '~/v4/social/elements/HyperLink'; import Footer from './Wrappers/Footer'; import Header from './Wrappers/Header'; import { PageTypes } from '~/social/constants'; +import { motion, PanInfo, useAnimationControls } from 'framer-motion'; import useUser from '~/core/hooks/useUser'; +import { BottomSheet } from '~/v4/core/components/BottomSheet'; +import { Typography } from '~/v4/core/components'; +import { Button } from '~/v4/core/components/Button'; export const renderer: CustomRenderer = ({ story, action, config }) => { const { formatMessage } = useIntl(); @@ -63,7 +60,7 @@ export const renderer: CustomRenderer = ({ story, action, config }) => { imageSize: 'small', }); - const user = useUser(); + const user = useUser(client?.userId); const heading =
{community?.displayName}
; const subheading = @@ -103,6 +100,37 @@ export const renderer: CustomRenderer = ({ story, action, config }) => { const targetRootId = 'asc-uikit-stories-viewer'; + const controls = useAnimationControls(); + + const handleSwipeDown = () => { + controls + .start({ + y: '100%', + transition: { duration: 0.3, ease: 'easeOut' }, + }) + .then(() => { + if (page.type === PageTypes.ViewStory && page.storyType === 'globalFeed') { + onChangePage(PageTypes.NewsFeed); + } else { + onClickCommunity(community?.communityId as string); + } + }); + }; + + const handleDragStart = () => { + action('pause', true); + }; + + const handleDragEnd = (_event: MouseEvent | TouchEvent | PointerEvent, info: PanInfo) => { + if (info.offset.y > 100) { + handleSwipeDown(); + } else { + controls.start({ y: 0, transition: { duration: 0.3, ease: 'easeOut' } }).then(() => { + action('play', true); + }); + } + }; + useEffect(() => { if (isPaused || isOpenBottomSheet || isOpenCommentSheet) { action('pause', true); @@ -133,7 +161,16 @@ export const renderer: CustomRenderer = ({ story, action, config }) => { }, []); return ( -
+
{ }} addStoryButton={addStoryButton} /> + { onLoad={imageLoaded} alt="Story Image" /> + {!loaded && (
{loader ||
loading...
}
)} + - - - - {actions?.map((bottomSheetAction) => ( - { - bottomSheetAction.action(); - closeBottomSheet(); - }} - > - {bottomSheetAction.icon} - - {formatMessage({ id: bottomSheetAction.name })} - - - ))} - - - + {actions?.map((bottomSheetAction) => ( + + ))} + - - - - - - - - - + + {story.items?.[0]?.data?.url && ( - +
{ {story.items?.[0]?.data?.customText || story.items?.[0].data.url} - +
)} +
{ onClickComment={openCommentSheet} showImpression={isCreator || haveStoryPermission} /> -
+ ); }; diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css b/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css index 8f70388ca..27f4f8618 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css @@ -1,234 +1,244 @@ .iconButton { - position: absolute; - width: 2rem; - height: 2rem; - background-color: rgba(0, 0, 0, 0.5); - border-radius: 50%; - border: none; - top: 6rem; - left: 1.25rem; - z-index: 9999; - cursor: pointer; - } - - .rendererContainer { - position: relative; - width: 100%; - height: 100%; - } - - .storyVideo { - width: 100%; - height: 100%; - object-fit: contain; - } - - .muteCircleIcon { - width: 100%; - height: 100%; - } - - .unmuteCircleIcon { - width: 100%; - height: 100%; - } - - .loadingOverlay { - position: absolute; - left: 0; - top: 0; - background: rgba(0, 0, 0, 0.9); - z-index: 9; - display: flex; - justify-content: center; - align-items: center; - color: #ccc; - } - - .storyImage { - width: auto; - max-width: 100%; - max-height: 100%; - margin: auto; - } - - .playStoryButton { - color: #ffffff; - cursor: pointer; - } - - .pauseStoryButton { - color: #ffffff; - cursor: pointer; - } - - .closeButton { - color: #ffffff; - width: 1.25rem; - height: 1.25rem; - cursor: pointer; - } - - .verifiedBadge { - color: #ffffff; - } - - .dotsButton { - width: 1.5rem; - height: 1.5rem; - cursor: pointer; - color: #ffffff; - } - - .viewStoryInfoContainer { - display: flex; - flex-direction: column; - justify-content: flex-start; - width: 100%; - } - - .viewStoryCompostBarContainer { - width: 100%; - display: flex; - position: absolute; - justify-content: space-between; - align-items: center; - height: 3.5rem; - padding: 0.75rem; - background-color: #000; - bottom: 0; - } - - .viewStoryCompostBarViewIconContainer { - display: flex; - align-items: center; - justify-content: space-between; - color: #fff; - gap: 0.25rem; - } - - .viewStoryCompostBarEngagementContainer { - display: flex; - align-items: center; - justify-content: space-between; - color: #fff; - gap: 0.75rem; - } - - .viewStoryCompostBarEngagementIconContainer { - display: flex; - align-items: center; - justify-content: space-between; - color: #fff; - gap: 0.25rem; - border-radius: 50%; - padding: 0.5rem 0.625rem; - background-color: #292b32; - } - - .storyContent { - flex: 1; - } - - .header { - height: 5rem; - padding: 0.75rem 1rem 0.625rem 1rem; - } - - .viewStoryContainer { - position: relative; - width: 100%; - height: 100%; - display: flex; - flex-direction: column; - background-color: black; - } - - .viewStoryHeaderContainer { - z-index: 9999; - position: absolute; - width: 100%; - display: flex; - justify-content: space-between; - align-items: center; - flex-direction: column; - padding: 1.5rem 1rem 0.625rem 1rem; - gap: 0.5rem; - } - - .avatarContainer { - position: relative; - width: 2.5rem; - height: 2.5rem; - border-radius: 50%; - flex-shrink: 0; - } - - .addStoryButton { - position: absolute; - bottom: 0; - right: 0; - } - - .addStoryButton:hover { - cursor: pointer; - } - - .viewStoryHeaderListActionsContainer { - display: flex; - gap: 1.25rem; - justify-content: flex-end; - align-items: center; - } - - .viewStoryHeadingInfoContainer { - display: flex; - justify-content: space-between; - width: 100%; - gap: 0.75rem; - align-items: center; - } - - .viewStoryHeading { - cursor: pointer; - display: flex; - gap: 0.25rem; - color: #fff; - font-size: 0.938rem; - font-style: normal; - font-weight: 600; - line-height: 1.25rem; - letter-spacing: -0.24px; - margin-right: 0.25rem; - align-items: center; - } - - .viewStoryHeadingTitle { - width: auto; - max-width: 11.688rem; - } - - .viewStorySubHeading { - display: inline-flex; - gap: 0.25rem; - margin-bottom: 0.25rem; - color: #fff; - font-size: 0.813rem; - font-style: normal; - font-weight: 400; - line-height: 1.25rem; - letter-spacing: -0.1px; - } - - .story { - display: flex; - position: relative; - overflow: hidden; - } - - .storyContent { - width: auto; - max-width: 100%; - max-height: 100%; - margin: auto; - } \ No newline at end of file + position: absolute; + width: 2rem; + height: 2rem; + background-color: rgba(0, 0, 0, 0.5); + border-radius: 50%; + border: none; + top: 6rem; + left: 1.25rem; + z-index: 9999; + cursor: pointer; +} + +.rendererContainer { + position: relative; + width: 100%; + height: 100%; +} + +.storyVideo { + width: 100%; + height: 100%; + object-fit: contain; +} + +.muteCircleIcon { + width: 100%; + height: 100%; +} + +.unmuteCircleIcon { + width: 100%; + height: 100%; +} + +.loadingOverlay { + position: absolute; + left: 0; + top: 0; + background: rgba(0, 0, 0, 0.9); + z-index: 9; + display: flex; + justify-content: center; + align-items: center; + color: #ccc; +} + +.storyImage { + width: auto; + max-width: 100%; + max-height: 100%; + margin: auto; +} + +.playStoryButton { + color: #ffffff; + cursor: pointer; +} + +.pauseStoryButton { + color: #ffffff; + cursor: pointer; +} + +.closeButton { + color: #ffffff; + width: 1.25rem; + height: 1.25rem; + cursor: pointer; +} + +.verifiedBadge { + color: #ffffff; +} + +.dotsButton { + width: 1.5rem; + height: 1.5rem; + cursor: pointer; + color: #ffffff; +} + +.viewStoryInfoContainer { + display: flex; + flex-direction: column; + justify-content: flex-start; + width: 100%; +} + +.viewStoryCompostBarContainer { + width: 100%; + display: flex; + position: absolute; + justify-content: space-between; + align-items: center; + height: 3.5rem; + padding: 0.75rem; + background-color: #000; + bottom: 0; +} + +.viewStoryCompostBarViewIconContainer { + display: flex; + align-items: center; + justify-content: space-between; + color: #fff; + gap: 0.25rem; +} + +.viewStoryCompostBarEngagementContainer { + display: flex; + align-items: center; + justify-content: space-between; + color: #fff; + gap: 0.75rem; +} + +.viewStoryCompostBarEngagementIconContainer { + display: flex; + align-items: center; + justify-content: space-between; + color: #fff; + gap: 0.25rem; + border-radius: 50%; + padding: 0.5rem 0.625rem; + background-color: #292b32; +} + +.storyContent { + flex: 1; +} + +.header { + height: 5rem; + padding: 0.75rem 1rem 0.625rem 1rem; +} + +.viewStoryContainer { + position: relative; + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + background-color: black; +} + +.viewStoryHeaderContainer { + z-index: 9999; + position: absolute; + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + flex-direction: column; + padding: 1.5rem 1rem 0.625rem 1rem; + gap: 0.5rem; +} + +.avatarContainer { + position: relative; + width: 2.5rem; + height: 2.5rem; + border-radius: 50%; + flex-shrink: 0; +} + +.addStoryButton { + position: absolute; + bottom: 0; + right: 0; +} + +.addStoryButton:hover { + cursor: pointer; +} + +.viewStoryHeaderListActionsContainer { + display: flex; + gap: 1.25rem; + justify-content: flex-end; + align-items: center; +} + +.viewStoryHeadingInfoContainer { + display: flex; + justify-content: space-between; + width: 100%; + gap: 0.75rem; + align-items: center; +} + +.viewStoryHeading { + cursor: pointer; + display: flex; + gap: 0.25rem; + color: #fff; + font-size: 0.938rem; + font-style: normal; + font-weight: 600; + line-height: 1.25rem; + letter-spacing: -0.24px; + margin-right: 0.25rem; + align-items: center; +} + +.viewStoryHeadingTitle { + width: auto; + max-width: 11.688rem; +} + +.viewStorySubHeading { + display: inline-flex; + gap: 0.25rem; + margin-bottom: 0.25rem; + color: #fff; + font-size: 0.813rem; + font-style: normal; + font-weight: 400; + line-height: 1.25rem; + letter-spacing: -0.1px; +} + +.story { + display: flex; + position: relative; + overflow: hidden; +} + +.storyContent { + width: auto; + max-width: 100%; + max-height: 100%; + margin: auto; +} + +.actionButton { + display: inline-flex; + justify-content: flex-start; + text-align: left; + gap: var(--asc-spacing-s1); + border: none; + color: var(--asc-color-base-default); + background-color: var(--asc-color-base-background); +} diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx index 5e9f447f6..cff53dd2a 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx @@ -11,21 +11,18 @@ import useSDK from '~/core/hooks/useSDK'; import { LIKE_REACTION_KEY } from '~/constants'; import Truncate from 'react-truncate-markup'; import { CustomRenderer } from './types'; -import { HyperLinkButtonContainer, LoadingOverlay, RendererContainer, StoryVideo } from './styles'; +import { LoadingOverlay, StoryVideo } from './styles'; import { SpeakerButton } from '~/v4/social/elements'; import Header from './Wrappers/Header'; -import { BottomSheet } from '~/v4/core/components'; -import { MobileSheet } from '~/v4/core/components/BottomSheet/styles'; -import { - MobileActionSheetContent, - MobileSheetHeader, - StoryActionItem, - StoryActionItemText, -} from '../styles'; +import { BottomSheet, Button, Typography } from '~/v4/core/components'; + import { CommentTray } from '~/v4/social/components'; import { HyperLink } from '~/v4/social/elements/HyperLink'; import Footer from './Wrappers/Footer'; import { PageTypes } from '~/social/constants'; +import { motion, PanInfo, useAnimationControls } from 'framer-motion'; + +import rendererStyles from './Renderers.module.css'; import useUser from '~/core/hooks/useUser'; export const renderer: CustomRenderer = ({ story, action, config, messageHandler }) => { @@ -38,7 +35,7 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler const [isOpenCommentSheet, setIsOpenCommentSheet] = useState(false); const { width, height, loader, storyStyles } = config; const { client } = useSDK(); - const user = useUser(client.currentUserId); + const user = useUser(client?.userId); const isLiked = !!(story && story.myReactions && story.myReactions.includes(LIKE_REACTION_KEY)); const totalLikes = story?.reactions[LIKE_REACTION_KEY] || 0; @@ -81,11 +78,12 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler const isCreator = creator?.userId === user?.userId; const computedStyles = { - ...styles.storyContent, + ...storyContentStyles, ...(storyStyles || {}), }; const vid = useRef(null); + const controls = useAnimationControls(); const onWaiting = () => { action('pause', true); @@ -128,6 +126,37 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler const targetRootId = 'asc-uikit-stories-viewer'; + const handleSwipeDown = () => { + controls + .start({ + y: '100%', + transition: { duration: 0.3, ease: 'easeOut' }, + }) + .then(() => { + if (page.type === PageTypes.ViewStory && page.storyType === 'globalFeed') { + onChangePage(PageTypes.NewsFeed); + } else { + onClickCommunity(community?.communityId as string); + } + }); + }; + + const handleDragStart = () => { + setIsPaused(true); + action('pause', true); + }; + + const handleDragEnd = (_event: MouseEvent | TouchEvent | PointerEvent, info: PanInfo) => { + if (info.offset.y > 100) { + handleSwipeDown(); + } else { + controls.start({ y: 0, transition: { duration: 0.3, ease: 'easeOut' } }).then(() => { + setIsPaused(false); + action('play', true); + }); + } + }; + useEffect(() => { if (vid.current) { if (isPaused || isOpenBottomSheet || isOpenCommentSheet) { @@ -162,7 +191,16 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler }, []); return ( - + - - - - {actions?.map((bottomSheetAction) => ( - { - bottomSheetAction.action(); - closeBottomSheet(); - }} - > - {bottomSheetAction.icon} - - {formatMessage({ id: bottomSheetAction.name })} - - - ))} - - - + {actions?.map((bottomSheetAction) => ( + + ))} - - - - - - - - - - - + {story.items?.[0]?.data?.url && ( - +
{story.items?.[0].data?.customText || story.items?.[0].data.url} - +
)}
- + ); }; -const styles = { - storyContent: { - width: 'auto', - maxWidth: '100%', - maxHeight: '100%', - margin: 'auto', - position: 'relative' as const, - }, - videoContainer: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - }, +const storyContentStyles = { + width: 'auto', + maxWidth: '100%', + maxHeight: '100%', + margin: 'auto', + position: 'relative' as const, +}; + +const videoContainerStyles = { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', }; export const tester: Tester = (story) => { diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/styles.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/styles.tsx index e60ee3683..a6d7e2deb 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/styles.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/styles.tsx @@ -1,5 +1,4 @@ import styled from 'styled-components'; -import { POSITION_BOTTOM } from '~/helpers'; import { PauseIcon, @@ -11,7 +10,6 @@ import { MuteCircle, UnmuteCircle, } from '~/icons'; -import { LinkButtonContainer } from '~/v4/social/pages/DraftsPage/styles'; export const IconButton = styled.button` position: absolute; @@ -26,11 +24,6 @@ export const IconButton = styled.button` cursor: pointer; `; -export const HyperLinkButtonContainer = styled(LinkButtonContainer)` - bottom: 6rem; - z-index: 9999; -`; - export const RendererContainer = styled.div` display: flex; flex-direction: column; diff --git a/src/v4/social/internal-components/VideoPreview/VideoPreview.tsx b/src/v4/social/internal-components/VideoPreview/VideoPreview.tsx new file mode 100644 index 000000000..2449f0334 --- /dev/null +++ b/src/v4/social/internal-components/VideoPreview/VideoPreview.tsx @@ -0,0 +1,19 @@ +import React from 'react'; + +type BaseVideoPreviewProps = { + src: string; + mimeType?: string; + mediaFit?: string; +} & React.VideoHTMLAttributes; + +export const BaseVideoPreview = React.forwardRef( + ({ src, mimeType, mediaFit, ...props }, ref) => ( + + ), +); diff --git a/src/v4/social/internal-components/VideoPreview/index.ts b/src/v4/social/internal-components/VideoPreview/index.ts new file mode 100644 index 000000000..442a23464 --- /dev/null +++ b/src/v4/social/internal-components/VideoPreview/index.ts @@ -0,0 +1 @@ +export { BaseVideoPreview } from './VideoPreview'; diff --git a/src/v4/social/pages/Application/sdk.stories.tsx b/src/v4/social/pages/Application/sdk.stories.tsx index 24fd1f577..048b7785b 100644 --- a/src/v4/social/pages/Application/sdk.stories.tsx +++ b/src/v4/social/pages/Application/sdk.stories.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import UiKitApp from '.'; +import UiKitSocialApplication from '.'; export default { title: 'V4/Social', @@ -7,9 +7,9 @@ export default { export const SDKCommunityAppV4 = { render: (props) => { - return ; + return ; }, - name: 'ApplicationV4', + name: 'Application', args: { shouldHideExplore: false, diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.module.css b/src/v4/social/pages/DraftsPage/DraftsPage.module.css index c2783e65d..4fcab54f9 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.module.css +++ b/src/v4/social/pages/DraftsPage/DraftsPage.module.css @@ -64,11 +64,10 @@ .hyperLinkContainer { position: absolute; - background-color: transparent; padding: 1rem; display: flex; justify-content: center; - bottom: 2rem; + bottom: 6rem; left: 0; right: 0; } @@ -80,31 +79,21 @@ background: none; border: none; cursor: pointer; - color: var(--color-white); + color: var(--asc-color-white); font-size: 1.5rem; } -.header { - position: absolute; - display: flex; - align-items: center; - padding: 10px; - background-color: var(--color-white); - top: 0; -} - .progressBar { position: absolute; top: 0; left: 0; right: 0; height: 4px; - background-color: var(--color-gray); } .progressFill { height: 100%; - background-color: var(--color-primary); + background-color: var(--asc-color-primary-default); transition: width 0.1s linear; } @@ -117,12 +106,11 @@ .name { font-size: 16px; font-weight: bold; - color: var(--color-black); + color: var(--asc-color-black); } .description { font-size: 14px; - color: var(--color-gray); } .storyPreview { @@ -144,8 +132,8 @@ right: 0; display: flex; align-items: center; - padding: 10px; - background-color: rgba(0, 0, 0, 0.5); + padding: var(--asc-spacing-s1); + background-color: transparent; color: var(--color-white); } @@ -179,14 +167,6 @@ font-size: 14px; } -.hyperLinkContainer { - position: absolute; - bottom: 20px; - left: 20px; - right: 20px; - text-align: center; -} - .playPauseButton { position: absolute; top: 50%; diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index 9173ef398..486f5dfda 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -11,17 +11,22 @@ import { SubmitHandler } from 'react-hook-form'; import Truncate from 'react-truncate-markup'; import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; -import { AspectRatioButton, BackButton, HyperLinkButton, ShareStoryButton } from '../../elements'; -import { useStoryContext } from '../../providers/StoryProvider'; -import { StoryVideoPreview } from './styles'; +import { + AspectRatioButton, + BackButton, + HyperLinkButton, + ShareStoryButton, + HyperLink, +} from '~/v4/social/elements'; +import { useStoryContext } from '~/v4/social/providers/StoryProvider'; import { StoryRepository } from '@amityco/ts-sdk'; -import { HyperLink } from '../../elements/HyperLink'; -import { HyperLinkConfig } from '../../components'; +import { HyperLinkConfig } from '~/v4/social/components'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; import { useNotifications } from '~/v4/core/providers/NotificationProvider'; import { useNavigation } from '~/social/providers/NavigationProvider'; import { PageTypes } from '~/social/constants'; +import { BaseVideoPreview } from '../../internal-components/VideoPreview'; type AmityStoryMediaType = { type: 'image'; url: string } | { type: 'video'; url: string }; @@ -239,7 +244,7 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor />
) : mediaType?.type === 'video' ? ( - ` - width: 2rem; - height: 2rem; - border-radius: 50%; - background-color: ${({ backgroundColor }) => backgroundColor}; - border: none; - cursor: pointer; -`; - -export const ActionsContainer = styled.div` - display: flex; - gap: 0.75rem; -`; - -export const LinkButtonContainer = styled.div` - position: absolute; - bottom: 2rem; - left: 50%; - transform: translateX(-50%); -`; - -export const DraftImageContainer = styled.div<{ colors: { hex: string }[] }>` - width: 100%; - height: 100%; - position: relative; - border-radius: 0.75rem; - overflow: hidden; - background: linear-gradient( - 180deg, - ${(props) => props.colors?.[0]?.hex || '#000'} 0%, - ${(props) => props.colors?.[props?.colors?.length - 1]?.hex || '#000'} 100% - ); -`; - -export const DraftImage = styled.img<{ imageMode: 'fit' | 'fill'; colors: { hex: string }[] }>` - width: 100%; - height: 100%; - object-fit: ${(props) => (props?.imageMode === 'fit' ? 'contain' : 'cover')}; -`; - -export const StoryDraftFooter = styled.div` - width: 100%; - position: absolute; - bottom: -50px; - background-color: #000; - display: flex; - justify-content: flex-end; - padding: 0.75rem; - overflow: hidden; -`; - -export const ShareStoryButton = styled.button` - display: inline-flex; - height: 2.5rem; - padding: 0.375rem 0.5rem 0.25rem 0.25rem; - align-items: center; - justify-content: center; - flex-shrink: 0; - border-radius: 1.5rem; - background-color: #fff; - border: none; - color: #292b32; - font-size: 0.938rem; - font-style: normal; - font-weight: 600; - line-height: 1.25rem; - letter-spacing: -0.24px; - cursor: pointer; - gap: 0.5rem; -`; - -export const StoryVideoPreview = styled(VideoPreview)` - background-color: #000; -`; diff --git a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx index 1a22193ba..825226238 100644 --- a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx @@ -180,7 +180,7 @@ export const GlobalFeedStory: React.FC = () => { icon: ( ), diff --git a/src/v4/styles/global.css b/src/v4/styles/global.css index 32b8d9b2e..579508fd3 100644 --- a/src/v4/styles/global.css +++ b/src/v4/styles/global.css @@ -122,48 +122,3 @@ 0px 2px 4px 0px rgba(40, 41, 61, 0.04); } -/* - The [data-theme='light'] attribute selector targets elements with the data-theme attribute set to 'light'. - It is used to define styles specific to the light theme. -*/ -[data-theme='light'] { - --asc-color-base-default: #292b32; - --asc-color-base-shade1: #636878; - --asc-color-base-shade2: #898e9e; - --asc-color-base-shade3: #a5a9b5; - --asc-color-base-shade4: #ebecef; - --asc-color-base-shade5: #f9f9fa; - - --asc-color-base-inverse: #ffffff; - - --asc-color-secondary-default: #292b32; - --asc-color-secondary-shade1: #636878; - --asc-color-secondary-shade2: #898e9e; - --asc-color-secondary-shade3: #a5a9b5; - --asc-color-secondary-shade4: #ebecef; - --asc-color-secondary-shade5: #f9f9fa; -} - -/* - The [data-theme='dark'] attribute selector targets elements with the data-theme attribute set to 'dark'. - It is used to define styles specific to the dark theme. -*/ -[data-theme='dark'] { - --asc-color-base-background: #191919; - - --asc-color-base-inverse: #ffffff; - - --asc-color-base-default: #ebecef; - --asc-color-base-shade1: #a5a9b5; - --asc-color-base-shade2: #6e7487; - --asc-color-base-shade3: #40434e; - --asc-color-base-shade4: #292b32; - --asc-color-base-shade5: #191919; - - --asc-color-secondary-default: #ebecef; - --asc-color-secondary-shade1: #a5a9b5; - --asc-color-secondary-shade2: #6e7487; - --asc-color-secondary-shade3: #40434e; - --asc-color-secondary-shade4: #292b32; - --asc-color-secondary-shade5: #191919; -} \ No newline at end of file From 240f0e54bc3283529f2159a38a8a834700b71917 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Wed, 8 May 2024 10:40:29 +0700 Subject: [PATCH 065/300] fix: ASC-22263 - hyperlink background (#326) * fix: hyperlink background * fix: hyperlink icon color * fix: css (#327) --- .../elements/HyperLink/HyperLink.module.css | 40 +++++++++---------- .../pages/DraftsPage/DraftsPage.module.css | 6 +++ src/v4/social/pages/DraftsPage/DraftsPage.tsx | 1 + 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/src/v4/social/elements/HyperLink/HyperLink.module.css b/src/v4/social/elements/HyperLink/HyperLink.module.css index 6568d869b..2698895eb 100644 --- a/src/v4/social/elements/HyperLink/HyperLink.module.css +++ b/src/v4/social/elements/HyperLink/HyperLink.module.css @@ -1,21 +1,21 @@ .hyperlink { - border: 1px solid var(--asc-color-base-shade4); - color: var(--asc-color-secondary-default); - background: rgba(255, 255, 255, 0.80); - display: inline-flex; - align-items: center; - padding: var(--asc-spacing-s2) var(--asc-spacing-m1) var(--asc-spacing-s2) var(--asc-spacing-s2); - border-radius: var(--asc-border-radius-xxl); - gap: var(--asc-spacing-s1); - font-size: 0.9375rem; - font-style: normal; - font-weight: 600; - line-height: 1.25rem; - letter-spacing: -0.015rem; - } - - .hyperlinkIcon { - fill: var(--asc-color-primary-default); - width: 1.5rem; - height: 1.5rem; - } \ No newline at end of file + border: 1px solid var(--asc-color-base-shade4); + color: var(--asc-color-secondary-default); + background: rgba(255, 255, 255, 0.2); + display: inline-flex; + align-items: center; + padding: var(--asc-spacing-s2) var(--asc-spacing-m1) var(--asc-spacing-s2) var(--asc-spacing-s2); + border-radius: var(--asc-border-radius-xxl); + gap: var(--asc-spacing-s1); + font-size: 0.9375rem; + font-style: normal; + font-weight: 600; + line-height: 1.25rem; + letter-spacing: -0.015rem; +} + +.hyperlinkIcon { + color: var(--asc-color-primary-default); + width: 1.5rem; + height: 1.5rem; +} diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.module.css b/src/v4/social/pages/DraftsPage/DraftsPage.module.css index 4fcab54f9..280baf472 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.module.css +++ b/src/v4/social/pages/DraftsPage/DraftsPage.module.css @@ -62,6 +62,12 @@ width: 100%; } +.videoPreview { + width: 100%; + height: 100%; + background-color: var(--asc-color-black); +} + .hyperLinkContainer { position: absolute; padding: 1rem; diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index 486f5dfda..fff95e43f 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -245,6 +245,7 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor
) : mediaType?.type === 'video' ? ( Date: Thu, 9 May 2024 14:39:37 +0700 Subject: [PATCH 066/300] fix: ASC-22263 - hyperlink doesn't show in view story page (#328) * fix: hyperlink background * fix: hyperlink icon color * fix: css (#327) * fix: story * fix: css --- .../StoryViewer/Renderers/Image.tsx | 2 +- .../Renderers/Renderers.module.css | 80 +++++++++++-------- .../StoryViewer/Renderers/Video.tsx | 2 +- 3 files changed, 50 insertions(+), 34 deletions(-) diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index 7372cfdbc..7c7c314f3 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -251,7 +251,7 @@ export const renderer: CustomRenderer = ({ story, action, config }) => { {story.items?.[0]?.data?.url && ( -
+
{story.items?.[0]?.data?.url && ( -
+
Date: Fri, 10 May 2024 13:59:45 +0700 Subject: [PATCH 067/300] fix: ASC-22264 - reply comment margin (#329) * fix: hyperlink background * fix: hyperlink icon color * fix: css * fix: add scroller * fix: comment --- .../BottomSheet/BottomSheet.module.css | 14 ++++---- .../components/BottomSheet/BottomSheet.tsx | 15 ++++++-- .../Comment/Comment.module.css | 34 +++++++++++-------- .../Comment/CommentText.module.css | 3 -- .../Comment/UIComment.module.css | 25 ++++++-------- .../internal-components/Comment/UIComment.tsx | 10 +++--- .../internal-components/Comment/index.tsx | 4 ++- .../CommentList/CommentList.module.css | 1 + .../CommentList/CommentList.tsx | 4 +-- .../Renderers/Renderers.module.css | 3 +- src/v4/social/pages/DraftsPage/DraftsPage.tsx | 4 +-- 11 files changed, 61 insertions(+), 56 deletions(-) diff --git a/src/v4/core/components/BottomSheet/BottomSheet.module.css b/src/v4/core/components/BottomSheet/BottomSheet.module.css index 50dfbf72a..747db51f9 100644 --- a/src/v4/core/components/BottomSheet/BottomSheet.module.css +++ b/src/v4/core/components/BottomSheet/BottomSheet.module.css @@ -5,9 +5,6 @@ which have higher specificity. */ .react-modal-sheet-container { - border-top-left-radius: var(--asc-spacing-m2) !important; - border-top-right-radius: var(--asc-spacing-m2) !important; - background-color: var(--asc-color-base-background); color: var(--asc-color-base-default); } @@ -16,12 +13,15 @@ which have higher specificity. } .react-modal-sheet-header { + display: flex; + height: 2.5rem; + padding-bottom: 1rem; + justify-content: center; + align-items: center; + gap: 0.5rem; + align-self: stretch; background-color: var(--asc-color-base-background); color: var(--asc-color-base-default); - text-align: center; - border-bottom: 1px solid var(--asc-color-base-shade4); - padding-bottom: 0.5rem; - background: var(--asc-color-base-background); } .react-modal-sheet-content { diff --git a/src/v4/core/components/BottomSheet/BottomSheet.tsx b/src/v4/core/components/BottomSheet/BottomSheet.tsx index 0ddd38cc6..3eadd5e1b 100644 --- a/src/v4/core/components/BottomSheet/BottomSheet.tsx +++ b/src/v4/core/components/BottomSheet/BottomSheet.tsx @@ -19,10 +19,17 @@ interface BottomSheetProps { export const BottomSheet = ({ children, headerTitle, ...props }: BottomSheetProps) => { return ( - + {headerTitle && ( @@ -30,7 +37,9 @@ export const BottomSheet = ({ children, headerTitle, ...props }: BottomSheetProp {headerTitle} )} - {children} + + {children} + diff --git a/src/v4/social/internal-components/Comment/Comment.module.css b/src/v4/social/internal-components/Comment/Comment.module.css index c847277c9..327387304 100644 --- a/src/v4/social/internal-components/Comment/Comment.module.css +++ b/src/v4/social/internal-components/Comment/Comment.module.css @@ -1,17 +1,21 @@ .actionButton { - display: inline-flex; - justify-content: flex-start; - align-items: center; - gap: 0.25rem; - border: none; - color: var(--asc-color-base-default); - background: var(--asc-color-base-background); - text-align: start; - } + display: inline-flex; + justify-content: flex-start; + align-items: center; + gap: 0.25rem; + border: none; + color: var(--asc-color-base-default); + background: var(--asc-color-base-background); + text-align: start; +} - .actionIcon { - width: 1.25rem; - height: 1.25rem; - fill: var(--asc-color-base-default); - color: var(--asc-color-base-default); - } \ No newline at end of file +.actionIcon { + width: 1.25rem; + height: 1.25rem; + fill: var(--asc-color-base-default); + color: var(--asc-color-base-default); +} + +.replyContainer { + margin-left: 3.25rem; +} diff --git a/src/v4/social/internal-components/Comment/CommentText.module.css b/src/v4/social/internal-components/Comment/CommentText.module.css index f56f9b394..abcd23fcf 100644 --- a/src/v4/social/internal-components/Comment/CommentText.module.css +++ b/src/v4/social/internal-components/Comment/CommentText.module.css @@ -7,9 +7,6 @@ padding: 0.75rem; display: inline-block; white-space: pre-wrap; - font-size: var(--asc-text-font-size-md); - font-weight: var(--asc-text-font-weight-normal); - line-height: var(--asc-line-height-lg); } .readMoreButton { diff --git a/src/v4/social/internal-components/Comment/UIComment.module.css b/src/v4/social/internal-components/Comment/UIComment.module.css index 6e73ad1e3..d5f70fc13 100644 --- a/src/v4/social/internal-components/Comment/UIComment.module.css +++ b/src/v4/social/internal-components/Comment/UIComment.module.css @@ -8,11 +8,6 @@ width: 100%; } -.banIcon { - margin-left: 0.265rem; - margin-top: 1px; -} - .commentDate { color: var(--asc-color-base-shade2); } @@ -47,13 +42,11 @@ } .interactionBar { - width: 100%; display: flex; - justify-content: space-between; - width: 100%; - gap: 0.5rem; - align-items: center; - padding: 0.125rem 0; + padding: 0.25rem 1rem 0.75rem 0rem; + align-items: flex-start; + gap: 12.9375rem; + align-self: stretch; } .commentEditContainer { @@ -136,13 +129,15 @@ } .content { - width: 100%; + display: inline-flex; + flex-direction: column; + gap: 0.25rem; } .commentHeader { - display: flex; - justify-content: space-between; + display: inline-flex; + justify-content: flex-start; align-items: center; - gap: 0.5rem; + gap: 0.125rem; color: var(--asc-color-base-default); } diff --git a/src/v4/social/internal-components/Comment/UIComment.tsx b/src/v4/social/internal-components/Comment/UIComment.tsx index 4a81267f4..8ded27d94 100644 --- a/src/v4/social/internal-components/Comment/UIComment.tsx +++ b/src/v4/social/internal-components/Comment/UIComment.tsx @@ -1,11 +1,10 @@ import React from 'react'; -import Truncate from 'react-truncate-markup'; + import { FormattedMessage } from 'react-intl'; import CommentText from './CommentText'; import { backgroundImage as UserImage } from '~/icons/User'; -import BanIcon from '~/icons/Ban'; import { Mentioned, Metadata } from '~/v4/helpers/utils'; import { QueryMentioneesFnType } from '~/v4/chat/hooks/useMention'; @@ -110,9 +109,6 @@ const UIComment = ({
{authorName} - - <>{isBanned && } -
{isEditing ? ( @@ -144,7 +140,9 @@ const UIComment = ({
) : ( - +
+ +
)} {!isEditing && (canLike || canReply || options.length > 0) && ( diff --git a/src/v4/social/internal-components/Comment/index.tsx b/src/v4/social/internal-components/Comment/index.tsx index f22158cc8..518878018 100644 --- a/src/v4/social/internal-components/Comment/index.tsx +++ b/src/v4/social/internal-components/Comment/index.tsx @@ -264,7 +264,9 @@ export const Comment = ({ commentId, readonly, onClickReply }: CommentProps) => return ( <> {isReplyComment ? ( -
{renderedComment}
+
+ {renderedComment} +
) : (
{renderedComment}
diff --git a/src/v4/social/internal-components/CommentList/CommentList.module.css b/src/v4/social/internal-components/CommentList/CommentList.module.css index 1b8bce01b..b84f93ef2 100644 --- a/src/v4/social/internal-components/CommentList/CommentList.module.css +++ b/src/v4/social/internal-components/CommentList/CommentList.module.css @@ -2,6 +2,7 @@ max-height: calc(100vh - 16rem); overflow-y: auto; overflow: hidden; + margin-bottom: 1rem; } .tabIcon { diff --git a/src/v4/social/internal-components/CommentList/CommentList.tsx b/src/v4/social/internal-components/CommentList/CommentList.tsx index efa7266ca..f3024056e 100644 --- a/src/v4/social/internal-components/CommentList/CommentList.tsx +++ b/src/v4/social/internal-components/CommentList/CommentList.tsx @@ -1,4 +1,4 @@ -import React, { memo, useState } from 'react'; +import React, { memo } from 'react'; import { useIntl } from 'react-intl'; import useCommentsCollection from '~/social/hooks/collections/useCommentsCollection'; @@ -27,7 +27,6 @@ export const CommentList = ({ readonly = false, isExpanded = true, onClickReply, - style, shouldAllowInteraction, }: CommentListProps) => { const { comments, hasMore, loadMore } = useCommentsCollection({ @@ -78,7 +77,6 @@ export const CommentList = ({ commentId={comment.commentId} readonly={readonly} onClickReply={() => onClickReply?.(comment as Amity.Comment)} - style={style} shouldAllowInteraction={shouldAllowInteraction} /> ); diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css b/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css index 775b6c148..9cc7a4d32 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css @@ -250,7 +250,8 @@ } .actionButton { - display: inline-flex; + width: 100%; + display: flex; justify-content: flex-start; text-align: left; gap: var(--asc-spacing-s1); diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index fff95e43f..6e1f562fa 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -227,8 +227,8 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor style={{ background: `linear-gradient( 180deg, - ${colors?.length > 0 ? colors[0].hex : 'var(--color-black)'} 0%, - ${colors?.length > 0 ? colors[colors?.length - 1].hex : 'var(--color-black)'} 100% + ${colors?.length > 0 ? colors[0].hex : 'var(--asc-color-black)'} 0%, + ${colors?.length > 0 ? colors[colors?.length - 1].hex : 'var(--asc-color-black)'} 100% )`, }} > From bbcd949c6dd9cfc6deb42a8f17678afa02d4a0e6 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Mon, 13 May 2024 15:42:31 +0700 Subject: [PATCH 068/300] fix: ASC-21792 - comment list infinite scroll (#330) * fix: hyperlink background * fix: hyperlink icon color * fix: css * fix: add scroller * fix: comment * fix: scroll * fix: comment list * fix: reaction list * fix: comment date css shrink * fix: type * fix: css * fix: color * fix: comment scroll * fix: comment list * fix: use intersection observer instead * fix: padding --- .../BottomSheet/BottomSheet.module.css | 3 +- .../components/BottomSheet/BottomSheet.tsx | 6 +- .../LoadMoreWrapper.module.css | 7 ++ .../LoadMoreWrapper/LoadMoreWrapper.tsx | 53 +++++---- .../HyperLinkConfig.module.css | 5 +- .../ReactionList/ReactionList.module.css | 58 ++++++---- .../components/ReactionList/ReactionList.tsx | 1 - .../social/components/ReactionList/styles.tsx | 104 ------------------ .../Comment/UIComment.module.css | 8 +- .../internal-components/Comment/index.tsx | 6 +- .../CommentList/CommentList.tsx | 48 ++++---- 11 files changed, 114 insertions(+), 185 deletions(-) delete mode 100644 src/v4/social/components/ReactionList/styles.tsx diff --git a/src/v4/core/components/BottomSheet/BottomSheet.module.css b/src/v4/core/components/BottomSheet/BottomSheet.module.css index 747db51f9..b258fba24 100644 --- a/src/v4/core/components/BottomSheet/BottomSheet.module.css +++ b/src/v4/core/components/BottomSheet/BottomSheet.module.css @@ -14,7 +14,6 @@ which have higher specificity. .react-modal-sheet-header { display: flex; - height: 2.5rem; padding-bottom: 1rem; justify-content: center; align-items: center; @@ -22,9 +21,11 @@ which have higher specificity. align-self: stretch; background-color: var(--asc-color-base-background); color: var(--asc-color-base-default); + border-bottom: 1px solid var(--asc-color-base-shade4); } .react-modal-sheet-content { + display: flex; background-color: var(--asc-color-base-background); padding: 1rem; color: var(--asc-color-base-default); diff --git a/src/v4/core/components/BottomSheet/BottomSheet.tsx b/src/v4/core/components/BottomSheet/BottomSheet.tsx index 3eadd5e1b..d74a40450 100644 --- a/src/v4/core/components/BottomSheet/BottomSheet.tsx +++ b/src/v4/core/components/BottomSheet/BottomSheet.tsx @@ -1,7 +1,7 @@ import React from 'react'; + import Sheet from 'react-modal-sheet'; import { Typography } from '~/v4/core/components/Typography'; - import styles from './BottomSheet.module.css'; interface BottomSheetProps { @@ -37,9 +37,7 @@ export const BottomSheet = ({ children, headerTitle, ...props }: BottomSheetProp {headerTitle} )} - - {children} - + {children} diff --git a/src/v4/core/components/LoadMoreWrapper/LoadMoreWrapper.module.css b/src/v4/core/components/LoadMoreWrapper/LoadMoreWrapper.module.css index cc3eb1311..a766c66c0 100644 --- a/src/v4/core/components/LoadMoreWrapper/LoadMoreWrapper.module.css +++ b/src/v4/core/components/LoadMoreWrapper/LoadMoreWrapper.module.css @@ -41,3 +41,10 @@ .chevronDownIcon { margin-left: 0.3125rem; } + +.content { + display: flex; + flex-direction: column; + flex: 1; + overflow-y: auto; +} diff --git a/src/v4/core/components/LoadMoreWrapper/LoadMoreWrapper.tsx b/src/v4/core/components/LoadMoreWrapper/LoadMoreWrapper.tsx index fbb7328f3..175a74e1d 100644 --- a/src/v4/core/components/LoadMoreWrapper/LoadMoreWrapper.tsx +++ b/src/v4/core/components/LoadMoreWrapper/LoadMoreWrapper.tsx @@ -1,15 +1,14 @@ -import React, { useState, useEffect, ReactNode } from 'react'; +import React, { useState, useEffect, ReactNode, useRef } from 'react'; import { useIntl } from 'react-intl'; import clsx from 'clsx'; import { Button } from '~/v4/core/components'; import styles from './LoadMoreWrapper.module.css'; -import { ChevronDownIcon } from '~/v4/social/icons'; interface LoadMoreWrapperProps { - hasMore?: boolean; - loadMore?: () => void; + hasMore: boolean; + loadMore: () => void; text?: string; - contentSlot: ReactNode; + contentSlot: React.JSX.Element[]; className?: string; prependIcon?: ReactNode; appendIcon?: ReactNode; @@ -23,30 +22,44 @@ export const LoadMoreWrapper = ({ contentSlot, className = '', prependIcon = null, - appendIcon = , + appendIcon, isExpanded = true, }: LoadMoreWrapperProps) => { const { formatMessage } = useIntl(); const [expanded, setExpanded] = useState(isExpanded); + const observerRef = useRef(null); - useEffect(() => setExpanded(isExpanded), [isExpanded]); + useEffect(() => { + setExpanded(isExpanded); + }, [isExpanded]); + + useEffect(() => { + const observer = new IntersectionObserver( + (entries) => { + if (entries[0].isIntersecting && hasMore) { + loadMore(); + } + }, + { threshold: 1 }, + ); + + if (observerRef.current) { + observer.observe(observerRef.current); + } + + return () => { + if (observerRef.current) { + observer.unobserve(observerRef.current); + } + }; + }, [hasMore, loadMore]); if (expanded) { return ( - <> +
{contentSlot} - {hasMore && ( - - )} - +
+
); } diff --git a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css index 5e40478f4..5e5e68d50 100644 --- a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css +++ b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css @@ -76,7 +76,7 @@ .styledSecondaryButton { border: none; color: var(--asc-color-base-default); - background: var(--asc-color-base-background) + background: var(--asc-color-base-background); } .removeIcon { @@ -103,4 +103,5 @@ height: 0.0625rem; align-self: stretch; background-color: var(--asc-color-base-shade4); -} \ No newline at end of file + margin: 1rem 0; +} diff --git a/src/v4/social/components/ReactionList/ReactionList.module.css b/src/v4/social/components/ReactionList/ReactionList.module.css index cf31c26be..484874fd7 100644 --- a/src/v4/social/components/ReactionList/ReactionList.module.css +++ b/src/v4/social/components/ReactionList/ReactionList.module.css @@ -6,57 +6,67 @@ .tabList { display: flex; - gap: var(--asc-spacing-s1); - border-bottom: 1px solid var(--asc-color-base-shade4); - padding-bottom: var(--asc-spacing-s1); + justify-content: flex-start; + align-items: center; + border-bottom: 1px solid var(--asc-color-neutral-shade2); + margin-bottom: 1rem; } .tabItem { cursor: pointer; - padding: var(--asc-spacing-xxs2) var(--asc-spacing-s1); - border-radius: var(--asc-border-radius-sm); - background: var(--asc-color-base-background); - color: var(--asc-color-base-shade6); + padding: 0.5rem 1rem; + position: relative; + transition: color 0.3s ease; +} + +.tabItem::after { + content: ''; + position: absolute; + bottom: -1px; + left: 0; + width: 100%; + height: 2px; + background-color: transparent; + transition: background-color 0.3s ease; } .tabItem.active { - background-color: var(--asc-color-base-shade4); color: var(--asc-color-primary-default); } +.tabItem.active::after { + background-color: var(--asc-color-primary-default); +} + .reactionEmoji { display: flex; align-items: center; - gap: var(--asc-spacing-s1); + gap: 0.5rem; } .tabCount { - font-size: 0.8rem; - background: var(--asc-color-base-background); - color: var(--asc-color-base-shade1); - padding: 0.1rem 0.3rem; - border-radius: var(--asc-border-radius-sm); + color: var(--asc-color-base-shade2); + transition: color 0.3s ease; +} + +.tabItem.active .tabCount { + color: var(--asc-color-primary-default); } .userList { - display: flex; - flex-wrap: wrap; - gap: var(--asc-spacing-s1); + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 1rem; } .userItem { display: flex; align-items: center; - gap: var(--asc-spacing-s1); - background: var(--asc-color-base-background); - padding: var(--asc-spacing-s1); - border-radius: var(--asc-border-radius-sm); - width: 100%; + gap: 0.5rem; } .userDetailsContainer { display: flex; align-items: center; - gap: var(--asc-spacing-s1); - color: var(--asc-color-base-default); + gap: 0.5rem; } diff --git a/src/v4/social/components/ReactionList/ReactionList.tsx b/src/v4/social/components/ReactionList/ReactionList.tsx index c653c3b66..af25bb11f 100644 --- a/src/v4/social/components/ReactionList/ReactionList.tsx +++ b/src/v4/social/components/ReactionList/ReactionList.tsx @@ -1,5 +1,4 @@ import React, { Fragment, useState } from 'react'; - import { FireIcon, HeartIcon, LikedIcon } from '~/icons'; import styles from './ReactionList.module.css'; import { useReactionsCollection } from '~/v4/social/hooks/collections/useReactionsCollection'; diff --git a/src/v4/social/components/ReactionList/styles.tsx b/src/v4/social/components/ReactionList/styles.tsx deleted file mode 100644 index d5a90df78..000000000 --- a/src/v4/social/components/ReactionList/styles.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import styled from 'styled-components'; - -export const ReactionListContainer = styled.div` - border-radius: 0.25rem; - overflow: hidden; - max-height: 100%; - width: 100%; - display: flex; - flex-direction: column; -`; - -export const TabList = styled.ul` - display: flex; - list-style-type: none; - padding: 0; - margin: 0; - overflow-x: auto; - white-space: nowrap; - width: 100%; - height: 3rem; - border-bottom: 0.0625rem solid ${({ theme }) => theme.v4.colors.base.shade4}; - gap: 1.25rem; - - &::-webkit-scrollbar { - display: none; - } - -ms-overflow-style: none; - scrollbar-width: none; -`; - -export const TabItem = styled.li<{ active: boolean }>` - display: flex; - align-items: center; - justify-content: center; - padding: 0rem 0.25rem; - cursor: pointer; - border-radius: 0.25rem; - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - border-bottom: 0.125rem solid transparent; - flex-shrink: 0; - - &:hover { - background-color: ${({ theme }) => theme.v4.colors.base.shade4}; - } - - ${({ active, theme }) => - active && - ` - border-bottom-color: ${theme.v4.colors.primary.default}; - color: ${theme.v4.colors.primary.default}; - `} -`; - -export const TabCount = styled.span` - margin-left: 0.25rem; -`; - -export const UserList = styled.ul` - list-style-type: none; - margin: 0; - padding: 0; - overflow-y: auto; - flex: 1; - max-height: calc(100% - 3rem); -`; - -export const UserItem = styled.li` - display: flex; - justify-content: space-between; - gap: 0.5rem; - align-items: center; - margin-bottom: 0.5rem; - padding: 0.5rem; - border-bottom: 0.0625rem solid ${({ theme }) => theme.v4.colors.base.shade4}; - - &:hover { - cursor: pointer; - background-color: ${({ theme }) => theme.v4.colors.base.shade4}; - } -`; - -export const Divider = styled.hr` - border: none; - border-bottom: 0.0625rem solid ${({ theme }) => theme.v4.colors.base.shade4}; - margin: 0.5rem 0; - - &:last-child { - display: none; - } -`; - -export const ReactionEmoji = styled.div` - display: flex; - align-items: center; - justify-content: center; - gap: 0.5rem; -`; - -export const UserDetailsContainer = styled.div` - display: flex; - align-items: center; - gap: 0.5rem; -`; diff --git a/src/v4/social/internal-components/Comment/UIComment.module.css b/src/v4/social/internal-components/Comment/UIComment.module.css index d5f70fc13..879e00e65 100644 --- a/src/v4/social/internal-components/Comment/UIComment.module.css +++ b/src/v4/social/internal-components/Comment/UIComment.module.css @@ -9,6 +9,7 @@ } .commentDate { + flex-shrink: 0; color: var(--asc-color-base-shade2); } @@ -43,10 +44,10 @@ .interactionBar { display: flex; + width: 100%; + justify-content: space-between; padding: 0.25rem 1rem 0.75rem 0rem; - align-items: flex-start; - gap: 12.9375rem; - align-self: stretch; + align-items: center; } .commentEditContainer { @@ -132,6 +133,7 @@ display: inline-flex; flex-direction: column; gap: 0.25rem; + width: 100%; } .commentHeader { diff --git a/src/v4/social/internal-components/Comment/index.tsx b/src/v4/social/internal-components/Comment/index.tsx index 518878018..4f12418fe 100644 --- a/src/v4/social/internal-components/Comment/index.tsx +++ b/src/v4/social/internal-components/Comment/index.tsx @@ -102,7 +102,11 @@ export const Comment = ({ commentId, readonly, onClickReply }: CommentProps) => return toggleFlagComment(); }; - const handleEditComment = async (text: string, mentionees: Mentionees, metadata: Metadata) => + const handleEditComment = async ( + text: string, + mentionees: Amity.UserMention[], + metadata: Metadata, + ) => commentId && CommentRepository.updateComment(commentId, { data: { diff --git a/src/v4/social/internal-components/CommentList/CommentList.tsx b/src/v4/social/internal-components/CommentList/CommentList.tsx index f3024056e..00718965e 100644 --- a/src/v4/social/internal-components/CommentList/CommentList.tsx +++ b/src/v4/social/internal-components/CommentList/CommentList.tsx @@ -1,11 +1,11 @@ import React, { memo } from 'react'; import { useIntl } from 'react-intl'; import useCommentsCollection from '~/social/hooks/collections/useCommentsCollection'; - import { Comment } from '../Comment'; import styles from './CommentList.module.css'; -import { LoadMoreWrapper } from '~/v4/core/components/LoadMoreWrapper'; import { ExpandIcon } from '~/v4/social/icons'; +import { Button } from '~/v4/core/components'; +import { LoadMoreWrapper } from '~/v4/core/components/LoadMoreWrapper/LoadMoreWrapper'; interface CommentListProps { parentId?: string; @@ -35,8 +35,8 @@ export const CommentList = ({ referenceType, limit, }); - const { formatMessage } = useIntl(); + const { formatMessage } = useIntl(); const isReplyComment = !!parentId; const commentCount = comments?.length; @@ -60,29 +60,27 @@ export const CommentList = ({ if (comments?.length === 0) return null; - return ( -
- { - return ( - onClickReply?.(comment as Amity.Comment)} - shouldAllowInteraction={shouldAllowInteraction} - /> - ); - })} + const renderComments = () => { + return comments.map((comment) => ( + onClickReply?.(comment as Amity.Comment)} + shouldAllowInteraction={shouldAllowInteraction} /> -
+ )); + }; + + return ( + ); }; From 3528ce48f847495dda3246ec5a2e7ba9f5cbf002 Mon Sep 17 00:00:00 2001 From: Bonn Date: Mon, 13 May 2024 17:45:38 +0700 Subject: [PATCH 069/300] Release/v4.0.0 beta.4 (#334) * fix: github actions * chore: dropdown instead of free-text * chore: add none option * chore: add none option for pre-release input * fix: add NPM_TOKEN env * chore(release): 4.0.0-beta.4 --------- Co-authored-by: bmo-amity-bot --- .github/workflows/dev.yaml | 2 +- .github/workflows/production.yaml | 30 ++++++++++++++++++++++-------- CHANGELOG.md | 6 ++++++ package.json | 2 +- 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/.github/workflows/dev.yaml b/.github/workflows/dev.yaml index a361edb96..fc182ba67 100644 --- a/.github/workflows/dev.yaml +++ b/.github/workflows/dev.yaml @@ -40,7 +40,7 @@ jobs: - name: Get pnpm store directory id: pnpm-cache run: | - echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT + echo "pnpm_cache_dir=$(pnpm store path | tr -d '\n')" >> $GITHUB_OUTPUT - uses: actions/cache@v3 name: Setup pnpm cache diff --git a/.github/workflows/production.yaml b/.github/workflows/production.yaml index f55defe2c..63351027c 100644 --- a/.github/workflows/production.yaml +++ b/.github/workflows/production.yaml @@ -4,11 +4,25 @@ on: workflow_dispatch: inputs: release_as: + type: choice description: 'release as' required: false + options: + - none + - major + - minor + - patch pre-release: + type: choice description: 'prerelease' required: false + options: + - none + - alpha + - beta + +env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} jobs: publish: @@ -34,7 +48,7 @@ jobs: - name: Get pnpm store directory id: pnpm-cache run: | - echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT + echo "pnpm_cache_dir=$(pnpm store path | tr -d '\n')" >> $GITHUB_OUTPUT - uses: actions/cache@v3 name: Setup pnpm cache @@ -49,31 +63,31 @@ jobs: - name: increase version (patch) run: pnpm standard-version --yes - if: github.event.inputs.release_as == 'patch' && github.event.inputs.pre-release == '' + if: github.event.inputs.release_as == 'patch' && github.event.inputs.pre-release == 'none' - name: increase version (patch) (pre-release) run: pnpm standard-version --yes --release-as patch --prerelease ${{ github.event.inputs.pre-release }} - if: github.event.inputs.release_as == 'patch' && github.event.inputs.pre-release != '' + if: github.event.inputs.release_as == 'patch' && github.event.inputs.pre-release != 'none' - name: increase version (minor) run: pnpm standard-version --yes --release-as minor - if: github.event.inputs.release_as == 'minor' && github.event.inputs.pre-release == '' + if: github.event.inputs.release_as == 'minor' && github.event.inputs.pre-release == 'none' - name: increase version (minor) (pre-release) run: pnpm standard-version --yes --prerelease ${{ github.event.inputs.pre-release }} - if: github.event.inputs.release_as == 'minor' && github.event.inputs.pre-release != '' + if: github.event.inputs.release_as == 'minor' && github.event.inputs.pre-release != 'none' - name: increase version (major) run: pnpm standard-version --yes --release-as major - if: github.event.inputs.release_as == 'major' && github.event.inputs.pre-release == '' + if: github.event.inputs.release_as == 'major' && github.event.inputs.pre-release == 'none' - name: increase version (major) (pre-release) run: pnpm standard-version --yes --release-as major --prerelease ${{ github.event.inputs.pre-release }} - if: github.event.inputs.release_as == 'major' && github.event.inputs.pre-release != '' + if: github.event.inputs.release_as == 'major' && github.event.inputs.pre-release != 'none' - name: increase version (pre-release) run: pnpm standard-version --prerelease ${{ github.event.inputs.pre-release }} - if: github.event.inputs.release_as == '' && github.event.inputs.pre-release != '' + if: github.event.inputs.release_as == 'none' && github.event.inputs.pre-release != 'none' - name: build run: pnpm run build diff --git a/CHANGELOG.md b/CHANGELOG.md index 8191dcf49..f656294b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## 4.0.0-beta.4 (2024-05-13) + +### Bug Fixes + +- add NPM_TOKEN env ([b22eca3](https://github.com/EkoCommunications/AmityUiKitWeb/commit/b22eca3208f632498db40d8a320a8aeac402f45c)) + ## 4.0.0-beta.3 (2024-04-26) ### Bug Fixes diff --git a/package.json b/package.json index e6f7132fa..742139c97 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@amityco/ui-kit", - "version": "4.0.0-beta.3", + "version": "4.0.0-beta.4", "engines": { "node": ">=16", "pnpm": ">=8" From 1e41eeff723b8aecd238e32ff97ca7d3cfab97ec Mon Sep 17 00:00:00 2001 From: Pitchaya T <33589608+ptchayap@users.noreply.github.com> Date: Wed, 15 May 2024 10:55:40 +0700 Subject: [PATCH 070/300] fix: to support un config value (#340) --- src/v4/core/providers/ThemeProvider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v4/core/providers/ThemeProvider.tsx b/src/v4/core/providers/ThemeProvider.tsx index 472898a5c..928d15037 100644 --- a/src/v4/core/providers/ThemeProvider.tsx +++ b/src/v4/core/providers/ThemeProvider.tsx @@ -91,7 +91,7 @@ const generateComponentPalette = (config: Config, currentTheme: 'light' | 'dark' configurable.componentIds.forEach((componentId) => { const componentConfig = (config.customizations as { [key: string]: { theme: Theme } })?.[ `${configurable.pageId}/${componentId}/*` - ].theme; + ]?.theme; if (componentConfig) { const themeToGenerate = currentTheme === 'light' ? componentConfig.light : componentConfig.dark; From 26621609a8359e56ac2a3490240c93e8189a1c1a Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Mon, 20 May 2024 11:00:54 +0700 Subject: [PATCH 071/300] fix: hyperlink long text (#335) --- .../elements/HyperLink/HyperLink.module.css | 20 ++++++++++++++++++- .../social/elements/HyperLink/HyperLink.tsx | 4 +++- src/v4/social/pages/DraftsPage/DraftsPage.tsx | 4 +--- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/v4/social/elements/HyperLink/HyperLink.module.css b/src/v4/social/elements/HyperLink/HyperLink.module.css index 2698895eb..e16c56d0e 100644 --- a/src/v4/social/elements/HyperLink/HyperLink.module.css +++ b/src/v4/social/elements/HyperLink/HyperLink.module.css @@ -1,7 +1,7 @@ .hyperlink { border: 1px solid var(--asc-color-base-shade4); color: var(--asc-color-secondary-default); - background: rgba(255, 255, 255, 0.2); + background: #ffffffcc; display: inline-flex; align-items: center; padding: var(--asc-spacing-s2) var(--asc-spacing-m1) var(--asc-spacing-s2) var(--asc-spacing-s2); @@ -12,10 +12,28 @@ font-weight: 600; line-height: 1.25rem; letter-spacing: -0.015rem; + max-width: 200px; + border: var(--asc-color-base-shade4); + text-decoration: none; } .hyperlinkIcon { color: var(--asc-color-primary-default); width: 1.5rem; height: 1.5rem; + flex-shrink: 0; +} + +.hyperlinkText { + display: flex; + align-items: center; + max-width: calc(100% - 2rem); + overflow: hidden; + color: var(--asc-color-secondary-shade4); +} + +.text { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } diff --git a/src/v4/social/elements/HyperLink/HyperLink.tsx b/src/v4/social/elements/HyperLink/HyperLink.tsx index e107f63ba..e4dff3e56 100644 --- a/src/v4/social/elements/HyperLink/HyperLink.tsx +++ b/src/v4/social/elements/HyperLink/HyperLink.tsx @@ -10,7 +10,9 @@ export const HyperLink: React.FC = ({ href, children, ...rest } return ( - {children} +
+ {children} +
); }; diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index 6e1f562fa..52a19c9c7 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -263,9 +263,7 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor target="_blank" rel="noopener noreferrer" > - - {hyperLink[0]?.data?.customText || hyperLink[0].data.url} - + {hyperLink[0]?.data?.customText || hyperLink[0].data.url}
)} From ccdbbddcb5b1aed817bf1411c74242b0b7463946 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Mon, 20 May 2024 11:05:19 +0700 Subject: [PATCH 072/300] fix: hyperlink custom text input max length (#336) --- src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx index 72483ff46..26b7f13d2 100644 --- a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx +++ b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx @@ -170,6 +170,7 @@ export const HyperLinkConfig = ({ })} className={clsx(styles.input, errors?.customText && styles.hasError)} {...register('customText')} + maxLength={MAX_LENGTH} /> {errors?.customText && ( {errors?.customText?.message} From 3ebfc1a5021ec299d531bc5507d2c7236ceca4a0 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Mon, 20 May 2024 12:21:43 +0700 Subject: [PATCH 073/300] fix: ASC-220001 - share story button (#337) * fix: share story button * fix: use avatar v4 * fix: remove unused --- src/v4/core/components/Avatar/Avatar.tsx | 1 + .../elements/ShareStoryButton/ShareStoryButton.tsx | 10 ++++++++-- src/v4/social/pages/DraftsPage/DraftsPage.tsx | 10 +++------- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/v4/core/components/Avatar/Avatar.tsx b/src/v4/core/components/Avatar/Avatar.tsx index 93b513a3c..12a061429 100644 --- a/src/v4/core/components/Avatar/Avatar.tsx +++ b/src/v4/core/components/Avatar/Avatar.tsx @@ -19,6 +19,7 @@ export const Avatar = ({ onClick, loading, size = 'medium', + backgroundImage, ...props }: AvatarProps) => { const [visible, setVisible] = useState(false); diff --git a/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx b/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx index 949665925..3b66ac7b8 100644 --- a/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx +++ b/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx @@ -1,11 +1,12 @@ import React from 'react'; -import Avatar from '~/core/components/Avatar'; import { useIntl } from 'react-intl'; import { isValidHttpUrl } from '~/utils'; import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; import { Icon } from '~/v4/core/components/Icon'; +import { backgroundImage as communityBackgroundImage } from '~/icons/Community'; import styles from './ShareStoryButton.module.css'; +import { Avatar } from '~/v4/core/components'; interface ShareButtonProps { onClick: () => void; @@ -41,7 +42,12 @@ export const ShareStoryButton = ({ onClick={onClick} > {!elementConfig?.hide_avatar && ( - + )} {formatMessage({ id: 'storyDraft.button.shareStory' })} {isRemoteImage ? ( diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index 52a19c9c7..1c6974911 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -2,9 +2,6 @@ import React, { useEffect, useState } from 'react'; import { useIntl } from 'react-intl'; import { extractColors } from 'extract-colors'; import { readFileAsync } from '~/helpers'; -import useUser from '~/core/hooks/useUser'; -import useSDK from '~/core/hooks/useSDK'; -import useImage from '~/core/hooks/useImage'; import styles from './DraftsPage.module.css'; import { SubmitHandler } from 'react-hook-form'; @@ -27,6 +24,7 @@ import { useNotifications } from '~/v4/core/providers/NotificationProvider'; import { useNavigation } from '~/social/providers/NavigationProvider'; import { PageTypes } from '~/social/constants'; import { BaseVideoPreview } from '../../internal-components/VideoPreview'; +import { useCommunityInfo } from '~/social/components/CommunityInfo/hooks'; type AmityStoryMediaType = { type: 'image'; url: string } | { type: 'video'; url: string }; @@ -60,9 +58,7 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor setIsHyperLinkBottomSheetOpen(false); }; - const { currentUserId } = useSDK(); - const user = useUser(currentUserId); - const creatorAvatar = useImage({ imageSize: 'small', fileId: user?.avatarFileId }); + const community = useCommunityInfo(targetId); const { formatMessage } = useIntl(); @@ -284,7 +280,7 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor onClick={() => onCreateStory(file, imageMode, {}, hyperLink.length > 0 ? hyperLink : []) } - avatar={creatorAvatar} + avatar={community.avatarFileUrl} />
From 067ff98c45898f2f7100a5c7a15d378287f7e1e7 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Mon, 20 May 2024 12:37:10 +0700 Subject: [PATCH 074/300] fix: ASC-21590 - hyperlink ui (#347) * fix: hyperlink long text * fix: story hyperlink --- .../internal-components/StoryViewer/Renderers/Image.tsx | 5 ++++- .../internal-components/StoryViewer/Renderers/Video.tsx | 5 ++++- src/v4/social/pages/DraftsPage/DraftsPage.tsx | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index 7c7c314f3..05bd831ef 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -263,7 +263,10 @@ export const renderer: CustomRenderer = ({ story, action, config }) => { onClick={() => story.analytics.markLinkAsClicked()} > - {story.items?.[0]?.data?.customText || story.items?.[0].data.url} + + {story.items[0]?.data?.customText || + story.items[0].data.url.replace(/^https?:\/\//, '')} +
diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx index bf090aeb1..42ee12517 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx @@ -302,7 +302,10 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler onClick={() => story.analytics.markLinkAsClicked()} > - {story.items?.[0].data?.customText || story.items?.[0].data.url} + + {story.items[0]?.data?.customText || + story.items[0].data.url.replace(/^https?:\/\//, '')} +
diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index 1c6974911..70163b802 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -259,7 +259,7 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor target="_blank" rel="noopener noreferrer" > - {hyperLink[0]?.data?.customText || hyperLink[0].data.url} + {hyperLink[0]?.data?.customText || hyperLink[0].data.url.replace(/^https?:\/\//, '')}
)} From 83a1d348363238a433f3cb78a1d536a26a692a77 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Mon, 20 May 2024 12:44:58 +0700 Subject: [PATCH 075/300] fix: story commu condition (#346) --- src/utils.ts | 6 ------ src/v4/social/components/ViewStoryPage/index.tsx | 5 +---- .../internal-components/StoryViewer/Renderers/Image.tsx | 6 +++--- .../internal-components/StoryViewer/Renderers/Video.tsx | 6 +++--- src/v4/social/pages/StoryPage/CommunityFeedStory.tsx | 4 ++-- src/v4/social/pages/StoryPage/GlobalFeedStory.tsx | 4 ++-- 6 files changed, 11 insertions(+), 20 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index 6357a8e4b..8dd500000 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -22,12 +22,6 @@ export const checkStoryPermission = ( return false; } - const userPermission = client.hasPermission(Permissions.ManageStoryPermission).currentUser(); - - if (userPermission) { - return true; - } - if (communityId) { const communityPermission = client .hasPermission(Permissions.ManageStoryPermission) diff --git a/src/v4/social/components/ViewStoryPage/index.tsx b/src/v4/social/components/ViewStoryPage/index.tsx index ba3ed181c..3049611e3 100644 --- a/src/v4/social/components/ViewStoryPage/index.tsx +++ b/src/v4/social/components/ViewStoryPage/index.tsx @@ -18,7 +18,7 @@ import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; import { CreateStoryButton } from '../../elements'; import { renderers } from '../../internal-components/StoryViewer/Renderers'; -import { checkStoryPermission } from '~/utils'; + import { AmityDraftStoryPage } from '../../pages'; import { useStoryContext } from '../../providers/StoryProvider'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; @@ -78,10 +78,7 @@ const StoryViewer = ({ pageId, targetId, duration = 5000, onClose }: StoryViewer const { file, setFile } = useStoryContext(); const [colors, setColors] = useState([]); - const { client } = useSDK(); - const isStoryCreator = stories[currentIndex]?.creator?.userId === currentUserId; - const haveStoryPermission = checkStoryPermission(client, targetId); const confirmDeleteStory = (storyId: string) => { const isLastStory = currentIndex === 0; diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index 05bd831ef..0b60e110c 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -75,7 +75,7 @@ export const renderer: CustomRenderer = ({ story, action, config }) => { const isOfficial = community?.isOfficial || false; const isCreator = creator?.userId === user?.userId; - const haveStoryPermission = checkStoryPermission(client, community?.communityId); + const isModerator = checkStoryPermission(client, community?.communityId); const computedStyles = { ...rendererStyles.storyContent, @@ -176,7 +176,7 @@ export const renderer: CustomRenderer = ({ story, action, config }) => { heading={heading} subheading={subheading} isHaveActions={actions?.length > 0} - haveStoryPermission={haveStoryPermission} + haveStoryPermission={isModerator} isOfficial={isOfficial} isPaused={isPaused} onPlay={play} @@ -280,7 +280,7 @@ export const renderer: CustomRenderer = ({ story, action, config }) => { totalLikes={totalLikes} isLiked={isLiked} onClickComment={openCommentSheet} - showImpression={isCreator || haveStoryPermission} + showImpression={isCreator || isModerator} /> ); diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx index 42ee12517..6c9c18432 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx @@ -74,7 +74,7 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler '' ); - const haveStoryPermission = checkStoryPermission(client, community?.communityId); + const isModerator = checkStoryPermission(client, community?.communityId); const isCreator = creator?.userId === user?.userId; const computedStyles = { @@ -212,7 +212,7 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler heading={heading} subheading={subheading} isHaveActions={actions?.length > 0} - haveStoryPermission={haveStoryPermission} + haveStoryPermission={isModerator} isOfficial={isOfficial} isPaused={isPaused} onPlay={play} @@ -318,7 +318,7 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler totalLikes={totalLikes} isLiked={isLiked} onClickComment={openCommentSheet} - showImpression={isCreator || haveStoryPermission} + showImpression={isCreator || isModerator} /> ); diff --git a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx index f5addb5f4..159309e4f 100644 --- a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx @@ -81,7 +81,7 @@ export const CommunityFeedStory = ({ communityId }: CommunityFeedStoryProps) => const [colors, setColors] = useState([]); const isStoryCreator = stories[currentIndex]?.creator?.userId === currentUserId; - const haveStoryPermission = checkStoryPermission(client, communityId); + const isModerator = checkStoryPermission(client, communityId); const confirmDeleteStory = (storyId: string) => { const isLastStory = currentIndex === 0; @@ -172,7 +172,7 @@ export const CommunityFeedStory = ({ communityId }: CommunityFeedStoryProps) => url, type: isImage ? 'image' : 'video', actions: [ - isStoryCreator || haveStoryPermission + isStoryCreator || isModerator ? { name: 'delete', action: () => deleteStory(story?.storyId as string), diff --git a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx index 825226238..f5109aa64 100644 --- a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx @@ -80,7 +80,7 @@ export const GlobalFeedStory: React.FC = () => { const [colors, setColors] = useState([]); const isStoryCreator = stories[currentIndex]?.creator?.userId === currentUserId; - const haveStoryPermission = checkStoryPermission(client, stories[currentIndex]?.targetId); + const isModerator = checkStoryPermission(client, stories[currentIndex]?.targetId); const confirmDeleteStory = (storyId: string) => { const isLastStory = currentIndex === 0; @@ -173,7 +173,7 @@ export const GlobalFeedStory: React.FC = () => { url, type: isImage ? 'image' : 'video', actions: [ - isStoryCreator || haveStoryPermission + isStoryCreator || isModerator ? { name: 'delete', action: () => deleteStory(story?.storyId as string), From fe8aab87d9319fcc79f14458a1d7ccc473661499 Mon Sep 17 00:00:00 2001 From: Pitchaya T <33589608+ptchayap@users.noreply.github.com> Date: Mon, 20 May 2024 16:52:33 +0700 Subject: [PATCH 076/300] feat: ASC-22133 - custom reaction provider (#325) * feat: add custom reaction provider * feat: add to use reactionsContext * fix: rename provider * feat: ASC-22127 - reaction preview message bubble (#331) * feat: add reaction preview container * feat: add abbreviateCount function * feat: add fallback reaction * feat: update pr --------- Co-authored-by: Kiattirat S * feat: ASC-22124 - message reaction picker (#332) * feat: add reaction container and button * feat: add active and hover state * fix: storybook display * fix: remove unused * feat: add checking for active state * feat: add open state on reaction picker * feat: add hover state on Quick reaction button * feat: seperate components * feat: close picker when clicking outside * feat: add handling on click reaction button * fix: hover state * Update src/v4/chat/components/LiveChatMessageContent/MessageReaction/index.tsx * Delete src/v4/chat/hooks/useMessageReactions.ts --------- Co-authored-by: Kiattirat Sujjapongse * feat: ASC-22130 - add quick reaction (#339) * feat: add handling for quick reaction button * fix: add await to wait for add and remove reaction * fix: reaction pickert style * fix: storybook name * fix: reaction picker storybook * feat: add pageId, componentId and elementId * fix: change internal component name and export * fix: export from components * fix: message reaction components name * feat: add static file to include in storybook build * fix: remove duplicated file * fix: new design on my reaction * fix: not use z-index * fix: position of each reaction * fix: remove border on reaction preview for my reaction style * fix: remove unused * feat(reaction): ASC-22136 - reaction panel (#344) * feat: add handling for quick reaction button * fix: add await to wait for add and remove reaction * fix: reaction pickert style * fix: storybook name * fix: reaction picker storybook * feat: add pageId, componentId and elementId * fix: remove comment * feat(reaction-list): add empty files * feat(reaction): reuse reaction tab from social * feat(reaction): pass openReaction func to child * feat(reaction): render data from config * feat(reaction): update panel style * feat(reaction): update reaction list * feat(reaction): update reaction list * feat(reaction): add all state for reaction list * feat(reaction): change unit * feat(reaction): remove unused files * feat(reaction): update PR * feat(reaction): update PR * feat(reaction): update PR * fix: remove unused * feat(reaction): update PR --------- Co-authored-by: ptchaya_p Co-authored-by: Pitchaya T <33589608+ptchayap@users.noreply.github.com> --------- Co-authored-by: Kiattirat S --- .storybook/main.ts | 1 + amity-uikit.config.json | 10 ++ src/i18n/en.json | 5 +- src/index.ts | 4 + .../AmityLiveChatMessageList/index.tsx | 8 + .../index.tsx | 0 .../index.tsx | 6 + .../AmityLiveChatMessageSenderView/index.tsx | 6 + .../MessageReaction/index.tsx | 153 ++++++++++++++++++ .../MessageReaction/styles.module.css | 15 ++ .../LiveChatMessageContent/index.tsx | 103 ++++++++---- .../LiveChatMessageContent/styles.module.css | 50 ++++-- .../components/MessageQuickReaction/index.tsx | 44 +++++ .../MessageQuickReaction/styles.module.css | 18 +++ .../MessageReactionPicker/index.tsx | 40 +++++ .../livechatMessageReactionPicker.stories.tsx | 41 +++++ .../MessageReactionPicker/styles.module.css | 31 ++++ .../MessageReactionPreview/index.tsx | 61 +++++++ ...livechatMessageReactionPreview.stories.tsx | 41 +++++ .../MessageReactionPreview/styles.module.css | 50 ++++++ src/v4/chat/components/index.ts | 4 + .../ChatContainer/ChatReadyState.tsx | 4 +- .../AmityLiveChatPage/ChatContainer/index.tsx | 10 +- .../ChatContainer/styles.module.css | 6 +- src/v4/chat/pages/AmityLiveChatPage/index.tsx | 3 +- .../core/components/Typography/Typography.tsx | 57 +++++-- src/v4/core/providers/AmityUIKitProvider.tsx | 67 ++++---- .../core/providers/CustomReactionProvider.tsx | 32 ++++ .../core/providers/CustomizationProvider.tsx | 2 + src/v4/helpers/utils.ts | 8 + src/v4/icons/FallbackReaction.tsx | 28 ++++ src/v4/icons/QuickReactionIcon.tsx | 27 ++++ src/v4/icons/ReactionListSkeleton.tsx | 17 ++ src/v4/icons/SmilePlus.tsx | 19 +++ .../components/ReactionList/ReactionIcon.tsx | 20 +++ .../ReactionList/ReactionList.module.css | 117 ++++++++++---- .../components/ReactionList/ReactionList.tsx | 112 +++++++------ .../ReactionList/ReactionListEmptyState.tsx | 23 +++ .../ReactionList/ReactionListError.tsx | 18 +++ .../ReactionList/ReactionListLoadingState.tsx | 17 ++ .../ReactionList/ReactionListPanel.tsx | 53 ++++++ src/v4/utils/abbreviateCount.ts | 9 ++ src/v4/utils/selectMessageReaction.ts | 23 +++ static/message_reaction_fire.png | Bin 0 -> 4712 bytes static/message_reaction_grinning.png | Bin 0 -> 1658 bytes static/message_reaction_heart.png | Bin 0 -> 4439 bytes static/message_reaction_like.png | Bin 0 -> 4302 bytes static/message_reaction_sad.png | Bin 0 -> 1833 bytes 48 files changed, 1178 insertions(+), 185 deletions(-) create mode 100644 src/v4/chat/components/AmityLiveChatMessageQuickReaction/index.tsx create mode 100644 src/v4/chat/components/LiveChatMessageContent/MessageReaction/index.tsx create mode 100644 src/v4/chat/components/LiveChatMessageContent/MessageReaction/styles.module.css create mode 100644 src/v4/chat/components/MessageQuickReaction/index.tsx create mode 100644 src/v4/chat/components/MessageQuickReaction/styles.module.css create mode 100644 src/v4/chat/components/MessageReactionPicker/index.tsx create mode 100644 src/v4/chat/components/MessageReactionPicker/livechatMessageReactionPicker.stories.tsx create mode 100644 src/v4/chat/components/MessageReactionPicker/styles.module.css create mode 100644 src/v4/chat/components/MessageReactionPreview/index.tsx create mode 100644 src/v4/chat/components/MessageReactionPreview/livechatMessageReactionPreview.stories.tsx create mode 100644 src/v4/chat/components/MessageReactionPreview/styles.module.css create mode 100644 src/v4/core/providers/CustomReactionProvider.tsx create mode 100644 src/v4/icons/FallbackReaction.tsx create mode 100644 src/v4/icons/QuickReactionIcon.tsx create mode 100644 src/v4/icons/ReactionListSkeleton.tsx create mode 100644 src/v4/icons/SmilePlus.tsx create mode 100644 src/v4/social/components/ReactionList/ReactionIcon.tsx create mode 100644 src/v4/social/components/ReactionList/ReactionListEmptyState.tsx create mode 100644 src/v4/social/components/ReactionList/ReactionListError.tsx create mode 100644 src/v4/social/components/ReactionList/ReactionListLoadingState.tsx create mode 100644 src/v4/social/components/ReactionList/ReactionListPanel.tsx create mode 100644 src/v4/utils/abbreviateCount.ts create mode 100644 src/v4/utils/selectMessageReaction.ts create mode 100644 static/message_reaction_fire.png create mode 100644 static/message_reaction_grinning.png create mode 100644 static/message_reaction_heart.png create mode 100644 static/message_reaction_like.png create mode 100644 static/message_reaction_sad.png diff --git a/.storybook/main.ts b/.storybook/main.ts index af637becd..29eb40d98 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -13,6 +13,7 @@ const config: StorybookConfig = { '@storybook/addon-a11y', ], framework: '@storybook/react-vite', + staticDirs: ['../static'], }; export default config; diff --git a/amity-uikit.config.json b/amity-uikit.config.json index f25286441..ce9f67eda 100644 --- a/amity-uikit.config.json +++ b/amity-uikit.config.json @@ -27,6 +27,13 @@ } }, "excludes": [], + "message_reactions": [ + { "name": "heart", "image": "message_reaction_heart.png" }, + { "name": "like", "image": "message_reaction_like.png" }, + { "name": "fire", "image": "message_reaction_fire.png" }, + { "name": "grinning", "image": "message_reaction_grinning.png" }, + { "name": "sad", "image": "message_reaction_sad.png" } + ], "customizations": { "select_target_page/*/*": { "theme": {}, @@ -200,6 +207,9 @@ "live_chat/message_composer/*": { "message_limit": 200, "placeholder_text": "Write a message" + }, + "live_chat_page/message_list/message_quick_reaction": { + "reaction": "heart" } } } diff --git a/src/i18n/en.json b/src/i18n/en.json index 2e77c1e71..154427c33 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -435,5 +435,8 @@ "livechat.error.tooLongMessage.description": "Your message is too long. Please shorten your message and try again.", "livechat.error.sendingMessage.error": "Your message wasn't sent. Please try again.", "livechat.error.sendingMessage.blockedWord": "Your message wasn't sent as it contains a blocked word.", - "livechat.error.sendingMessage.notAllowLink": "Your message wasn’t sent as it contained a link that’s not allowed." + "livechat.error.sendingMessage.notAllowLink": "Your message wasn’t sent as it contained a link that’s not allowed.", + "livechat.reaction.errorOnload": "Unable to load reactions", + "livechat.reaction.emptyState": "No reactions yet", + "livechat.reaction.emptyState.description": "Be the first to react to this message!" } diff --git a/src/index.ts b/src/index.ts index 5927767c4..3ff2fb0c9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -35,6 +35,10 @@ export { AmityLiveChatMessageComposeBar, } from '~/v4/chat/components'; +export { MessageReactionPreview as AmityLiveChatMessageReactionPreview } from '~/v4/chat/components/MessageReactionPreview'; +export { MessageReactionPicker as AmityLiveChatMessageReactionPicker } from '~/v4/chat/components/MessageReactionPicker'; +export { MessageQuickReaction as AmityLiveChatMessageQuickReaction } from '~/v4/chat/components/MessageQuickReaction'; + import type { AmityMessageActionType } from '~/v4/chat/components'; export type { AmityMessageActionType }; diff --git a/src/v4/chat/components/AmityLiveChatMessageList/index.tsx b/src/v4/chat/components/AmityLiveChatMessageList/index.tsx index 329b18e2f..34cbb6e9f 100644 --- a/src/v4/chat/components/AmityLiveChatMessageList/index.tsx +++ b/src/v4/chat/components/AmityLiveChatMessageList/index.tsx @@ -16,14 +16,18 @@ import { useLiveChatNotifications } from '~/v4/chat/providers/LiveChatNotificati import { useCopyMessage } from '~/v4/core/hooks'; interface AmityLiveChatMessageListProps { + pageId?: string; channel: Amity.Channel; replyMessage: (message: Amity.Message<'text'>) => void; } export const AmityLiveChatMessageList = ({ + pageId, channel, replyMessage, }: AmityLiveChatMessageListProps) => { + const componentId = 'message_list'; + const sdk = useSDK(); const containerRef = React.useRef(null); const { formatMessage } = useIntl(); @@ -132,6 +136,8 @@ export const AmityLiveChatMessageList = ({ } key={message.messageId} containerRef={containerRef} + pageId={pageId} + componentId={componentId} /> ); @@ -169,6 +175,8 @@ export const AmityLiveChatMessageList = ({ }} key={message.messageId} containerRef={containerRef} + pageId={pageId} + componentId={componentId} /> ); })} diff --git a/src/v4/chat/components/AmityLiveChatMessageQuickReaction/index.tsx b/src/v4/chat/components/AmityLiveChatMessageQuickReaction/index.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/src/v4/chat/components/AmityLiveChatMessageReceiverView/index.tsx b/src/v4/chat/components/AmityLiveChatMessageReceiverView/index.tsx index 4419aa1bd..74859a42a 100644 --- a/src/v4/chat/components/AmityLiveChatMessageReceiverView/index.tsx +++ b/src/v4/chat/components/AmityLiveChatMessageReceiverView/index.tsx @@ -5,12 +5,16 @@ import LiveChatMessageContent from '../LiveChatMessageContent'; import { AmityMessageActionType } from '../LiveChatMessageContent/MessageAction'; interface AmityLiveChatMessageReceiverViewProps { + pageId?: string; + componentId?: string; message: Amity.Message; containerRef: React.RefObject; action: AmityMessageActionType; } export const AmityLiveChatMessageReceiverView = ({ + pageId = '*', + componentId = '*', message, containerRef, action, @@ -20,6 +24,8 @@ export const AmityLiveChatMessageReceiverView = ({ return ( } userDisplayName={user?.displayName} avatarUrl={avatarFileUrl} diff --git a/src/v4/chat/components/AmityLiveChatMessageSenderView/index.tsx b/src/v4/chat/components/AmityLiveChatMessageSenderView/index.tsx index 5478e4ac4..1b5f8f870 100644 --- a/src/v4/chat/components/AmityLiveChatMessageSenderView/index.tsx +++ b/src/v4/chat/components/AmityLiveChatMessageSenderView/index.tsx @@ -5,12 +5,16 @@ import LiveChatMessageContent from '../LiveChatMessageContent'; import { AmityMessageActionType } from '../LiveChatMessageContent/MessageAction'; interface AmityLiveChatMessageSenderViewProps { + pageId?: string; + componentId?: string; message: Amity.Message; containerRef: React.RefObject; action?: AmityMessageActionType; } export const AmityLiveChatMessageSenderView = ({ + pageId = '*', + componentId = '*', message, containerRef, action, @@ -25,6 +29,8 @@ export const AmityLiveChatMessageSenderView = ({ avatarUrl={avatarFileUrl} containerRef={containerRef} action={action} + pageId={pageId} + componentId={componentId} /> ); }; diff --git a/src/v4/chat/components/LiveChatMessageContent/MessageReaction/index.tsx b/src/v4/chat/components/LiveChatMessageContent/MessageReaction/index.tsx new file mode 100644 index 000000000..4b6499e43 --- /dev/null +++ b/src/v4/chat/components/LiveChatMessageContent/MessageReaction/index.tsx @@ -0,0 +1,153 @@ +import React, { useCallback, useRef, useEffect, useState } from 'react'; +import { MessageQuickReaction } from '~/v4/chat/components/MessageQuickReaction'; +import { MessageReactionPicker } from '~/v4/chat/components/MessageReactionPicker'; +import { convertRemToPx, getCssVariableValue } from '~/v4/helpers/utils'; +import styles from './styles.module.css'; + +export const MessageReaction = ({ + pageId = '*', + componentId = '*', + message, + containerRef, +}: { + pageId?: string; + componentId?: string; + message: Amity.Message; + containerRef?: React.RefObject; +}) => { + const [isReactionPickerOpen, setIsReactionPickerOpen] = useState(false); + + const [isHoveredQuickReaction, setIsHoveredQuickReaction] = useState(false); + const [isHoveredReactionPicker, setIsHoveredReactionPicker] = useState(false); + + const [transform, setTransform] = React.useState(); + const ref = useRef(null); + + const isHoveredQuickReactionRef = useRef(isHoveredQuickReaction); + + useEffect(() => { + isHoveredQuickReactionRef.current = isHoveredQuickReaction; + }, [isHoveredQuickReaction]); + + const onOpenPicker = useCallback(() => { + if (!isHoveredQuickReactionRef.current) return; + + const timeoutId = setTimeout(() => { + if (isHoveredQuickReactionRef.current) { + setIsReactionPickerOpen(true); + } + }, 500); + + return () => clearTimeout(timeoutId); + }, []); + + const isHoveredReactionPickerRef = useRef(isHoveredReactionPicker); + + useEffect(() => { + isHoveredReactionPickerRef.current = isHoveredReactionPicker; + }, [isHoveredReactionPicker]); + + const onClosePicker = useCallback(() => { + setTimeout(() => { + if (!isHoveredReactionPickerRef.current) { + setIsReactionPickerOpen(false); + } + }, 1000); + }, []); + + const handleClickOutside = useCallback((event) => { + if (ref.current && !ref.current.contains(event.target)) { + setIsReactionPickerOpen(false); + } + }, []); + + const calculateTransform = () => { + if (!ref.current || !containerRef?.current) return 'translate(-50%, 0px)'; + + const parentRect = containerRef.current.getBoundingClientRect(); + const pickerRect = ref.current.getBoundingClientRect(); + + if (!parentRect || !pickerRect) return 'translate(-50%, 0px)'; + + const padding = convertRemToPx( + Number(getCssVariableValue('--asc-spacing-s1').replace('rem', '')), + ); + + const overflowRight = pickerRect.right - parentRect.right; + const overflowLeft = parentRect.left - pickerRect.left; + + if (overflowRight > 0) { + // If overflowing to the right, adjust the transform + return `translate(calc(-50% - ${overflowRight}px), 0px)`; + } else if (overflowLeft > 0) { + // If overflowing to the left, adjust the transform + return `translate(calc(-50% + ${overflowLeft}px), 0px)`; + } else { + // If not overflowing, keep the original transform + return 'translate(-50%, 0px)'; + } + }; + + const onMouseDown = (event: any) => { + handleClickOutside(event); + }; + + const onSelectReaction = () => { + setIsHoveredQuickReaction(false); + setIsHoveredReactionPicker(false); + setIsReactionPickerOpen(false); + }; + + useEffect(() => { + + document.addEventListener('mousedown', onMouseDown); + document.addEventListener('touchstart', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + document.removeEventListener('touchstart', handleClickOutside); + }; + }, []); + + useEffect(() => { + if (isHoveredQuickReaction) { + !isReactionPickerOpen && onOpenPicker(); + } else { + isReactionPickerOpen && onClosePicker(); + } + }, [isHoveredQuickReaction, isReactionPickerOpen]); + + return ( +
+ {isReactionPickerOpen && ( +
setIsHoveredReactionPicker(true)} + onMouseLeave={() => { + setIsHoveredReactionPicker(false); + }} + > + +
+ )} + +
{ + setIsHoveredQuickReaction(true); + }} + onMouseLeave={() => { + setIsHoveredQuickReaction(false); + }} + > + +
+
+ ); +}; diff --git a/src/v4/chat/components/LiveChatMessageContent/MessageReaction/styles.module.css b/src/v4/chat/components/LiveChatMessageContent/MessageReaction/styles.module.css new file mode 100644 index 000000000..6b1de54dc --- /dev/null +++ b/src/v4/chat/components/LiveChatMessageContent/MessageReaction/styles.module.css @@ -0,0 +1,15 @@ +.reactionContainer { + position: relative; +} + +.reactionPickerWrap { + position: absolute; + bottom: 1.5rem; + left: 0.625rem; + transform: translate(-50%, 0px); +} + +.reactionButton { + width: 1.25rem; + height: 1.25rem; +} diff --git a/src/v4/chat/components/LiveChatMessageContent/index.tsx b/src/v4/chat/components/LiveChatMessageContent/index.tsx index 40aa71f56..01bed88fc 100644 --- a/src/v4/chat/components/LiveChatMessageContent/index.tsx +++ b/src/v4/chat/components/LiveChatMessageContent/index.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import styles from './styles.module.css'; import { Typography } from '~/v4/core/components'; import MessageAction, { AmityMessageActionType } from './MessageAction'; @@ -9,8 +9,14 @@ import useSDK from '~/core/hooks/useSDK'; import MessageBubble from './MessageBubble'; import useChannelPermission from '~/v4/chat/hooks/useChannelPermission'; import Flag from '~/v4/icons/Flag'; +import { MessageReaction } from './MessageReaction'; +import { MessageReactionPreview } from '~/v4/chat/components/MessageReactionPreview'; +import Sheet from 'react-modal-sheet'; +import { ReactionList } from '~/v4/social/components'; interface MessageItemProps { + pageId?: string; + componentId?: string; message: Amity.Message<'text'>; userDisplayName?: string; avatarUrl?: string; @@ -19,6 +25,8 @@ interface MessageItemProps { } const LiveChatMessageContent = ({ + pageId = '*', + componentId = '*', message, avatarUrl, userDisplayName, @@ -29,41 +37,76 @@ const LiveChatMessageContent = ({ const sdk = useSDK(); const isOwner = message.creatorId === sdk.currentUserId; const { isModerator } = useChannelPermission(message.channelId); + const [openReactionPanel, setOpenReactionPanel] = useState(undefined); return ( - -
- {message.isDeleted ? ( -
- -
- - {formatMessage({ - id: 'livechat.deleted.message', - })} - + <> + +
+ {message.isDeleted ? ( +
+ +
+ + {formatMessage({ + id: 'livechat.deleted.message', + })} + +
-
- ) : ( -
- - {action && ( - + +
+ setOpenReactionPanel(message)} + /> +
+ {action && ( + 0} + /> + )} + 0} + pageId={pageId} + componentId={componentId} /> - )} - {message.flagCount > 0 && } -
- + {message.flagCount > 0 && } +
+ +
-
- )} -
- + )} +
+ + + {openReactionPanel && ( + setOpenReactionPanel(undefined)} + className={styles.reactionListSheet} + > + + + + + + + + + )} + ); }; diff --git a/src/v4/chat/components/LiveChatMessageContent/styles.module.css b/src/v4/chat/components/LiveChatMessageContent/styles.module.css index 26529261c..2b92e5f8e 100644 --- a/src/v4/chat/components/LiveChatMessageContent/styles.module.css +++ b/src/v4/chat/components/LiveChatMessageContent/styles.module.css @@ -4,12 +4,25 @@ gap: var(--asc-spacing-s1); } +.messageItemContainerInner { + position: relative; +} + +.messageItemContainerInner[data-reactions='true'] { + margin-bottom: 1.313rem; +} + .messageBubbleWrap { display: flex; align-items: flex-end; gap: var(--asc-spacing-xxs3); } +.messageReaction { + position: absolute; + bottom: -1.313rem; +} + .messageDeletedBubble { display: flex; align-items: center; @@ -19,8 +32,8 @@ gap: var(--asc-spacing-xxs2); border-radius: var(--asc-border-radius-lg); color: var( - --live-chat-message-list-asc-color-base-inverse, - var(--live-chat-asc-color-base-inverse, var(--asc-color-base-inverse)) + --live-chat-message-list-asc-color-base-inverse, + var(--live-chat-asc-color-base-inverse, var(--asc-color-base-inverse)) ); } @@ -34,8 +47,8 @@ height: 1rem; width: 1rem; fill: var( - --live-chat-message-list-asc-color-base-inverse, - var(--live-chat-asc-color-base-inverse, var(--asc-color-base-inverse)) + --live-chat-message-list-asc-color-base-inverse, + var(--live-chat-asc-color-base-inverse, var(--asc-color-base-inverse)) ); } @@ -44,11 +57,6 @@ height: 1.25rem; } -.reactionIcon { - width: 1.25rem; - height: 1.25rem; -} - .flagIcon { height: 1rem; } @@ -56,8 +64,8 @@ .timestamp { font-family: var(--asc-text-global-font-family); color: var( - --live-chat-message-list-asc-color-base-shade2, - var(--live-chat-asc-color-base-shade2, var(--asc-color-base-shade2)) + --live-chat-message-list-asc-color-base-shade2, + var(--live-chat-asc-color-base-shade2, var(--asc-color-base-shade2)) ); margin-bottom: var(--asc-spacing-s1); @@ -65,3 +73,23 @@ font-size: 0.5rem; line-height: 0.75rem; } + +.reactionListSheet { + z-index: 1001 !important; + max-height: 85%; + top: 15% !important; + + @media (min-width: 768px) { + width: 375px !important; + } +} + +.reactionListContainer { + height: 100% !important; + background-color: var(--asc-color-base-background) !important; +} + +.reactionListBackdrop { + background-color: var(--asc-color-black) !important; + opacity: 0.5 !important; +} \ No newline at end of file diff --git a/src/v4/chat/components/MessageQuickReaction/index.tsx b/src/v4/chat/components/MessageQuickReaction/index.tsx new file mode 100644 index 000000000..33a350d58 --- /dev/null +++ b/src/v4/chat/components/MessageQuickReaction/index.tsx @@ -0,0 +1,44 @@ +import React, { useCallback } from 'react'; +import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; +import { useCustomReaction } from '~/v4/core/providers/CustomReactionProvider'; +import { QuickReactionIcon } from '~/v4/icons/QuickReactionIcon'; +import { selectMessageReaction } from '~/v4/utils/selectMessageReaction'; +import styles from './styles.module.css'; + +interface MessageQuickReactionProps { + pageId?: string; + componentId?: string; + message: Amity.Message; + onSelectReaction?: () => void; +} + +export const MessageQuickReaction = ({ + pageId = '*', + componentId = '*', + message, + onSelectReaction, +}: MessageQuickReactionProps) => { + const elementId = 'message_quick_reaction'; + const { config: reactionConfig } = useCustomReaction(); + const { getConfig } = useCustomization(); + + const elementConfig = getConfig(`${pageId}/${componentId}/${elementId}`); + + const onClickQuickReaction = useCallback(() => { + if ( + reactionConfig && + elementConfig.reaction && + reactionConfig.find((config) => config.name === elementConfig.reaction) + ) { + selectMessageReaction({ reactionName: elementConfig.reaction, message }); + } + + onSelectReaction && onSelectReaction(); + }, [reactionConfig, elementConfig, message]); + + return ( +
+ +
+ ); +}; diff --git a/src/v4/chat/components/MessageQuickReaction/styles.module.css b/src/v4/chat/components/MessageQuickReaction/styles.module.css new file mode 100644 index 000000000..ce96cb230 --- /dev/null +++ b/src/v4/chat/components/MessageQuickReaction/styles.module.css @@ -0,0 +1,18 @@ +.quickReactionIcon { + width: 1.25rem; + height: 1.25rem; + color: var(--asc-color-base-shade2); +} + +.quickReactionIconContainer { + display: flex; +} + +.quickReactionIconContainer:hover { + cursor: pointer; + background-color: var( + --live-chat-message-list-asc-color-secondary-shade4, + var(--live-chat-asc-color-secondary-shade4, var(--asc-color-secondary-shade4)) + ); + border-radius: var(--asc-border-radius-sm); +} diff --git a/src/v4/chat/components/MessageReactionPicker/index.tsx b/src/v4/chat/components/MessageReactionPicker/index.tsx new file mode 100644 index 000000000..6f2feb347 --- /dev/null +++ b/src/v4/chat/components/MessageReactionPicker/index.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { AmityReactionType, useCustomReaction } from '~/v4/core/providers/CustomReactionProvider'; +import { selectMessageReaction } from '~/v4/utils/selectMessageReaction'; +import styles from './styles.module.css'; + +export const MessageReactionPicker = ({ + message, + onSelectReaction, +}: { + message: Amity.Message; + onSelectReaction: (reactionName: string) => void; +}) => { + const { config } = useCustomReaction(); + + const onClickReaction = (reactionName: AmityReactionType['name']) => { + selectMessageReaction({ reactionName, message }); + }; + + if (!config) return null; + + return ( +
+ {config.map((reaction) => { + return ( + {reaction.name} { + onClickReaction(reaction.name); + onSelectReaction && onSelectReaction(reaction.name); + }} + /> + ); + })} +
+ ); +}; diff --git a/src/v4/chat/components/MessageReactionPicker/livechatMessageReactionPicker.stories.tsx b/src/v4/chat/components/MessageReactionPicker/livechatMessageReactionPicker.stories.tsx new file mode 100644 index 000000000..3e4a42805 --- /dev/null +++ b/src/v4/chat/components/MessageReactionPicker/livechatMessageReactionPicker.stories.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { MessageReactionPicker } from '.'; + +export default { + title: 'V4/MessageReactionPicker', +}; + +const message = { + reactions: { + like: 1, + angry: 2, + haha: 3, + wow: 4, + sad: 5, + heart: 6, + }, + reactionsCount: 21, + myReactions: ['grinning'], +}; + +export const liveChatMessageReactionPicker = { + render: () => ( +
+
+ {}} /> +
+
+ ), + name: 'MessageReactionPicker', +}; diff --git a/src/v4/chat/components/MessageReactionPicker/styles.module.css b/src/v4/chat/components/MessageReactionPicker/styles.module.css new file mode 100644 index 000000000..f74b32273 --- /dev/null +++ b/src/v4/chat/components/MessageReactionPicker/styles.module.css @@ -0,0 +1,31 @@ +.reactionPickerContainer { + display: flex; + cursor: pointer; + background-color: var(--asc-color-base-shade4); + border-radius: var(--asc-border-radius-full); + gap: var(--asc-spacing-xxs3); + width: max-content; + padding: 0.313rem 0.438rem; +} + +.reactionButton { + width: 2rem; + height: 2rem; + border: 0.313rem var(--asc-color-base-shade4) solid; + border-radius: var(--asc-border-radius-full); +} + +.reactionButton[data-active='true'] { + border-radius: var(--asc-border-radius-full); + border: 0.313rem var(--asc-color-base-shade1) solid; +} + +.reactionButton[data-active='true']:hover { + border-radius: var(--asc-border-radius-full); + border: 0.313rem var(--asc-color-base-shade1) solid; +} + +.reactionButton:hover { + border-radius: var(--asc-border-radius-full); + border: 0.313rem var(--asc-color-secondary-shade3) solid; +} diff --git a/src/v4/chat/components/MessageReactionPreview/index.tsx b/src/v4/chat/components/MessageReactionPreview/index.tsx new file mode 100644 index 000000000..fc5d22f88 --- /dev/null +++ b/src/v4/chat/components/MessageReactionPreview/index.tsx @@ -0,0 +1,61 @@ +import React, { useMemo } from 'react'; +import styles from './styles.module.css'; +import { useCustomReaction } from '~/v4/core/providers/CustomReactionProvider'; +import FallbackReaction from '~/v4/icons/FallbackReaction'; +import { abbreviateCount } from '~/v4/utils/abbreviateCount'; + +export const MessageReactionPreview = ({ + message, + onClick, +}: { + message: Amity.Message; + onClick?: () => void; +}) => { + const { config: reactionConfig } = useCustomReaction(); + // find the top 3 reactions + const topReactions = useMemo( + () => + Object.entries(message.reactions) + .sort((a, b) => b[1] - a[1]) // sort by value in descending order + // remove reaction that has zero value + .filter((reaction) => reaction[1] > 0) + .slice(0, 3) + .sort((a, b) => a[1] - b[1]), + [message.reactions], + ); + + if (!message.reactionsCount) return null; + + return ( +
+
+ {topReactions.map((reaction) => { + const reactionMapConfig = reactionConfig.find((config) => config.name === reaction[0]); + return ( + <> + {reactionMapConfig ? ( + {reactionMapConfig.name} + ) : ( + + )} + + ); + })} +
+
{abbreviateCount(message.reactionsCount)}
+
+ ); +}; diff --git a/src/v4/chat/components/MessageReactionPreview/livechatMessageReactionPreview.stories.tsx b/src/v4/chat/components/MessageReactionPreview/livechatMessageReactionPreview.stories.tsx new file mode 100644 index 000000000..e0efcf5b4 --- /dev/null +++ b/src/v4/chat/components/MessageReactionPreview/livechatMessageReactionPreview.stories.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { MessageReactionPreview } from './'; + +export default { + title: 'V4/MessageReactionPreview', +}; + +const message = { + reactions: { + like: 1, + angry: 2, + haha: 3, + wow: 4, + sad: 5, + heart: 6, + }, + reactionsCount: 21, + myReactions: ['grinning'], +}; + +export const liveChatMessageReactionPreview = { + render: () => ( +
+
+ +
+
+ ), + name: 'MessageReactionPreview', +}; diff --git a/src/v4/chat/components/MessageReactionPreview/styles.module.css b/src/v4/chat/components/MessageReactionPreview/styles.module.css new file mode 100644 index 000000000..b8fd6addb --- /dev/null +++ b/src/v4/chat/components/MessageReactionPreview/styles.module.css @@ -0,0 +1,50 @@ +.reactionPreviewContainer { + display: flex; + flex-direction: row; + align-items: center; + background-color: var(--asc-color-base-shade3); + border: 1px solid var(--asc-color-secondary-shade4); + padding: var(--asc-spacing-xxs2) var(--asc-spacing-xxs3); + border-radius: var(--asc-border-radius-full); +} + +.reactionPreviewContainer[data-myreaction='true'] { + background-color: var(--asc-color-primary-default); + border: none; +} + +.reactionIconContainer { + display: flex; + flex-direction: row-reverse; + align-items: center; +} + +.reactionIconContainer > :not(:first-child) { + margin-right: calc(var(--asc-spacing-s1) * -1); +} + +.reactionIconContainer > :first-child { + transform: translateX(0rem); +} + +.reactionIconContainer > :nth-child(2) { + transform: translateX(0rem); +} + +.reactionIconContainer > :last-child { + transform: translateX(0rem); +} + +.reactionIcon { + width: 1.25rem; + height: 1.25rem; +} + +.reactionCount { + margin-left: var(--asc-spacing-xxs1); + color: var(--asc-color-base-inverse); +} + +.fallbackIcon { + color: var(--asc-color-base-shade1); +} diff --git a/src/v4/chat/components/index.ts b/src/v4/chat/components/index.ts index 777c787b3..9b139945e 100644 --- a/src/v4/chat/components/index.ts +++ b/src/v4/chat/components/index.ts @@ -4,5 +4,9 @@ export { AmityLiveChatMessageSenderView } from './AmityLiveChatMessageSenderView export { AmityLiveChatMessageList } from './AmityLiveChatMessageList'; export { AmityLiveChatMessageComposeBar } from './AmityLiveChatMessageComposeBar'; +export { MessageReactionPicker } from './MessageReactionPicker'; +export { MessageQuickReaction } from './MessageQuickReaction'; +export { MessageReactionPreview } from './MessageReactionPreview'; + import type { AmityMessageActionType } from './LiveChatMessageContent/MessageAction'; export type { AmityMessageActionType }; diff --git a/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ChatReadyState.tsx b/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ChatReadyState.tsx index 84eef0373..58c46e592 100644 --- a/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ChatReadyState.tsx +++ b/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ChatReadyState.tsx @@ -16,7 +16,7 @@ import useSearchChannelUser from '~/v4/chat/hooks/collections/useSearchChannelUs import useSDK from '~/core/hooks/useSDK'; import useChannelPermission from '~/v4/chat/hooks/useChannelPermission'; -const ChatReadyState = ({ channel }: { channel: Amity.Channel }) => { +const ChatReadyState = ({ pageId = '*', channel }: { pageId?: string; channel: Amity.Channel }) => { const isOnline = useConnectionStates(); const { isModerator } = useChannelPermission(channel.channelId); @@ -59,7 +59,7 @@ const ChatReadyState = ({ channel }: { channel: Amity.Channel }) => { return ( <> - + {isOnline && ( <> diff --git a/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/index.tsx b/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/index.tsx index ee5b4208f..aa1b8c735 100644 --- a/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/index.tsx +++ b/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/index.tsx @@ -2,9 +2,15 @@ import React from 'react'; import ChatLoadingState from './ChatLoadingState'; import ChatReadyState from './ChatReadyState'; -const ChatContainer = ({ channel }: { channel: Amity.Channel | null }) => { +const ChatContainer = ({ + pageId = '*', + channel, +}: { + pageId?: string; + channel: Amity.Channel | null; +}) => { if (!channel) return ; - return ; + return ; }; export default ChatContainer; diff --git a/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/styles.module.css b/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/styles.module.css index e5884d283..6be928595 100644 --- a/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/styles.module.css +++ b/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/styles.module.css @@ -37,8 +37,8 @@ border-radius: var(--asc-border-radius-sm); border: var(--asc-border-radius-none); background-color: var( - --live-chat-asc-color-secondary-default, - var(--asc-color-secondary-default) + --live-chat-asc-color-secondary-default, + var(--asc-color-secondary-default) ); color: var(--live-chat-asc-color-base-inverse, var(--asc-color-base-inverse)); display: flex; @@ -133,4 +133,4 @@ gap: var(--asc-spacing-s2); padding: var(--asc-spacing-m1); color: var(--live-chat-asc-color-base-shade2, var(--asc-color-base-shade2)); -} +} \ No newline at end of file diff --git a/src/v4/chat/pages/AmityLiveChatPage/index.tsx b/src/v4/chat/pages/AmityLiveChatPage/index.tsx index b9c9dcca2..18321dd20 100644 --- a/src/v4/chat/pages/AmityLiveChatPage/index.tsx +++ b/src/v4/chat/pages/AmityLiveChatPage/index.tsx @@ -12,6 +12,7 @@ interface AmityLiveChatPageProps { export const AmityLiveChatPage = ({ channelId }: AmityLiveChatPageProps) => { const channel = useChannel(channelId); const ref = useRef(null); + const pageId = 'live_chat_page'; return ( @@ -19,7 +20,7 @@ export const AmityLiveChatPage = ({ channelId }: AmityLiveChatPageProps) => {
- +
); diff --git a/src/v4/core/components/Typography/Typography.tsx b/src/v4/core/components/Typography/Typography.tsx index fbc295f91..7eff4074b 100644 --- a/src/v4/core/components/Typography/Typography.tsx +++ b/src/v4/core/components/Typography/Typography.tsx @@ -15,61 +15,86 @@ const Typography: React.FC & { BodyBold: React.FC; Caption: React.FC; CaptionBold: React.FC; -} = ({ children, className = '' }) => { - return
{children}
; +} = ({ children, className = '', ...props }) => { + return ( +
+ {children} +
+ ); }; -Typography.Heading = ({ children, className = '' }) => { +Typography.Heading = ({ children, className = '', ...props }) => { return ( -

+

{children}

); }; -Typography.Title = ({ children, className = '' }) => { +Typography.Title = ({ children, className = '', ...props }) => { return ( -

+

{children}

); }; -Typography.Subtitle = ({ children, className = '' }) => { +Typography.Subtitle = ({ children, className = '', ...props }) => { return ( -

+

{children}

); }; -Typography.Body = ({ children, className = '' }) => { +Typography.Body = ({ children, className = '', ...props }) => { return ( -

+

{children}

); }; -Typography.BodyBold = ({ children, className = '' }) => { +Typography.BodyBold = ({ children, className = '', ...props }) => { return ( -

+

{children}

); }; -Typography.Caption = ({ children, className = '' }) => { +Typography.Caption = ({ children, className = '', ...props }) => { return ( -

+

{children}

); }; -Typography.CaptionBold = ({ children, className = '' }) => { +Typography.CaptionBold = ({ children, className = '', ...props }) => { return ( -

+

{children}

); diff --git a/src/v4/core/providers/AmityUIKitProvider.tsx b/src/v4/core/providers/AmityUIKitProvider.tsx index d7f7b4a97..7e8188e2b 100644 --- a/src/v4/core/providers/AmityUIKitProvider.tsx +++ b/src/v4/core/providers/AmityUIKitProvider.tsx @@ -30,6 +30,7 @@ import { ConfirmProvider } from '~/v4/core/providers/ConfirmProvider'; import { ConfirmProvider as LegacyConfirmProvider } from '~/core/providers/ConfirmProvider'; import { NotificationProvider } from '~/v4/core/providers/NotificationProvider'; import { NotificationProvider as LegacyNotificationProvider } from '~/core/providers/NotificationProvider'; +import { CustomReactionProvider } from './CustomReactionProvider'; export type AmityUIKitConfig = Config; @@ -138,38 +139,40 @@ const AmityUIKitProvider: React.FC = ({ - - - - - - - - - - - - {children} - - - - - - - - - - - - - - - + + + + + + + + + + + + + {children} + + + + + + + + + + + + + + + + diff --git a/src/v4/core/providers/CustomReactionProvider.tsx b/src/v4/core/providers/CustomReactionProvider.tsx new file mode 100644 index 000000000..7c18eb89b --- /dev/null +++ b/src/v4/core/providers/CustomReactionProvider.tsx @@ -0,0 +1,32 @@ +import React, { createContext, useContext } from 'react'; +import { useCustomization } from './CustomizationProvider'; + +export type AmityReactionType = { + name: string; + image: string; +}; + +const CustomReactionContext = createContext([]); + +export const useCustomReaction = () => { + const config = useContext(CustomReactionContext); + return { config }; +}; + +export const CustomReactionProvider: React.FC = ({ children }) => { + const { config } = useCustomization(); + const [reactions, setReactions] = React.useState([]); + + React.useEffect(() => { + if (!config) return; + + const reactionConfig = config?.message_reactions; + if (!reactionConfig) return; + + setReactions(reactionConfig); + }, [config]); + + return ( + {children} + ); +}; diff --git a/src/v4/core/providers/CustomizationProvider.tsx b/src/v4/core/providers/CustomizationProvider.tsx index 52a385c4e..fde68dca0 100644 --- a/src/v4/core/providers/CustomizationProvider.tsx +++ b/src/v4/core/providers/CustomizationProvider.tsx @@ -1,4 +1,5 @@ import React, { createContext, useContext, useState, useEffect } from 'react'; +import { AmityReactionType } from './CustomReactionProvider'; interface CustomizationContextValue { config: Config | null; @@ -41,6 +42,7 @@ export interface Config { dark?: Theme['dark']; }; excludes?: string[]; + message_reactions?: AmityReactionType[]; customizations?: { 'select_target_page/*/*'?: { theme?: { diff --git a/src/v4/helpers/utils.ts b/src/v4/helpers/utils.ts index 0c7e1c4d0..9afa54300 100644 --- a/src/v4/helpers/utils.ts +++ b/src/v4/helpers/utils.ts @@ -164,3 +164,11 @@ export function parseMentionsMarkup( export function isNonNullable(value: TValue | undefined | null): value is TValue { return value != null; } + +export function getCssVariableValue(variable: string) { + return getComputedStyle(document.documentElement).getPropertyValue(variable); +} + +export function convertRemToPx(rem: number) { + return rem * parseFloat(getComputedStyle(document.documentElement).fontSize); +} diff --git a/src/v4/icons/FallbackReaction.tsx b/src/v4/icons/FallbackReaction.tsx new file mode 100644 index 000000000..4aca57bcd --- /dev/null +++ b/src/v4/icons/FallbackReaction.tsx @@ -0,0 +1,28 @@ +import React from 'react'; + +interface FallbackIconProps extends React.SVGProps { + backgroundColor?: string; +} + +const FallbackReaction = ({ backgroundColor, ...props }: FallbackIconProps) => ( + + + + +); + +export default FallbackReaction; diff --git a/src/v4/icons/QuickReactionIcon.tsx b/src/v4/icons/QuickReactionIcon.tsx new file mode 100644 index 000000000..7fd55731f --- /dev/null +++ b/src/v4/icons/QuickReactionIcon.tsx @@ -0,0 +1,27 @@ +import React from 'react'; + +export const QuickReactionIcon = (props: React.SVGProps) => { + return ( + + + + + ); +}; diff --git a/src/v4/icons/ReactionListSkeleton.tsx b/src/v4/icons/ReactionListSkeleton.tsx new file mode 100644 index 000000000..301439259 --- /dev/null +++ b/src/v4/icons/ReactionListSkeleton.tsx @@ -0,0 +1,17 @@ +import React from 'react'; + +const Svg = (props: React.SVGProps) => ( + + + + +); + +export default Svg; diff --git a/src/v4/icons/SmilePlus.tsx b/src/v4/icons/SmilePlus.tsx new file mode 100644 index 000000000..d09da778b --- /dev/null +++ b/src/v4/icons/SmilePlus.tsx @@ -0,0 +1,19 @@ +import React from 'react'; + +const Svg = (props: React.SVGProps) => ( + + + +); + +export default Svg; diff --git a/src/v4/social/components/ReactionList/ReactionIcon.tsx b/src/v4/social/components/ReactionList/ReactionIcon.tsx new file mode 100644 index 000000000..b1359a7aa --- /dev/null +++ b/src/v4/social/components/ReactionList/ReactionIcon.tsx @@ -0,0 +1,20 @@ +import { AmityReactionType } from '~/v4/core/providers/CustomReactionProvider'; +import React from 'react'; + +export const ReactionIcon = ({ + reactionConfigItem, + className, +}: { + reactionConfigItem: AmityReactionType; + className: string; +}) => { + return ( + {reactionConfigItem.name} + ); +}; diff --git a/src/v4/social/components/ReactionList/ReactionList.module.css b/src/v4/social/components/ReactionList/ReactionList.module.css index 484874fd7..6f1c28c47 100644 --- a/src/v4/social/components/ReactionList/ReactionList.module.css +++ b/src/v4/social/components/ReactionList/ReactionList.module.css @@ -1,72 +1,119 @@ .reactionListContainer { display: flex; flex-direction: column; - gap: var(--asc-spacing-m); + gap: var(--asc-spacing-m1); + height: 100%; } .tabList { display: flex; - justify-content: flex-start; - align-items: center; - border-bottom: 1px solid var(--asc-color-neutral-shade2); - margin-bottom: 1rem; + gap: var(--asc-spacing-s2); + border-bottom: 1px solid var(--asc-color-base-shade4); + overflow: auto; } .tabItem { cursor: pointer; - padding: 0.5rem 1rem; - position: relative; - transition: color 0.3s ease; + padding: var(--asc-spacing-xxs2) var(--asc-spacing-s1); + background: var(--asc-color-base-background); + color: var(--asc-color-base-shade6); + padding-bottom: var(--asc-spacing-s1); + border-bottom: transparent; +} + +.tabItem.active { + color: var(--asc-color-primary-default); + border-bottom: 1px solid var(--asc-color-primary-default); +} + +.reactionEmoji { + display: flex; + align-items: center; + gap: var(--asc-spacing-s1); } -.tabItem::after { - content: ''; - position: absolute; - bottom: -1px; - left: 0; +.userList { + display: flex; + flex-wrap: wrap; + gap: var(--asc-spacing-s1); +} + +.userItem { + display: flex; + align-items: center; + gap: var(--asc-spacing-s1); + background: var(--asc-color-base-background); + padding: var(--asc-spacing-s1); + border-radius: var(--asc-border-radius-sm); width: 100%; - height: 2px; - background-color: transparent; - transition: background-color 0.3s ease; + border-bottom: 1px solid var(--asc-color-base-shade4); } -.tabItem.active { - color: var(--asc-color-primary-default); +.userDetailsContainer { + display: flex; + color: var(--asc-color-base-default); + justify-content: space-between; + width: 100%; } -.tabItem.active::after { - background-color: var(--asc-color-primary-default); +.userDetailsProfile { + display: flex; + gap: var(--asc-spacing-s2); + align-items: center; } -.reactionEmoji { +.userDetailsReaction { display: flex; align-items: center; - gap: 0.5rem; } -.tabCount { - color: var(--asc-color-base-shade2); - transition: color 0.3s ease; +.reactionItem { + width: 1.25rem; } -.tabItem.active .tabCount { - color: var(--asc-color-primary-default); +.reactionIcon { + width: 1.5rem; } -.userList { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); - gap: 1rem; +.reactionCustomStateContainer { + display: flex; + width: 100%; + height: 100%; + align-items: center; + justify-content: center; + color: var(--asc-color-base-shade2) } -.userItem { +.reactionCustomStateContainer.loadingState { + flex-direction: column; + justify-content: start; + align-items: flex-start; + animation: skeletonPulse 1.5s ease-in-out infinite; +} + +.reactionState { display: flex; + flex-direction: column; align-items: center; - gap: 0.5rem; + gap: var(--asc-spacing-s2); } -.userDetailsContainer { +.reactionState2Line { display: flex; + flex-direction: column; align-items: center; - gap: 0.5rem; + gap: var(--asc-spacing-xxs2); } + + +@keyframes skeletonPulse { + 0% { + opacity: 0.6; + } + 50% { + opacity: 1; + } + 100% { + opacity: 0.6; + } +} \ No newline at end of file diff --git a/src/v4/social/components/ReactionList/ReactionList.tsx b/src/v4/social/components/ReactionList/ReactionList.tsx index af25bb11f..b6d6a9b3d 100644 --- a/src/v4/social/components/ReactionList/ReactionList.tsx +++ b/src/v4/social/components/ReactionList/ReactionList.tsx @@ -1,22 +1,52 @@ -import React, { Fragment, useState } from 'react'; -import { FireIcon, HeartIcon, LikedIcon } from '~/icons'; +import React, { useState } from 'react'; import styles from './ReactionList.module.css'; import { useReactionsCollection } from '~/v4/social/hooks/collections/useReactionsCollection'; -import { Avatar, Typography } from '~/v4/core/components'; +import { Typography } from '~/v4/core/components'; +import { useCustomReaction } from '~/v4/core/providers/CustomReactionProvider'; +import { abbreviateCount } from '~/v4/utils/abbreviateCount'; +import { ReactionIcon } from '~/v4/social/components/ReactionList/ReactionIcon'; +import { ReactionListPanel } from '~/v4/social/components/ReactionList/ReactionListPanel'; +import { ReactionListError } from '~/v4/social/components/ReactionList/ReactionListError'; +import { ReactionListEmptyState } from '~/v4/social/components/ReactionList/ReactionListEmptyState'; +import { ReactionListLoadingState } from '~/v4/social/components/ReactionList/ReactionListLoadingState'; interface ReactionListProps { + pageId: string; referenceId: string; referenceType: Amity.ReactableType; } -type ReactionType = 'like' | 'love' | 'fire'; -type ReactionTabType = ReactionType | 'All'; +const RenderCondition = ({ + filteredReactions, + isLoading, + error, +}: { + filteredReactions: Amity.Reactor[]; + isLoading: boolean; + error: Error | null; +}) => { + if (isLoading) { + return ; + } -export const ReactionList = ({ referenceId, referenceType }: ReactionListProps) => { - const { reactions } = useReactionsCollection({ referenceId, referenceType }); + if (error) { + return ; + } + + if (filteredReactions.length === 0) { + return ; + } + + return ; +}; + +export const ReactionList = ({ pageId = '*', referenceId, referenceType }: ReactionListProps) => { + const componentId = 'reaction_list'; + const { reactions, error, isLoading } = useReactionsCollection({ referenceId, referenceType }); const [activeTab, setActiveTab] = useState('All'); + const { config } = useCustomReaction(); - const handleTabClick = (tab: ReactionTabType) => { + const handleTabClick = (tab: string) => { setActiveTab(tab); }; @@ -25,70 +55,48 @@ export const ReactionList = ({ referenceId, referenceType }: ReactionListProps) ? reactions : reactions.filter((reaction) => reaction.reactionName === activeTab.toLowerCase()); - if (reactions == null) return null; + if (reactions == null || !config) return null; return ( -
-
+
+
handleTabClick('All')} > - - - All {reactions.length} - - + + All {abbreviateCount(reactions.length)} +
- {(['like', 'love', 'fire'] as ReactionType[]).map((reactionType) => { + + {config.map((reactionConfigItem) => { + const { name: reactionType, image } = reactionConfigItem; + const count = reactions.filter( (reaction) => reaction.reactionName === reactionType, ).length; + return (
handleTabClick(reactionType)} > - {reactionType === 'like' && ( - - - {count} - - - )} - {reactionType === 'love' && ( - - - {count} - - - )} - {reactionType === 'fire' && ( - - - {count} - - - )} + + + + {abbreviateCount(count)} + +
); })}
-
- {filteredReactions.map((reaction) => { - return ( - -
-
- - {reaction.user?.displayName} -
-
-
- ); - })} -
+ +
); }; diff --git a/src/v4/social/components/ReactionList/ReactionListEmptyState.tsx b/src/v4/social/components/ReactionList/ReactionListEmptyState.tsx new file mode 100644 index 000000000..a238af1aa --- /dev/null +++ b/src/v4/social/components/ReactionList/ReactionListEmptyState.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { FormattedMessage } from 'react-intl'; +import styles from './ReactionList.module.css'; +import { Typography } from '~/v4/core/components'; +import SmilePlus from '~/v4/icons/SmilePlus'; + +export const ReactionListEmptyState = () => { + return ( +
+
+ +
+ + + + + + +
+
+
+ ); +}; diff --git a/src/v4/social/components/ReactionList/ReactionListError.tsx b/src/v4/social/components/ReactionList/ReactionListError.tsx new file mode 100644 index 000000000..7510eef0d --- /dev/null +++ b/src/v4/social/components/ReactionList/ReactionListError.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { FormattedMessage } from 'react-intl'; +import styles from './ReactionList.module.css'; +import { Typography } from '~/v4/core/components'; +import Redo from '~/v4/icons/Redo'; + +export const ReactionListError = () => { + return ( +
+
+ + + + +
+
+ ); +}; diff --git a/src/v4/social/components/ReactionList/ReactionListLoadingState.tsx b/src/v4/social/components/ReactionList/ReactionListLoadingState.tsx new file mode 100644 index 000000000..57fd91356 --- /dev/null +++ b/src/v4/social/components/ReactionList/ReactionListLoadingState.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import styles from './ReactionList.module.css'; +import ReactionListSkeleton from '~/v4/icons/ReactionListSkeleton'; +import clsx from 'clsx'; + +export const ReactionListLoadingState = () => { + return ( +
+ {Array.from({ length: 8 }).map((_, index) => ( + + ))} +
+ ); +}; diff --git a/src/v4/social/components/ReactionList/ReactionListPanel.tsx b/src/v4/social/components/ReactionList/ReactionListPanel.tsx new file mode 100644 index 000000000..b268bf450 --- /dev/null +++ b/src/v4/social/components/ReactionList/ReactionListPanel.tsx @@ -0,0 +1,53 @@ +import styles from './ReactionList.module.css'; +import React, { Fragment, useMemo } from 'react'; +import { Avatar, Typography } from '~/v4/core/components'; +import FallbackReaction from '~/v4/icons/FallbackReaction'; +import { ReactionIcon } from '~/v4/social/components/ReactionList/ReactionIcon'; +import { useCustomReaction } from '~/v4/core/providers/CustomReactionProvider'; + +export const ReactionListPanel = ({ + filteredReactions, +}: { + filteredReactions: Amity.Reactor[]; +}) => { + const { config } = useCustomReaction(); + const reactionList = useMemo(() => config.map(({ name }) => name), [config]); + + return ( +
+ {filteredReactions.map((reaction) => { + return ( + +
+
+
+ + + {reaction.user?.displayName} + +
+ +
+ {reactionList.includes(reaction.reactionName) ? ( + name === reaction.reactionName)! + } + className={styles.reactionIcon} + /> + ) : ( + + )} +
+
+
+
+ ); + })} +
+ ); +}; diff --git a/src/v4/utils/abbreviateCount.ts b/src/v4/utils/abbreviateCount.ts new file mode 100644 index 000000000..118a6615c --- /dev/null +++ b/src/v4/utils/abbreviateCount.ts @@ -0,0 +1,9 @@ +export const abbreviateCount = (count: number) => { + if (count >= 1000000) { + return (count / 1000000).toFixed(1) + 'M'; + } else if (count >= 1000) { + return (count / 1000).toFixed(1) + 'K'; + } else { + return count; + } +}; diff --git a/src/v4/utils/selectMessageReaction.ts b/src/v4/utils/selectMessageReaction.ts new file mode 100644 index 000000000..c35d4af04 --- /dev/null +++ b/src/v4/utils/selectMessageReaction.ts @@ -0,0 +1,23 @@ +import { ReactionRepository } from '@amityco/ts-sdk'; +import { AmityReactionType } from '~/v4/core/providers/CustomReactionProvider'; + +export const selectMessageReaction = async ({ + reactionName, + message, +}: { + reactionName: AmityReactionType['name']; + message: Amity.Message; +}) => { + const myReactions = message.myReactions || []; + + if (myReactions.includes(reactionName)) { + await ReactionRepository.removeReaction('message', message.messageId, reactionName); + return; + } + + if (myReactions.length > 0) { + await ReactionRepository.removeReaction('message', message.messageId, myReactions[0]); + } + + await ReactionRepository.addReaction('message', message.messageId, reactionName); +}; diff --git a/static/message_reaction_fire.png b/static/message_reaction_fire.png new file mode 100644 index 0000000000000000000000000000000000000000..143e7c0d9613bf59d58c7b35ae09a1383369ae83 GIT binary patch literal 4712 zcmV-u5|{0XP)!}*N zi-Pbj2rE4e34R6?eU?X<3>P{Nq7y`H2GR${9_Az|{r?&Q&hx1=IPugxJYacX24JPf z`QsQrah4}L9zC4^L?`{7m1GawPkqvT0{COfU2``Z%PeLpJ5j1s<{uO??%M!H@q`s8 zRvZkp?PHzt+OvrADK{x?1Olg*Q z=&c{k!WLyq2B4MG&v5)c%ZFobqR;_yuLK5W7ex>Pw2gHdzq4&EV)oUOFQ2msK z{1Zi&{E=4Lhk=Vt6&V2p@<5HVm!S2x%jR5qea$|`@rWp8*+hk!Kq-vF#~ajKv}7rc z&<37SSH6D^9)tmm=K+pL7)D+I8=6{iF@ZZN6BLCBgHi*Pn|K!p(u%Q;&AbvICy)Ak|Qopa0;^J zb%>QWmm6(J7jC(Gow3C86lD(`2584Y4G4n>bRlByB0-5aQZ_@EpMD1J#Q?N&Mzj5K za7s`>yH9t{`-YqxTPZygiAn1b5_qru_~pW}+OOWw_eeHS5!9?_eF#}D;GZa|707ws z&LQRqUsbBvY+-J8O9n7`p46NWtPop5VN79vU)_lpY|LxB_=#I$18APR0*Vn(B+c_n zK+Tp+SVG6fa#SlwU^sHrqC%E{I8}lR344O)bDS3DCI>bE%URIXogp@F7OO0VP&Xz} z0rmipBPN-Io&ar}eGPgEHBxB&Y0(?ku~~q!LdX|DV7Y=o3I&5YACggZX~RQ}5oD(Z zG5~vj#&mEodOw~zHTfPub?dzbI+G3Ch55s!tvIfD`TkPQiMkjBdku$gES z+;YSgVL_`=lhaWY^p)lUl0H&+c3l=gbaFBNS+_11c zM(7;KLX#jraK>payS@s8NV>QT8!!IHHB-?z06xP=b?6K(o|oxK&h-SCp9_0zOhMyV zp#d!MnUohu&ivmoJz*$plqX>V2TN&PZ4CQZH7RZjvhiXKRei(9zTz}_=NJEm#!FX# zIy|cT0rZK&lBOd`Gz=LjZ(B$}1;=6(#0Z!M*U!5&CzX{6HVaGe1OrlsORVvvY3=z} zu)XjOcrs##v@X8gQ&}wYz1y;`^LKxJ37l8c*KR}m(kV_1(}DqWu~GQe>=tIiIYOu8|=7qP6grYH!2)gQl%?Tgnzv%PqVbo};$KMrM3 z`M+yWI#}kXi28lsy)a(N(Fxlv=<+R!C`(6H~!@fro|^KY0szf(&4 z*VyZ=aH6d8-$ghC9NfEvni;JBcEKud=0rXmT^~V#D9t^-QIhn^zx#|afaLzkpgcq@ z3*h1JiW?z57b?wjONEnc%)iMnzM4Cyp|1_lWt4*}uvJMfuR-ILw=J`SHG*Uy3YmaH zaRk0>c1wMnD+%o7>wb+#T;LH z&sxuGy)aO7gXeh#lm&_L|CIiVdKS%IZyishT{Qkj=Z6 z6Ho|M|F(7h>cErL9xn2}6J#!cW0h>u23dz2^PNs;Wt`TrZa=otvT#HSRD~zP@ zu_$QuiGM#~PH~R)v8;OhxT7Cq6zJk8i4arf=k*JRbcv1C$V5?7n;0Kr$Nb6nDS`$Q z0#fKufVxG-0@pTE0&xRDQJX33sZ(E^S5ZpgUR7QfT-o!L0)>q7@8Q3p za`&|&v}iOg?JBTW|S4&ZjGuJlUhb&gVmH$3527^j6akuciTGjKRz;edx%9X$9+hHFD=C`$k%~W@#lV4BrRa6KM z*^buVxyvi=;08%`#{0WR^XIfdMz+HsqEXO`3)E^0{92$Ta$^UJX7UTTo(~X~6Lxyd zI-6XYbG?6vn>c=S{5OgX&@z2NJyVwx-MdMZMA|C#+ zMHMFF+j4_rsYjs`!&#%Cp8>X4^VjvgMHxaHyNGNn=!zW*O;N}S`4XQWox%A;zgEHq zs`*TyWP1ywqVio9#5WTTm2zYE?Brr{)5;B)^6U(go*60x^C1c78ytJcW;vz=zgb`4gFn@hzY)ALGj`6A=9G2xU^Y(z_0x^P;jS_GO zS*(2c^t?BK=y=i|C{J{LBU0Ymkwr&aCveP_-?uZ&fIUJTUMYgEhCOQxSbfM22W4oC zJ_S_T1L=-WL7^0+PP$w5$J+Zil7L$uwEnXFfUShwlA!g*}uyGMj9I&23U`wPvN~EmrKMTcI9RG(}%ePwu)yrhx1{O zcBIB~i1h|O294x@IXd%wfer;#{XqV{W}flYOYE^Uhgbn> zWza|uk~07vo~!eA+PuwzKlv zbwY(sNvj=Uun~HU^vW1#oU-AdkX{kDbuG%C5x9AU5%vG=2tqWXZ-SrtY;{|FcyhCU%95Un7D&Ob2Gz<_KRMksRMGgDrcd(2R+0pWB8APnQeYt#Z8e$erHO59 zg#R%{XkXfESz0T9BdX=eb&-9_VI$F2o4&u*=GEV_d2VTM1x7wM0i`_^*}qVs)&+>m zU15>a%hlLky22sy4&cLM zrmRhNe!?*Rdh(INF;U%u?YF*VJWq>j4EbG9J~#nGM}~PP8OSH8nB*wh00~{7NrUX3 zrz=y>&-K!=uK~1V3*S4%A2acQ{&f?9AK8@`qON}zU_bSGU3&EfP<0h(?+_%1KL+XW zG&J$y+*qZ%FG2PCbAVSF_IHLkz3qlFL!O%)0AHSKQ8vGe@dfUFN61%%0B6cm&&~AH zZ&L%9;TFDojt4nnhI3nJuGREPgH&PQsx3oC+A>O(r6o^6wsIF?Y?SMO1B?bHwQLnf&BVoh*1{#}H4X&%B# zb(HpnlW@L-*^|9e-2*p(lk~N(&Y*d{PQPIWrdE&|!K1>i#}nGjp+e6dL))t27i2rV zozE)r-5x9}P)}EO|0VVN(UOyJJR`&Hb@12aG@?TcTYX62t?MW$JiLoP>qLx^GGFo}4= zqJaF|YW)3I{P{v<>ZkMYfaP5=KrD3eOOsg}k79|AGGQJ`h$cC1$9dpGFP)T-4q8Ge4A6Hy=ayF}wC_jzUfTzLl5*Q}dfz?go_pT?ah`zB z$UE^E6}^zgB|yX=5raTfm4^|~>AO_66hBkBz%S9B(BUHjK0r#d!UI%&{FtKd(dm_h zCZ@?4NqCHB29PW9D7K9uWW=!QVSmu6$z#xv^vW~JHb8|GKXOqTDw>>v&?d??L|F!q z%kd!?a5>qH5w+fMW)bOYuRV>=j(?K@+z^6Q#T% zpj@AO|0Eh9O9~OuDq0gM^NV~BkhH2hxymz9q>Vf;$az4yf$yJxCGOqNsP~Vb)9}=J z*UOAb)t~1D;oR2-EpAFs{+Fda-1_Qau5l*Z$ZLIP@Y3Zvl!FiTn_e)=9}YZVX&u6hGTg{(Z=OLZ_*j36rpf2{jq)S4hv$Rxrd;y79k|8k`lIr;(s@4!8uA?% zKc?|$Ao=a_i?%%*M%#rj3N>v0&BmDx?tL}CG578psFnKn ziLve)cFSMta)XY27xnHo=v)1C=hF~!Z-5ssA6TCo_w@PDF_{-S!g&qv>I|b+>dzsK zZb7%d&**I&F5z)_m&Phz`0OL1s0_Pi+l5e3nQGOaMa+nRZg+VDojtM-oup;2J0w&j ziQ)7T^LE7&K>jxXPC0A<9j6za4UocVMId#$JwS@pF#myYu#|k_ZJ_Q+1cLM;`SsUM zBp1Re8_SO9>sbQ?1#T~Q*5996%c+^5h9vBWbRuv#@m4m#{&A6RDY(6Yz`?`73IUyU z&&~s+n6VUIr}328M4;HTF@O|FF7^$8grzVmzwRig)CL>?cmJ}MF)+=AGJxnxBvtO6 z6>B#U80>?eu5`xXsjv_emOo;PrUKvvPI_O8ToTp6!N`0(iaNPsc{&to!20LyFw(9! zAS*(+C4{CoR_zRsmn)FlNE1DY6djF|7s!#KKc!d@3V(ymFV<0uMBk;up53$7LQ+nP z_8Hy$n7cx!SLuZXnFkp>_;`)VJyerWOBgL5Hd7h07Ual(ai;a? z*IE(b>vltdFUS|JrYCUEJ5rtlpAc6I{iC1(vTkBO6qFHeG*=}iL|%@d`9_NqyN(m7#YFgDG=ZCI%fy1M0`n>_R&aXUUrF<}HHvis+*@{YZ= z3?vzYOD5bLf*_?stoU>FZhQJjBjp4s@A#n(*GH;yk+Uty2(_0=n^MufGvO-1yPSun z<~(>P(i;SAZqVGNl!vCudu)#LiUoN?^$bx`uOFuT2PfGZ_ApGbfdgcvhD8bn?O!xv z{BYiHDR}qjtV*=zY*Sx7!ZXE9JTt&yR1%`VAbG`WWgCN+um)|~(!Qm+jpZf27~n9< zGdL41n~Ze{oRA@8k;mr+_bNX|zp4&wc95hcx(*-t4-u=J64pxoV*mgE07*qoM6N<$ Ef&{JjfI`g0T zKkhl_cYfz_?;1GEqjuc`Ww@YTv6)?N0Lp;wWlTE)mJJ$rh+x1V6ksn{9Cw#g-M1s| zeHTo^NXM3&t8f;FfwM4bJ@+|*@frhmm3u_le}~~8)Tesk>3ddy`vU#%at`okv{iS) zu(jFd#anJ4hB+Q{rhr<{Ks!tpdYxQ-PLTF+b7P`}7~TL4z55Xa4$25eKGld zR>Ai+ycaNFk&+f^ta;obix$nW1x6g+;m3=bQvPe=&Y z0R8U1usqkGkeWN*TqxL~jxGIFn8lct0%~8muh*jOcQhs5laSdfXo7|Ku0p$%=hk$A?WPvA5 zC=W(wVbG9u4WlGj8bGa0Z{cLtAez)o^gMErYVo|{l`}y@0j4>;)iEpVbmCkQ#3F3d z{DP=#Tyk7G4$2|BkwU!S1bGZXSeDVhXM_N2>D-_sx`xO>T!n-urPy-zdVS}?Rk!!S zG{>|MP+NWPfP;LW_;k2ILUci7_G4mpFqrzao{(6T-u(|`P4Rtp3$Gly4ZDVT0xQS8 zFklNU@eE@B;$Ppheu^SZT>-V#gC6od`UQfRHGe}^i06X{Z-f-a>h}zDZ2X@5J-CI0 zPeUg*Uyte*NC*nB-n{5HKU_Nnv8JYg!`BZv$is4tU!nkjhy+OD8!)>AD6#o>haSWD zCO1Y4Ijaf}q=L~6AVOb4BQX!6ZIL^K>g?{&8tP$Dz&bc$)>sh)UP9U8X*XyS%LRUX<2 zOi_>#WRu_#j;)5!JQu_)F(3rk{S$654ae>oH+OD(U~tL`2yxzpI(KQiL^4SHoi5}; zMu-qoUkj%0+ud8c3FhS>ZV3LG!io^qMwm;S_Auu-?$0qo99H7E11-gSR*;P($No;%Wq%L1 zV80jF(PNA1Lg5N9axXszuVm+P3*A4PTFKg^fJ3VXdffBM2&ms72$WD%OQ{}6R)!5I z@USMoN61(QUSM$tKG$X-Tp&co@DBd%l@NJExgziaD(Mk7gwKH#qr!A5W?4rPV#l(1x{ye2z4g@21!yQd4P%5QN?0o5u^zXsx|p+ zy-!dODj5pY`MSi__Re0tEj^0^C{dRQ7EvxsY@8iDkBne=y9JH&VLPhTOxq1ztaiQ zt~I-0o^k-75JY5{*6yH5PcEy&)UwFstP!70(qOV8Fk~aMi?SGh;Ul3kqK3I5)<-d6 zqU-Vs!GHp5mURyk3QD4YEGt%=0PXxVniq#Osz0R~BSnCaw}i38HA$sp`e9_dOL%^%62lqI^%W5$eF8WeFx6&jDX+~A9ERF$Lx2YkDGE6^;5xx7;xtEK zlt#3M&*PoLk94R4YS#}`oE^94wFp8o9K(Z8n;gt7$9p&0E?Ww3x%FCSwF}@(BXhzZ zz55qX-~CE>Mse9CX5q%KLgB(QUyBnbVEo$ySl|6p>>(QE7>r$)NQ|PS#P!@Y#VeN# zKRg^r(3;|!77*gp(rUqBAi|c-A8uAybkUTB_oH}m2Q2*l4N&Utgktv+C;W|565bE9 zU>>yJ(`O3q9w4F4wjx0078DY7oQp9cWwyS`-;bhz%_@QXnxJvh9)&05kA(Rt5NJ+l!iA1 zMF0tinjMNFsKAnAM}Xy$JG~q%$_g`FNN{EWDm=;;o&c?+S>a}IE{9rNpdK1AYWWzl zt*%>ec<`X_D!3oog&EK_`(2I>kRU+V)mL-c!X}|ZSg~W{aIOYU9FM6u^B6N6M<|U1 zDm-G6hGW+56o6f!W=^v{U!eH=VME`}YgAWCsxghagNrczpw38zh888|kNiN*y`oDI=xf&;){41h_HeFY+X;q3jH^8O@ z2@7fB5X;w$6~dt;CP?9GL5Am!lYe{)?XeT^W{+(DK{&o`yQI0|^lW{L5$fD5!MM^S z2zJ915~qk=8Q{V~<7tEsCiQsu{@8JIbZ`s2+2iP@U&Um{Dx`yuEI3HwlS}eI3!?D3 z;3$1>AUI8b1p%Zrt)Qg);@eITNyoq5*zq(T`{k4HW{n9a{K?-BC-CNV1BE~4$WT!X z3DW8OYRMgqbKecMY$PjFdmo~|b08E1n55=5#~#@h>Vf&IDsVQ3xBSGW$8uRK>W&MR zA(+vEHbj7`L2Hq_d%|~EE1+LvcUG0OF=5aol%R5g)Coy(L0w#T&csh2hZBF;0cUF* z-!=@#e)cGGYmJNq!cp8y1mY;8ZcOuwO;RrKC@TD~gxO>jbQc5alCu$CC)GlX5ZoU? z$qX^a3rBzQaJXXLRUd`99FFkdwY%=e*qMqcE=^G&PRFQGjbxEy_J3p&-&t@6yZA=R zs37h`zQWBT7`x`SJw7Xfw4)M*ZTe1%4e`5hx70lZYOZ_p zwg=XQqGcu{+dl>edv0+l?=g@hhYM38I$A&EmLQQb0IHivmk87K1yr}FUOMu(?gCH# ziKKCmXf_HT>u6$LiLHj<6_u1E9(Z&DYPbBr333RgJG}6(-gKv<{Rz$N62b$dRiGpd z))1HLItwaMf7cpe*gq#Iz*RL{AxUw$+A{`Y6j0cQpc9%98e{YBVRHnOjgDdMn|I-K zlrR~D|E|0CfPaFKwL&3UcFjoKlInsQ1VOXD#E9WO$m@<~bC6vgI`;XSYrq>Ekxx$G z!;nV~H(;6OO+}vbnkgMX&0mPw(hf1hCLUAU9;VMaTs-Af}BGEj2~cz!52;lA&I zbtUZSgg72AULc{Jr_w1Kvsshd{!Jz97 z)gsZ^9Gor3ebG8bI0gX|c#;bt#Bjvi;H`SNcYRAG_{dSa?QXRD4j}oi)h-UCkk&l{ zx!oXXM_4jcHd+umtV8m>#)}2`OOpaRwhp?^hC_kXWoOXVH(!l!eM85_nT&-v8;{C(9#zZZOCls>PA{>(x8dd`BY&?pDZr1? z@seA2!g0;EWIc_onlQcieH0u?aja?JjSqemIDfr+)t}#uM>gIUAnz?dzH!KX_adgf z{DgFqghvpOn+F&Wgkab>Wt5YYoMoQXQpx1D$~5)!_Fet8KDTHvpzS5YJrT_SA;9hanZj`m<9w986weF%L0a{6tV=y)#A~B_eDbXOz-bj`xeFn>ki@=*)htKoS z2FQk%xnX znj|9x_CZia)?Fkqba4X%Y)W)097u;Jx{h*8d{!j5#M&l}IGD8Z-0e0jd2XcIO4if% z^!I;qjo)P0o2Hj}Kj%MUKq)bU4g6=I#a`Fgg5CO=OF(og|9XHKId4jnh z+{%i3_|j}c*Rv!2r;_oEiyEVq)tlTMd-4y3W?$5W2h9kfkt7?kM}XuwY_`O>sX_Ah zxJ;KQqe1z}Z~7ikV_g=z4d8gCma#DB_Ug+NlJ(~&?BLSjTjH z*d;>D?+1q1GcdV$Pw!R}D{&z(>F;t+$n`78;wS~0aDun|6omI~yC{~_LBUBOltd!Bx7F{B*qinU`A6LAgrD+Xj!a7d ze(d}B7v0V6ItYIuW>X){`>>74OsXuY6eX_bH$2+}+7Oiq$+kR8_@lqpv5^A|9C81y zcft=&N1kabz>m?&sxrO-+uf79!kQcXbqMfUy>^MkFDW*Qz<$^qIub-l+d3jc$xzM% zNVq~Gz;Fg;ZTBfc-JB~x#>G0#_yh*1C`*@j1dUMHJ3 z6iMUmlP1KsrWORLXUL6)p^}a7)qtkFUxu=C`{wxh~Y!dQe8y_+Es?synJW{O@YZ?C_Eq d5ZiRd{{beuF)-7h`-K1i002ovPDHLkV1h8*OFRGo literal 0 HcmV?d00001 diff --git a/static/message_reaction_like.png b/static/message_reaction_like.png new file mode 100644 index 0000000000000000000000000000000000000000..6fc08d1c7f1f62a04aed91cd1bd2f30ed2700073 GIT binary patch literal 4302 zcmV;<5HatGP)&~6&o8E#=pcuG=sI(z%rxJu1?DU6d1f>2!en6P=h&4v05`L(EIwdBM7}8b~ zC2G=6E009fqNx1Pbg0o(d9)R@NE^5XYrz<1TH4OEGj}id-s`dVx%2MK-RYfs&pG?- zvmW32*4lfYA~d;e+(FgzEzns4SVaI;AgKE93Wnd>5Xhf9h6DrtcMTB^`r||O5jePW z1J(LvpXm1k^|qAD+h=Ns85!Wd-PG+} z)8j{}L{EDqGe`3SFjH7As5OGZ=5>Y$k)vojLL>pt2-U^Oy zh|eM&BlL4JM#wA<4}9f^XE(t#+q4jH|1R1}f#k5@<*25Fx1b^OEP%m2`x#vdN(pQg z^Xvc|8nr`D;N{jAVtTL=c!Q+ z?%A}cV=~<)<$!y==Y#T$Y2l#c67S`}+%b2f#&IF0mEaT!#K?%bKI?b)jV<=f`5pyd zSlI#(-P{T;#d#bi@`#+P$8!`d;e<3JN3a!E-~RZi9Wye(y}N0%*SgE%J%g|i7%PrN z;S8FQ#JF#~7VinH7UP+_`th!~1mPo#MuTz1d-~pfG{b-xHYD)^a6n9GI(Fl$So077zFOeF`kO7h59kYEz!t%?;kHqWR zTP!2EsI;O@$A^?XTxMu<5N*2Ud;OcHWq>e&@pAP)a7rkCd(#;jc6#P>X;E+204DPKr{*&krCn))MEe^PHV3zN75Ttc^w~E zA&@v%s__?7_&E!?Xs~#HBIwS~Ki1zpp#i!A^ucZB2SjdFh*1_(qGTPzz;(8K9*ahH zVAh}5G{OSrjYzh0!zGgRT{Zsw@p# zIu<1JeM?T^d`pW-iaKo88ebtKhyp9{`KnAo!)cdKQR955IE8gG?Pp_zZc8shxZ~4p zlPbSoVt?tPaKyJW0!vY#_fHz}`eziU)Fu;9LI=~e^uZ7YJv_2+cm2eBpJ;EUjEzH9 z1hDc1Y4XC$R6ICAf|~YGd1L|=5w-QM1<<~9+OzOc?aUCtFJBsj-TThkx!`1k^xT3R zXe=DFh%fNg_4QoV7bWpm?lA}{tRn2WxN(_bc>bOZrs>W?1G1U{5+V9?cizx8E6Rtq zU@pSlH_eBKZ(9V#dLT>PS?%J4r=$@-3Hixp807dwuXwXkY@ombz z44{~|93QvZ9Fh~*8RZG#1EX{|DEum8IaPyb~o64wn^m+?Dang`8ly?tk(_r=pP zVdcVyWkMx0o5ZPZ;k&h7YA+*|RhPz-LovpI7wb?`7utM5)~89_5iOL%>Z^Hrs12sSPL zL;G;hMUQf-7T=|Ch=!iGHwz5&aQ61ip8LzfxEOiPhsb;ownMpi^MCkveDAAwhno>P)ZPT@*b z<5;Vh2BElxG?YX+sgrF*O)6j{%~*RvQ`(9Cy1-v%+bniWf1pJl zOOBR}Ny>c}-Eyq#<9Z-YV~MgpvVNBok+_M6IAwjD(JC~fEu4$7@Ii%lmU2|qL<}-L zmnNayU-I|XoH9cA)MZiux}lSQ(gb5FP4)aW<6mRgi{eyb_ zopL?wtp{)GKA!*wvUznpla-U8U8c~qKxsznd;Kg>p6W0wy7W9JH7rhqI3ns)I^aW~ zen=3MmXe{E6%;(qWG9}MH~UmdjboB^7H zfR~RB@&>7tvAje`dDs!9%>$b(iLTLX!{iQ@UikKsnm?|E*w-yD34Yti;5E*i1BwCg znQPkOOfu~4-?Um-Z*|fDosx2Y06IodBKy#eli^Y#lG+>8UX9{Jq}yC^)fGZIxO7ix3*AI zBxseGT1WfnmEI2r>ynz{2vL~lUp_<6zkC`4Up(-+iZ{d@Sa$K`OXPWb;)QPuVU(YuzwrIoRT z|LrSh;JZKXFN{?zNkOHh&THr4nvczePb_PLPhQ>zi{=#~YAp1opYRWgS*Lha2TFK;gxptI*hkH2U0D63_bg)x?#!|!Z4T5hxTgS4MO ziLZ(50aBz;x`kx!#U{7y@4TE$<$J3AGe>FaP3A!$Dz^rgknM-}H)m$awIf~wL`5Ks_eYxtR0H4N>!KpCo}zh>@G7iiW~BS5-` zz?c0k+X_182ZDkvi~^Sg<+&xtxYMwRQ!vae4WqnIh4*;G5NQ|5DKZpM87lN1S>cVw zb{bk#+dIi~H`Z`wYUD@8K>zFx=U}S@H zEeCw!U+f@T24hH(8UiiVD3kNVgR_Fmt~q;0P}W^AKOdSgJ1F5hUF*Z5Qt>&+G*3bu z$;iR+Xzya$ZtwtNecf2Pj=590|FQO(zjKQtmu+(D3l9!Ct;i;6w)Ymru9L-j@jJ>* zMxNryh8LxoE1ZRmmE|sjy%L>sDo-&>aR}P$J%NZGsqO5jjiu{^6P4>8KDO1X)lH2k zPz2`-3<6o6c`@9vP>krimP~2#m6ZotM`Y{!`XC)6c5!KyDyu7KEClg(<|FUyTGcb2 zj+4&R);#p~4nJZSWLyAHfL|QPaM%#9DHanf)ez~JJ2@6Oc`jld#p4onY&?0KDV#W_ z7&5)@{%lp(MEXr>s@k#Xtpk3+cq1?--GGl@2 zp6vh?oB&X)1`i#7a`nt#mJaQ}wyQck8Sb+96%HWK|Lh?9@sQrtU&?d2Cwz zgX}p_@+QdAT>k!#PJIOeTopPizT|xP?wyMAp-pSCV&#KJdMM5p%#(b9@;FuJ#7Re% zA+l5mDb)!l6~;TIg>k?DFpZ!=!7^hT)q<-~4JcU@klN}*23 z*q^Al5F)>&@iaHo-6^FhAto7PeIGI9Cv1M^I(MkFt+uCQ0A|o;ZN)Ykq7I!NlAFB( z)naPP2`ew2;@?JyE32W@PneiBu2*=)Vf#!K*VdFJ>#Og7%^L#m@kH23;r)ZyG9q)0 z`P|4Z%0ygEYZj?_-#K%!fC}gjgrV)Bl}aqCasjE1`UepH0-V19a&MJHf(uoV zp;9@tQY@9KqADa6Nsw9~<^c3ka0N|*6YunWCt1gicfB*a>%@-uNM?<9GQ021o0&In z-UzBQ@=h|&mjp?BH<5^wq&NwRscSica{OC<{wn_9>nJ{AdnQM9L{J@Ip&@WLSD&mB z)SMiDvSf)NRz{X0CNc%c$z+UX?9)^fy{d)lLylj$k1WbYWf`FruuOt4c_?=kOAeB- z`ok(Blmg^L@-*2py=;P4lY>QwotZ%jNFW8MflbN*Zixgn200!hjs&p=iwse%UX&vZ zBs_?-C64gB^^ze*79b~%o#QFW_#w-RCDUU2)jr+g+6s_&x(CRn6g3BfV&|0}oq}2l zkmKDWeCeX*Vw9Dz$FGp@7@%6d`Gf~u+;9Vag=#8*NBgr)WcF^P)VNgx&n}=wdnwP; zYCwoHVn@bR7FPwRc^*^U$~ibu!=9tq)BS9lPkDZ9c7N)`}i;CuMU=1-A2 zJ1yF{!J_uIy2M#%dPb^eO;1Z|Zx^(6^BM|3g%zwV{cQ8%#!&dX_ltDl(lkAJYL|Y4 z@QDvv>64FNp(wfb;|t1OFDNBAbaq&D{IfQCd*@n{svIjw<3EcQFy1{$2Ih~Kx%~Z% z=l%WrTItk@Hj09atmGRvXFczqJkd@E_O*}!d3KXK@I{RQDu!nuJX#zrQj6bi7U;(B zvlJH8QA>D$hA6iT4Px@Pl6heewB?rue@uVfD{4K^@5Obvam&9Bj0wid7IqRBXBJ41 z0eA1uX_b5UxJY5Sdr$LOk8CnRh;Cf0g}8m3>Hu1%fr&$Qi)PK2x*Ip>O+Q;V8;#EH zb!^eAW9yaxQzFlN7OgQ?07hJ$x&FXAMkBN5v;4rjYzv}nX?O7$Aa|1?a6*nBt3Acx z#SNz>&wBRz)Pr~F>lG_3-T{PUGk0>~CN({Sh46tiZG>j~Y7R)W4~AC`m%qqFPT8s4KIKmj>wA|QDB z`O&K1)6XlFqFWslkf$c1@7Yn$dp-Hj$p}IDrm}!+VG|8b@2z?mcZU6-=Y!~II!THyYCcaog zup7*>&-(#VT%iW%aOHv#brSOl%WO(US2{&390s5dTy~zL%1x4#mBmz zHu{Dr9G!9(cqtHkc-=St3V`|VxS|G%#kGJC{j7$wR41^My*)NZe@+(FE)tf{P88Pv#t$X3pjIrQLfg(QTZMHxmFE^y@ z7rU;OHb^TK5RnyNL*(*R1Y$^#d1lv;=zn%6ihZaRl4>3f5?t(a9d2rQ>lV7w4vxKsd zznrxmg?o6OG|m;MwU6gSiw|(0_Ug}20J;wp>7|U@6SF}Skca8J-EdU^`UrOgo+`|` zf>$)y$rtd87CU`C@qR01e<`?jr73>{tA?jNuS{~!keJw&IZrlx1AQRqQm}WABnFdE zIl2ZEfxoSK#-Ua+!j^qX77u*iN{$^HWb@J9K7aVwrs3-a_5BF9WZ2j}x&o3hrg)k^ zxUa=*6GX}(p7K()io8sgCJj_YtW9bb(DE9>18w_Mreq84g9t0eyr-AaEpF67#ao#) zkEmzVsjbE%k9R9~b9D%FXlgiuLy^%SIJm*SOOQiT;f~EAuUPQnY2_4=S95%gY0i)E z2Pa_-bJRCm zfhDZXL0gVn@*te0h^bF1M;Xd8kcl>*jD>upI9rLw@p*w(#WDH^YQv^LS+S?S$LIeC X(VE9x=t%rf00000NkvXXu0mjfeCJet literal 0 HcmV?d00001 From eca154ad8ddfb6bdbae6a02300781f7a94b98e31 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Fri, 24 May 2024 13:06:21 +0700 Subject: [PATCH 077/300] fix: ASC-22623 - story video (#349) * fix: story video * fix: video * fix: remove console.log --- .../social/internal-components/StoryViewer/Renderers/Video.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx index 6c9c18432..a5d2d6224 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx @@ -235,7 +235,7 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler data-qa-anchor="video_view" ref={vid} style={computedStyles} - src={story?.url || undefined} + src={story?.videoData?.fileUrl || story?.videoData?.videoUrl?.original} controls={false} onLoadedData={videoLoaded} playsInline From 596d1587913d46c2467615b0fcafefca619238e0 Mon Sep 17 00:00:00 2001 From: Kiattirat Sujjapongse Date: Fri, 24 May 2024 14:10:01 +0700 Subject: [PATCH 078/300] feat(reaction): update condition (#350) --- .../components/LiveChatMessageContent/MessageAction/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v4/chat/components/LiveChatMessageContent/MessageAction/index.tsx b/src/v4/chat/components/LiveChatMessageContent/MessageAction/index.tsx index 5f96e94b0..38bfc9136 100644 --- a/src/v4/chat/components/LiveChatMessageContent/MessageAction/index.tsx +++ b/src/v4/chat/components/LiveChatMessageContent/MessageAction/index.tsx @@ -104,7 +104,7 @@ const MessageAction = ({
)} */} - {isModerator && !isOwner && ( + {(isModerator || !isOwner) && (
Date: Fri, 24 May 2024 15:50:50 +0700 Subject: [PATCH 079/300] fix(reaction): ASC-22611 - update missing ui and action to remove reaction from message (#348) * fix: ASC-22611 - update ui * fix(reaction): missing ui and action to remove reaction from message * fix: ASC-22611 - update ui * fix: ASC-22611 - update ui * fix(reaction): hide 0 reaction count * fix(reaction): update pr * fix(reaction): export reaction list --- src/i18n/en.json | 3 +- src/index.ts | 4 + .../index.tsx | 0 src/v4/chat/hooks/useReaction.ts | 18 ++++ .../ReactionList/ReactionList.module.css | 25 +++++- .../components/ReactionList/ReactionList.tsx | 85 +++++++++++-------- .../ReactionList/ReactionListPanel.tsx | 15 ++++ 7 files changed, 112 insertions(+), 38 deletions(-) delete mode 100644 src/v4/chat/components/AmityLiveChatMessageQuickReaction/index.tsx create mode 100644 src/v4/chat/hooks/useReaction.ts diff --git a/src/i18n/en.json b/src/i18n/en.json index 154427c33..6e52a2f06 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -438,5 +438,6 @@ "livechat.error.sendingMessage.notAllowLink": "Your message wasn’t sent as it contained a link that’s not allowed.", "livechat.reaction.errorOnload": "Unable to load reactions", "livechat.reaction.emptyState": "No reactions yet", - "livechat.reaction.emptyState.description": "Be the first to react to this message!" + "livechat.reaction.emptyState.description": "Be the first to react to this message!", + "livechat.reaction.label.removeReaction": "Tap to remove reaction" } diff --git a/src/index.ts b/src/index.ts index 3ff2fb0c9..835c8c3ef 100644 --- a/src/index.ts +++ b/src/index.ts @@ -38,9 +38,13 @@ export { export { MessageReactionPreview as AmityLiveChatMessageReactionPreview } from '~/v4/chat/components/MessageReactionPreview'; export { MessageReactionPicker as AmityLiveChatMessageReactionPicker } from '~/v4/chat/components/MessageReactionPicker'; export { MessageQuickReaction as AmityLiveChatMessageQuickReaction } from '~/v4/chat/components/MessageQuickReaction'; +export { ReactionList as AmityReactionList } from '~/v4/social/components/ReactionList'; import type { AmityMessageActionType } from '~/v4/chat/components'; +import type { ReactionListProps } from '~/v4/social/components/ReactionList'; + export type { AmityMessageActionType }; +export type { ReactionListProps as AmityReactionListProps }; export { AmityLiveChatPage } from '~/v4/chat/pages'; diff --git a/src/v4/chat/components/AmityLiveChatMessageQuickReaction/index.tsx b/src/v4/chat/components/AmityLiveChatMessageQuickReaction/index.tsx deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/v4/chat/hooks/useReaction.ts b/src/v4/chat/hooks/useReaction.ts new file mode 100644 index 000000000..168aabfd9 --- /dev/null +++ b/src/v4/chat/hooks/useReaction.ts @@ -0,0 +1,18 @@ +import { ReactionRepository } from '@amityco/ts-sdk'; + +const useReaction = (referenceType: Amity.ReactableType, referenceId: string) => { + const addReaction = async (reaction: string) => { + await ReactionRepository.addReaction(referenceType, referenceId, reaction); + }; + + const removeReaction = async (reaction: string) => { + await ReactionRepository.removeReaction(referenceType, referenceId, reaction); + }; + + return { + addReaction, + removeReaction, + }; +}; + +export default useReaction; diff --git a/src/v4/social/components/ReactionList/ReactionList.module.css b/src/v4/social/components/ReactionList/ReactionList.module.css index 6f1c28c47..59cc3b21d 100644 --- a/src/v4/social/components/ReactionList/ReactionList.module.css +++ b/src/v4/social/components/ReactionList/ReactionList.module.css @@ -5,11 +5,26 @@ height: 100%; } +.tabListContainer { + display: flex; + align-items: center; + overflow-x: auto; + position: relative; + width: 100%; + scrollbar-width: none; /* For Firefox */ + -ms-overflow-style: none; /* For Internet Explorer and Edge */ +} + +.tabListContainer::-webkit-scrollbar { + display: none; /* For Chrome, Safari, and Opera */ +} + .tabList { display: flex; - gap: var(--asc-spacing-s2); + gap: var(--asc-spacing-s1); border-bottom: 1px solid var(--asc-color-base-shade4); - overflow: auto; + width: 100%; + min-width: max-content; } .tabItem { @@ -21,7 +36,7 @@ border-bottom: transparent; } -.tabItem.active { +.tabItem[data-active='true'] { color: var(--asc-color-primary-default); border-bottom: 1px solid var(--asc-color-primary-default); } @@ -105,6 +120,10 @@ gap: var(--asc-spacing-xxs2); } +.removeBtn { + cursor: pointer; + color: var(--asc-color-base-shade1); +} @keyframes skeletonPulse { 0% { diff --git a/src/v4/social/components/ReactionList/ReactionList.tsx b/src/v4/social/components/ReactionList/ReactionList.tsx index b6d6a9b3d..c254682f3 100644 --- a/src/v4/social/components/ReactionList/ReactionList.tsx +++ b/src/v4/social/components/ReactionList/ReactionList.tsx @@ -9,6 +9,7 @@ import { ReactionListPanel } from '~/v4/social/components/ReactionList/ReactionL import { ReactionListError } from '~/v4/social/components/ReactionList/ReactionListError'; import { ReactionListEmptyState } from '~/v4/social/components/ReactionList/ReactionListEmptyState'; import { ReactionListLoadingState } from '~/v4/social/components/ReactionList/ReactionListLoadingState'; +import useReaction from '~/v4/chat/hooks/useReaction'; interface ReactionListProps { pageId: string; @@ -19,10 +20,12 @@ interface ReactionListProps { const RenderCondition = ({ filteredReactions, isLoading, + removeReaction, error, }: { filteredReactions: Amity.Reactor[]; isLoading: boolean; + removeReaction: (reaction: string) => Promise; error: Error | null; }) => { if (isLoading) { @@ -37,7 +40,9 @@ const RenderCondition = ({ return ; } - return ; + return ( + + ); }; export const ReactionList = ({ pageId = '*', referenceId, referenceType }: ReactionListProps) => { @@ -45,6 +50,7 @@ export const ReactionList = ({ pageId = '*', referenceId, referenceType }: React const { reactions, error, isLoading } = useReactionsCollection({ referenceId, referenceType }); const [activeTab, setActiveTab] = useState('All'); const { config } = useCustomReaction(); + const { removeReaction } = useReaction(referenceType, referenceId); const handleTabClick = (tab: string) => { setActiveTab(tab); @@ -59,44 +65,55 @@ export const ReactionList = ({ pageId = '*', referenceId, referenceType }: React return (
-
-
handleTabClick('All')} - > - - All {abbreviateCount(reactions.length)} - -
+
+
+
handleTabClick('All')} + > + + All {abbreviateCount(reactions.length)} + +
- {config.map((reactionConfigItem) => { - const { name: reactionType, image } = reactionConfigItem; + {config.map((reactionConfigItem) => { + const { name: reactionType, image } = reactionConfigItem; - const count = reactions.filter( - (reaction) => reaction.reactionName === reactionType, - ).length; + const count = reactions.filter( + (reaction) => reaction.reactionName === reactionType, + ).length; - return ( -
handleTabClick(reactionType)} - > - - - - {abbreviateCount(count)} - - -
- ); - })} + if (!count) return null; + + return ( +
handleTabClick(reactionType)} + > + + + + {abbreviateCount(count)} + + +
+ ); + })} +
- +
); }; diff --git a/src/v4/social/components/ReactionList/ReactionListPanel.tsx b/src/v4/social/components/ReactionList/ReactionListPanel.tsx index b268bf450..d8294724d 100644 --- a/src/v4/social/components/ReactionList/ReactionListPanel.tsx +++ b/src/v4/social/components/ReactionList/ReactionListPanel.tsx @@ -4,12 +4,17 @@ import { Avatar, Typography } from '~/v4/core/components'; import FallbackReaction from '~/v4/icons/FallbackReaction'; import { ReactionIcon } from '~/v4/social/components/ReactionList/ReactionIcon'; import { useCustomReaction } from '~/v4/core/providers/CustomReactionProvider'; +import useSDK from '~/core/hooks/useSDK'; +import { FormattedMessage } from 'react-intl'; export const ReactionListPanel = ({ filteredReactions, + removeReaction, }: { filteredReactions: Amity.Reactor[]; + removeReaction: (reaction: string) => Promise; }) => { + const { currentUserId } = useSDK(); const { config } = useCustomReaction(); const reactionList = useMemo(() => config.map(({ name }) => name), [config]); @@ -28,6 +33,16 @@ export const ReactionListPanel = ({ /> {reaction.user?.displayName} + {currentUserId === reaction.user?.userId && ( + <> +
+
removeReaction(reaction.reactionName)}> + + + +
+ + )}
From 1799fe5202f198338d6076091781f8df12560e3e Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Fri, 24 May 2024 17:40:19 +0700 Subject: [PATCH 080/300] fix: create story condition (#352) --- src/i18n/en.json | 2 ++ .../social/components/StoryTab/StoryTabCommunity.tsx | 12 +++++++++--- .../StoryViewer/Renderers/Image.tsx | 4 +++- .../StoryViewer/Renderers/Video.tsx | 6 ++++-- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/i18n/en.json b/src/i18n/en.json index 6e52a2f06..3afa6e9c5 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -386,6 +386,8 @@ "storyDraft.button.shareStory": "Share story", + "storyTab.title": "Story", + "select.chatType.item": "{answerType} type", "chatComposer.label.channelId": "Channel ID", "chatComposer.label.displayName": "Display Name", diff --git a/src/v4/social/components/StoryTab/StoryTabCommunity.tsx b/src/v4/social/components/StoryTab/StoryTabCommunity.tsx index d804de851..915237160 100644 --- a/src/v4/social/components/StoryTab/StoryTabCommunity.tsx +++ b/src/v4/social/components/StoryTab/StoryTabCommunity.tsx @@ -19,6 +19,8 @@ import { StoryTitle, StoryWrapper, } from './styles'; +import { isAdmin } from '~/helpers/permissions'; +import { FormattedMessage } from 'react-intl'; interface StoryTabCommunityFeedProps { communityId: string; @@ -53,8 +55,10 @@ export const StoryTabCommunityFeed: React.FC = ({ co onClickStory(communityId, 'communityFeed'); }; - const { client } = useSDK(); - const hasStoryPermission = checkStoryPermission(client, communityId); + const { userRoles, client } = useSDK(); + const isGlobalAdmin = isAdmin(userRoles); + const hasStoryPermission = checkStoryPermission(client, communityId) || isGlobalAdmin; + const hasStoryRing = stories?.length > 0; const hasUnSeen = stories.some((story) => !story?.isSeen); const uploading = stories.some((story) => story?.syncState === 'syncing'); @@ -92,7 +96,9 @@ export const StoryTabCommunityFeed: React.FC = ({ co {isErrored && } - Story + + + ); diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index 0b60e110c..93eb225fc 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -25,6 +25,7 @@ import useUser from '~/core/hooks/useUser'; import { BottomSheet } from '~/v4/core/components/BottomSheet'; import { Typography } from '~/v4/core/components'; import { Button } from '~/v4/core/components/Button'; +import { isAdmin } from '~/helpers/permissions'; export const renderer: CustomRenderer = ({ story, action, config }) => { const { formatMessage } = useIntl(); @@ -75,7 +76,8 @@ export const renderer: CustomRenderer = ({ story, action, config }) => { const isOfficial = community?.isOfficial || false; const isCreator = creator?.userId === user?.userId; - const isModerator = checkStoryPermission(client, community?.communityId); + const isGlobalAdmin = isAdmin(user?.roles); + const isModerator = checkStoryPermission(client, community?.communityId) || isGlobalAdmin; const computedStyles = { ...rendererStyles.storyContent, diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx index a5d2d6224..d7fc9cd42 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx @@ -4,7 +4,7 @@ import { Tester } from 'react-insta-stories/dist/interfaces'; import useImage from '~/core/hooks/useImage'; import { checkStoryPermission, formatTimeAgo } from '~/utils'; import { useNavigation } from '~/social/providers/NavigationProvider'; -import { FormattedMessage, useIntl } from 'react-intl'; +import { useIntl } from 'react-intl'; import useSDK from '~/core/hooks/useSDK'; @@ -24,6 +24,7 @@ import { motion, PanInfo, useAnimationControls } from 'framer-motion'; import rendererStyles from './Renderers.module.css'; import useUser from '~/core/hooks/useUser'; +import { isAdmin } from '~/helpers/permissions'; export const renderer: CustomRenderer = ({ story, action, config, messageHandler }) => { const { formatMessage } = useIntl(); @@ -74,8 +75,9 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler '' ); - const isModerator = checkStoryPermission(client, community?.communityId); const isCreator = creator?.userId === user?.userId; + const isGlobalAdmin = isAdmin(user?.roles); + const isModerator = checkStoryPermission(client, community?.communityId) || isGlobalAdmin; const computedStyles = { ...storyContentStyles, From 7d6e687c3778ed2f1211d9c64a8a111cac8069bb Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Mon, 27 May 2024 10:55:19 +0700 Subject: [PATCH 081/300] fix: ASC-222740 - create story condition for global-admin role (#354) * fix: create story condition * fix: condition * fix: condition * fix: condition --- src/v4/social/components/StoryTab/StoryTabCommunity.tsx | 8 +++++--- .../internal-components/StoryViewer/Renderers/Image.tsx | 2 +- .../internal-components/StoryViewer/Renderers/Video.tsx | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/v4/social/components/StoryTab/StoryTabCommunity.tsx b/src/v4/social/components/StoryTab/StoryTabCommunity.tsx index 915237160..faa615072 100644 --- a/src/v4/social/components/StoryTab/StoryTabCommunity.tsx +++ b/src/v4/social/components/StoryTab/StoryTabCommunity.tsx @@ -21,6 +21,7 @@ import { } from './styles'; import { isAdmin } from '~/helpers/permissions'; import { FormattedMessage } from 'react-intl'; +import useUser from '~/core/hooks/useUser'; interface StoryTabCommunityFeedProps { communityId: string; @@ -55,9 +56,10 @@ export const StoryTabCommunityFeed: React.FC = ({ co onClickStory(communityId, 'communityFeed'); }; - const { userRoles, client } = useSDK(); - const isGlobalAdmin = isAdmin(userRoles); - const hasStoryPermission = checkStoryPermission(client, communityId) || isGlobalAdmin; + const { currentUserId, client } = useSDK(); + const user = useUser(currentUserId); + const isGlobalAdmin = isAdmin(user?.roles); + const hasStoryPermission = isGlobalAdmin || checkStoryPermission(client, communityId); const hasStoryRing = stories?.length > 0; const hasUnSeen = stories.some((story) => !story?.isSeen); diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index 93eb225fc..5dcb410e7 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -77,7 +77,7 @@ export const renderer: CustomRenderer = ({ story, action, config }) => { const isOfficial = community?.isOfficial || false; const isCreator = creator?.userId === user?.userId; const isGlobalAdmin = isAdmin(user?.roles); - const isModerator = checkStoryPermission(client, community?.communityId) || isGlobalAdmin; + const isModerator = isGlobalAdmin || checkStoryPermission(client, community?.communityId); const computedStyles = { ...rendererStyles.storyContent, diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx index d7fc9cd42..579280275 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx @@ -77,7 +77,7 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler const isCreator = creator?.userId === user?.userId; const isGlobalAdmin = isAdmin(user?.roles); - const isModerator = checkStoryPermission(client, community?.communityId) || isGlobalAdmin; + const isModerator = isGlobalAdmin || checkStoryPermission(client, community?.communityId); const computedStyles = { ...storyContentStyles, From 0c95669a273a8a97c2fc99f8bb8262e8a151ac42 Mon Sep 17 00:00:00 2001 From: Kiattirat Sujjapongse Date: Mon, 27 May 2024 13:41:19 +0700 Subject: [PATCH 082/300] fix(reaction): ASC-22622 - fix reaction UI bugs (#355) * fix(reaction): update pagination * fix(reaction): use total reactor count from message / story / post / comments * fix(reaction): update ui * fix(reaction): update ui --- package.json | 2 +- pnpm-lock.yaml | 28 ++-- src/i18n/en.json | 2 +- src/v4/chat/hooks/useReactionByReference.ts | 47 +++++++ .../ReactionList/ReactionList.module.css | 20 ++- .../components/ReactionList/ReactionList.tsx | 121 +++++++++++++++--- .../ReactionList/ReactionListPanel.tsx | 108 ++++++++++------ .../components/ReactionList/styles.module.css | 0 8 files changed, 247 insertions(+), 81 deletions(-) create mode 100644 src/v4/chat/hooks/useReactionByReference.ts create mode 100644 src/v4/social/components/ReactionList/styles.module.css diff --git a/package.json b/package.json index 742139c97..9b40a2017 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "react-dom": ">=17.0.2" }, "devDependencies": { - "@amityco/ts-sdk": "^6.23.0", + "@amityco/ts-sdk": "^6.25.0", "@storybook/addon-a11y": "^7.6.7", "@storybook/addon-actions": "^7.6.7", "@storybook/addon-backgrounds": "^7.6.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 295db7885..faf632901 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -104,8 +104,8 @@ dependencies: devDependencies: '@amityco/ts-sdk': - specifier: ^6.23.0 - version: 6.23.0 + specifier: ^6.25.0 + version: 6.25.0 '@storybook/addon-a11y': specifier: ^7.6.7 version: 7.6.18 @@ -240,7 +240,7 @@ devDependencies: version: 7.1.1(webpack@5.91.0) ts-jest: specifier: ^29.1.1 - version: 29.1.2(@babel/core@7.24.4)(esbuild@0.18.20)(jest@29.7.0)(typescript@4.9.5) + version: 29.1.2(@babel/core@7.24.4)(esbuild@0.19.12)(jest@29.7.0)(typescript@4.9.5) tsup: specifier: ^7.3.0 version: 7.3.0(postcss@8.4.38)(typescript@4.9.5) @@ -263,8 +263,8 @@ packages: resolution: {integrity: sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==} dev: true - /@amityco/ts-sdk@6.23.0: - resolution: {integrity: sha512-0wLdz4h8hojNcM7WFpgk/sGCbe3ir+lCHu9+yxmSckMoK18/vOq7jSZNHCkElM99smqtU8R5+EoLurQTSW45Gw==} + /@amityco/ts-sdk@6.25.0: + resolution: {integrity: sha512-TLqxGVWZMqP0LdwR+9iGGx2a0G3GqoDF1gxC543CCg5dP+D0BsEqohiPl4x+Svxdi6L3975D5+t3X3HjG4CBWg==} engines: {node: '>=12', npm: '>=6'} dependencies: agentkeepalive: 4.5.0 @@ -7202,7 +7202,7 @@ packages: dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.91.0(esbuild@0.18.20) + webpack: 5.91.0(esbuild@0.19.12) dev: true /file-system-cache@2.3.0: @@ -11551,7 +11551,7 @@ packages: dependencies: file-loader: 6.2.0(webpack@5.91.0) loader-utils: 2.0.4 - webpack: 5.91.0(esbuild@0.18.20) + webpack: 5.91.0(esbuild@0.19.12) dev: true /synchronous-promise@2.0.17: @@ -11624,7 +11624,7 @@ packages: unique-string: 2.0.0 dev: true - /terser-webpack-plugin@5.3.10(esbuild@0.18.20)(webpack@5.91.0): + /terser-webpack-plugin@5.3.10(esbuild@0.19.12)(webpack@5.91.0): resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -11641,12 +11641,12 @@ packages: optional: true dependencies: '@jridgewell/trace-mapping': 0.3.25 - esbuild: 0.18.20 + esbuild: 0.19.12 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 terser: 5.30.4 - webpack: 5.91.0(esbuild@0.18.20) + webpack: 5.91.0(esbuild@0.19.12) dev: true /terser@5.30.4: @@ -11797,7 +11797,7 @@ packages: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} dev: true - /ts-jest@29.1.2(@babel/core@7.24.4)(esbuild@0.18.20)(jest@29.7.0)(typescript@4.9.5): + /ts-jest@29.1.2(@babel/core@7.24.4)(esbuild@0.19.12)(jest@29.7.0)(typescript@4.9.5): resolution: {integrity: sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==} engines: {node: ^16.10.0 || ^18.0.0 || >=20.0.0} hasBin: true @@ -11820,7 +11820,7 @@ packages: dependencies: '@babel/core': 7.24.4 bs-logger: 0.2.6 - esbuild: 0.18.20 + esbuild: 0.19.12 fast-json-stable-stringify: 2.1.0 jest: 29.7.0 jest-util: 29.7.0 @@ -12394,7 +12394,7 @@ packages: resolution: {integrity: sha512-poXpCylU7ExuvZK8z+On3kX+S8o/2dQ/SVYueKA0D4WEMXROXgY8Ez50/bQEUmvoSMMrWcrJqCHuhAbsiwg7Dg==} dev: true - /webpack@5.91.0(esbuild@0.18.20): + /webpack@5.91.0(esbuild@0.19.12): resolution: {integrity: sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==} engines: {node: '>=10.13.0'} hasBin: true @@ -12425,7 +12425,7 @@ packages: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(esbuild@0.18.20)(webpack@5.91.0) + terser-webpack-plugin: 5.3.10(esbuild@0.19.12)(webpack@5.91.0) watchpack: 2.4.1 webpack-sources: 3.2.3 transitivePeerDependencies: diff --git a/src/i18n/en.json b/src/i18n/en.json index 3afa6e9c5..fc61f27f0 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -441,5 +441,5 @@ "livechat.reaction.errorOnload": "Unable to load reactions", "livechat.reaction.emptyState": "No reactions yet", "livechat.reaction.emptyState.description": "Be the first to react to this message!", - "livechat.reaction.label.removeReaction": "Tap to remove reaction" + "livechat.reaction.label.removeReaction": "Click to remove reaction" } diff --git a/src/v4/chat/hooks/useReactionByReference.ts b/src/v4/chat/hooks/useReactionByReference.ts new file mode 100644 index 000000000..0b8576309 --- /dev/null +++ b/src/v4/chat/hooks/useReactionByReference.ts @@ -0,0 +1,47 @@ +import { + CommentRepository, + MessageRepository, + PostRepository, + StoryRepository, +} from '@amityco/ts-sdk'; +import { useEffect, useState } from 'react'; + +const useReactionByReference = (referenceType: Amity.ReactableType, referenceId: string) => { + const [reactionCount, setReactionCount] = useState(0); + const [reactions, setReactions] = useState>({}); + const [myReaction, setMyReaction] = useState(null); + + const updateReaction = ({ + data, + loading, + error, + }: Amity.LiveObject) => { + if (loading || error) return; + + setReactionCount(data.reactionsCount); + setReactions(data.reactions); + setMyReaction(data.myReaction); + }; + + useEffect(() => { + if (referenceType === 'message') { + MessageRepository.getMessage(referenceId, updateReaction); + } else if (referenceType === 'story') { + StoryRepository.getStoryByStoryId(referenceId, updateReaction); + } else if (referenceType === 'comment') { + CommentRepository.getComment(referenceId, updateReaction); + } else if (referenceType === 'post') { + PostRepository.getPost(referenceId, updateReaction); + } else { + throw new Error('Unsupported reference type'); + } + }, [referenceId, referenceType]); + + return { + reactions, + reactionCount, + myReaction, + }; +}; + +export default useReactionByReference; diff --git a/src/v4/social/components/ReactionList/ReactionList.module.css b/src/v4/social/components/ReactionList/ReactionList.module.css index 59cc3b21d..04c531acc 100644 --- a/src/v4/social/components/ReactionList/ReactionList.module.css +++ b/src/v4/social/components/ReactionList/ReactionList.module.css @@ -49,8 +49,9 @@ .userList { display: flex; - flex-wrap: wrap; gap: var(--asc-spacing-s1); + width: 100%; + flex-direction: column; } .userItem { @@ -58,7 +59,6 @@ align-items: center; gap: var(--asc-spacing-s1); background: var(--asc-color-base-background); - padding: var(--asc-spacing-s1); border-radius: var(--asc-border-radius-sm); width: 100%; border-bottom: 1px solid var(--asc-color-base-shade4); @@ -67,6 +67,7 @@ .userDetailsContainer { display: flex; color: var(--asc-color-base-default); + padding: var(--asc-spacing-s1); justify-content: space-between; width: 100%; } @@ -135,4 +136,19 @@ 100% { opacity: 0.6; } +} + +.infiniteScrollContainer { + display: flex; + flex-grow: 1; + overflow: auto; + width: 100%; + + > div { + width: 100%; + } +} + +.reactionPanel { + height: 100%; } \ No newline at end of file diff --git a/src/v4/social/components/ReactionList/ReactionList.tsx b/src/v4/social/components/ReactionList/ReactionList.tsx index c254682f3..66b4afbbd 100644 --- a/src/v4/social/components/ReactionList/ReactionList.tsx +++ b/src/v4/social/components/ReactionList/ReactionList.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useMemo, useRef, useState } from 'react'; import styles from './ReactionList.module.css'; import { useReactionsCollection } from '~/v4/social/hooks/collections/useReactionsCollection'; import { Typography } from '~/v4/core/components'; @@ -10,6 +10,8 @@ import { ReactionListError } from '~/v4/social/components/ReactionList/ReactionL import { ReactionListEmptyState } from '~/v4/social/components/ReactionList/ReactionListEmptyState'; import { ReactionListLoadingState } from '~/v4/social/components/ReactionList/ReactionListLoadingState'; import useReaction from '~/v4/chat/hooks/useReaction'; +import useReactionByReference from '~/v4/chat/hooks/useReactionByReference'; +import FallbackReaction from '~/v4/icons/FallbackReaction'; interface ReactionListProps { pageId: string; @@ -17,16 +19,36 @@ interface ReactionListProps { referenceType: Amity.ReactableType; } +const UNKNOWN_TAB = 'unknown'; + +const filterReactionsByTab = ( + reactions: Amity.Reactor[], + activeTab: string, + allConfigReactions: string[], +) => { + if (activeTab === 'All') return reactions; + if (activeTab === UNKNOWN_TAB) { + return reactions.filter((reaction) => !allConfigReactions.includes(reaction.reactionName)); + } + return reactions.filter((reaction) => reaction.reactionName === activeTab.toLowerCase()); +}; + const RenderCondition = ({ filteredReactions, + hasMore, + loadMore, isLoading, removeReaction, error, + currentRef, }: { filteredReactions: Amity.Reactor[]; isLoading: boolean; + hasMore: boolean; + loadMore: () => void; removeReaction: (reaction: string) => Promise; error: Error | null; + currentRef: HTMLDivElement | null; }) => { if (isLoading) { return ; @@ -37,18 +59,40 @@ const RenderCondition = ({ } if (filteredReactions.length === 0) { + if (isLoading) { + return ; + } + return ; } return ( - + ); }; export const ReactionList = ({ pageId = '*', referenceId, referenceType }: ReactionListProps) => { const componentId = 'reaction_list'; - const { reactions, error, isLoading } = useReactionsCollection({ referenceId, referenceType }); + const { reactions, error, isLoading, hasMore, loadMore } = useReactionsCollection({ + referenceId, + referenceType, + limit: 25, + }); + + const { reactions: allReacted, reactionCount } = useReactionByReference( + referenceType, + referenceId, + ); + const [activeTab, setActiveTab] = useState('All'); + const containerRef = useRef(null); const { config } = useCustomReaction(); const { removeReaction } = useReaction(referenceType, referenceId); @@ -56,13 +100,31 @@ export const ReactionList = ({ pageId = '*', referenceId, referenceType }: React setActiveTab(tab); }; - const filteredReactions = - activeTab === 'All' - ? reactions - : reactions.filter((reaction) => reaction.reactionName === activeTab.toLowerCase()); - if (reactions == null || !config) return null; + const allConfigReactions = useMemo( + () => config.map((reactionConfigItem) => reactionConfigItem.name), + [config], + ); + + const unknownReaction = useMemo( + () => + Object.keys(allReacted).filter((reaction) => { + return !allConfigReactions.includes(reaction); + }), + [allReacted], + ); + + const totalUnknownReactionCount = useMemo( + () => unknownReaction.reduce((acc, curr) => acc + allReacted[curr], 0), + [allReacted], + ); + + const filteredReactions = useMemo( + () => filterReactionsByTab(reactions, activeTab, allConfigReactions), + [reactions, activeTab], + ); + return (
@@ -73,18 +135,14 @@ export const ReactionList = ({ pageId = '*', referenceId, referenceType }: React onClick={() => handleTabClick('All')} > - All {abbreviateCount(reactions.length)} + All {abbreviateCount(reactionCount)}
{config.map((reactionConfigItem) => { const { name: reactionType, image } = reactionConfigItem; - const count = reactions.filter( - (reaction) => reaction.reactionName === reactionType, - ).length; - - if (!count) return null; + if (!allReacted[reactionType]) return null; return (
- {abbreviateCount(count)} + {abbreviateCount(allReacted[reactionType])}
); })} + + {unknownReaction.length > 0 && ( +
handleTabClick(UNKNOWN_TAB)} + > + + + + {abbreviateCount(totalUnknownReactionCount)} + + +
+ )}
- +
+ +
); }; diff --git a/src/v4/social/components/ReactionList/ReactionListPanel.tsx b/src/v4/social/components/ReactionList/ReactionListPanel.tsx index d8294724d..6bfbe4f99 100644 --- a/src/v4/social/components/ReactionList/ReactionListPanel.tsx +++ b/src/v4/social/components/ReactionList/ReactionListPanel.tsx @@ -6,63 +6,87 @@ import { ReactionIcon } from '~/v4/social/components/ReactionList/ReactionIcon'; import { useCustomReaction } from '~/v4/core/providers/CustomReactionProvider'; import useSDK from '~/core/hooks/useSDK'; import { FormattedMessage } from 'react-intl'; +import InfiniteScroll from 'react-infinite-scroll-component'; export const ReactionListPanel = ({ filteredReactions, removeReaction, + hasMore, + loadMore, + isLoading, + currentRef, }: { filteredReactions: Amity.Reactor[]; removeReaction: (reaction: string) => Promise; + hasMore: boolean; + loadMore: () => void; + isLoading: boolean; + currentRef: HTMLDivElement | null; }) => { const { currentUserId } = useSDK(); const { config } = useCustomReaction(); const reactionList = useMemo(() => config.map(({ name }) => name), [config]); + if (!currentRef || !filteredReactions) return null; + return ( -
- {filteredReactions.map((reaction) => { - return ( - -
-
-
- - - {reaction.user?.displayName} - {currentUserId === reaction.user?.userId && ( - <> -
-
removeReaction(reaction.reactionName)}> - - - -
- - )} -
-
+
+ Loading... : null} + dataLength={filteredReactions.length || 0} + style={{ display: 'flex', width: '100%' }} + height={currentRef.clientHeight} + > +
+ {filteredReactions.map((reaction) => { + return ( + +
+
+
+ + + {reaction.user?.displayName} + {currentUserId === reaction.user?.userId && ( + <> +
+
removeReaction(reaction.reactionName)}> + + + +
+ + )} +
+
-
- {reactionList.includes(reaction.reactionName) ? ( - name === reaction.reactionName)! - } - className={styles.reactionIcon} - /> - ) : ( - - )} +
+ {reactionList.includes(reaction.reactionName) ? ( + name === reaction.reactionName)! + } + className={styles.reactionIcon} + /> + ) : ( + + )} +
+
-
-
- - ); - })} + + ); + })} +
+
); }; diff --git a/src/v4/social/components/ReactionList/styles.module.css b/src/v4/social/components/ReactionList/styles.module.css new file mode 100644 index 000000000..e69de29bb From c138b5739d74d78f41877d8cdb7f68216582b6de Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Mon, 27 May 2024 15:59:22 +0700 Subject: [PATCH 083/300] fix: ASC-22740 - create story permission condition to create story (#356) * fix: create story condition * fix: condition * fix: condition * fix: condition * fix: condition --- .../components/StoryTab/StoryTabCommunity.tsx | 6 ++++-- .../StoryViewer/Renderers/Image.tsx | 10 ++++++---- .../StoryViewer/Renderers/Video.tsx | 13 +++++++------ 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/v4/social/components/StoryTab/StoryTabCommunity.tsx b/src/v4/social/components/StoryTab/StoryTabCommunity.tsx index faa615072..0e23dccfb 100644 --- a/src/v4/social/components/StoryTab/StoryTabCommunity.tsx +++ b/src/v4/social/components/StoryTab/StoryTabCommunity.tsx @@ -19,7 +19,7 @@ import { StoryTitle, StoryWrapper, } from './styles'; -import { isAdmin } from '~/helpers/permissions'; +import { isAdmin, isModerator } from '~/helpers/permissions'; import { FormattedMessage } from 'react-intl'; import useUser from '~/core/hooks/useUser'; @@ -59,7 +59,9 @@ export const StoryTabCommunityFeed: React.FC = ({ co const { currentUserId, client } = useSDK(); const user = useUser(currentUserId); const isGlobalAdmin = isAdmin(user?.roles); - const hasStoryPermission = isGlobalAdmin || checkStoryPermission(client, communityId); + const isCommunityModerator = isModerator(user?.roles); + const hasStoryPermission = + isGlobalAdmin || isCommunityModerator || checkStoryPermission(client, communityId); const hasStoryRing = stories?.length > 0; const hasUnSeen = stories.some((story) => !story?.isSeen); diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index 5dcb410e7..55612a5cd 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -25,7 +25,7 @@ import useUser from '~/core/hooks/useUser'; import { BottomSheet } from '~/v4/core/components/BottomSheet'; import { Typography } from '~/v4/core/components'; import { Button } from '~/v4/core/components/Button'; -import { isAdmin } from '~/helpers/permissions'; +import { isAdmin, isModerator } from '~/helpers/permissions'; export const renderer: CustomRenderer = ({ story, action, config }) => { const { formatMessage } = useIntl(); @@ -77,7 +77,9 @@ export const renderer: CustomRenderer = ({ story, action, config }) => { const isOfficial = community?.isOfficial || false; const isCreator = creator?.userId === user?.userId; const isGlobalAdmin = isAdmin(user?.roles); - const isModerator = isGlobalAdmin || checkStoryPermission(client, community?.communityId); + const isCommunityModerator = isModerator(user?.roles); + const haveStoryPermission = + isGlobalAdmin || isCommunityModerator || checkStoryPermission(client, community?.communityId); const computedStyles = { ...rendererStyles.storyContent, @@ -178,7 +180,7 @@ export const renderer: CustomRenderer = ({ story, action, config }) => { heading={heading} subheading={subheading} isHaveActions={actions?.length > 0} - haveStoryPermission={isModerator} + haveStoryPermission={haveStoryPermission} isOfficial={isOfficial} isPaused={isPaused} onPlay={play} @@ -282,7 +284,7 @@ export const renderer: CustomRenderer = ({ story, action, config }) => { totalLikes={totalLikes} isLiked={isLiked} onClickComment={openCommentSheet} - showImpression={isCreator || isModerator} + showImpression={isCreator || haveStoryPermission} /> ); diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx index 579280275..f2f7bafa7 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx @@ -24,7 +24,7 @@ import { motion, PanInfo, useAnimationControls } from 'framer-motion'; import rendererStyles from './Renderers.module.css'; import useUser from '~/core/hooks/useUser'; -import { isAdmin } from '~/helpers/permissions'; +import { isAdmin, isModerator } from '~/helpers/permissions'; export const renderer: CustomRenderer = ({ story, action, config, messageHandler }) => { const { formatMessage } = useIntl(); @@ -62,8 +62,6 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler imageSize: 'small', }); - const isOfficial = community?.isOfficial || false; - const heading =
{community?.displayName}
; const subheading = createdAt && creator?.displayName ? ( @@ -75,9 +73,12 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler '' ); + const isOfficial = community?.isOfficial || false; const isCreator = creator?.userId === user?.userId; const isGlobalAdmin = isAdmin(user?.roles); - const isModerator = isGlobalAdmin || checkStoryPermission(client, community?.communityId); + const isCommunityModerator = isModerator(user?.roles); + const haveStoryPermission = + isGlobalAdmin || isCommunityModerator || checkStoryPermission(client, community?.communityId); const computedStyles = { ...storyContentStyles, @@ -214,7 +215,7 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler heading={heading} subheading={subheading} isHaveActions={actions?.length > 0} - haveStoryPermission={isModerator} + haveStoryPermission={haveStoryPermission} isOfficial={isOfficial} isPaused={isPaused} onPlay={play} @@ -320,7 +321,7 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler totalLikes={totalLikes} isLiked={isLiked} onClickComment={openCommentSheet} - showImpression={isCreator || isModerator} + showImpression={isCreator || haveStoryPermission} /> ); From be48905cfc8cc05837738611d4e58646304c332c Mon Sep 17 00:00:00 2001 From: Kiattirat S Date: Tue, 28 May 2024 15:57:45 +0700 Subject: [PATCH 084/300] feat(SDK): test build From cc22a385d72c679407aab0324003b66077813fd7 Mon Sep 17 00:00:00 2001 From: Kiattirat S Date: Tue, 28 May 2024 16:17:56 +0700 Subject: [PATCH 085/300] feat(SDK): upgrade ts-sdk version --- package.json | 2 +- pnpm-lock.yaml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 9b40a2017..d5b3dcd82 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "react-dom": ">=17.0.2" }, "devDependencies": { - "@amityco/ts-sdk": "^6.25.0", + "@amityco/ts-sdk": "^6.25.1", "@storybook/addon-a11y": "^7.6.7", "@storybook/addon-actions": "^7.6.7", "@storybook/addon-backgrounds": "^7.6.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index faf632901..3ebcec89f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -104,8 +104,8 @@ dependencies: devDependencies: '@amityco/ts-sdk': - specifier: ^6.25.0 - version: 6.25.0 + specifier: ^6.25.1 + version: 6.25.1 '@storybook/addon-a11y': specifier: ^7.6.7 version: 7.6.18 @@ -263,8 +263,8 @@ packages: resolution: {integrity: sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==} dev: true - /@amityco/ts-sdk@6.25.0: - resolution: {integrity: sha512-TLqxGVWZMqP0LdwR+9iGGx2a0G3GqoDF1gxC543CCg5dP+D0BsEqohiPl4x+Svxdi6L3975D5+t3X3HjG4CBWg==} + /@amityco/ts-sdk@6.25.1: + resolution: {integrity: sha512-Xm0BhctI1bMw2IDtpd2weY+BIF5bVhu0CXsG6qkZMVNx3whGteh24YDD3J6ZM8h29RaZp3VXb0yHGZLh6InKAw==} engines: {node: '>=12', npm: '>=6'} dependencies: agentkeepalive: 4.5.0 From 3a74cb6664a171d3064640bc259c9d584e45d3e4 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Fri, 31 May 2024 17:50:00 +0700 Subject: [PATCH 086/300] fix: ASC-21529 - view story wrapper css (#359) * fix: css * refactor: view story page condition --- .../social/pages/StoryPage/ViewStoryPage.tsx | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/src/v4/social/pages/StoryPage/ViewStoryPage.tsx b/src/v4/social/pages/StoryPage/ViewStoryPage.tsx index 2a452ef3c..1551ac395 100644 --- a/src/v4/social/pages/StoryPage/ViewStoryPage.tsx +++ b/src/v4/social/pages/StoryPage/ViewStoryPage.tsx @@ -13,24 +13,12 @@ interface AmityViewStoryPageProps { const AmityViewStoryPage: React.FC = ({ type }) => { const { page } = useNavigation(); - const renderContent = () => { - switch (type) { - case 'communityFeed': - if (page.type === PageTypes.ViewStory && page.targetId) { - return ; - } - return null; - case 'globalFeed': - if (page.type === PageTypes.ViewStory && page.targetId) { - return ; - } - return null; - default: - return null; - } - }; + if (page.type !== PageTypes.ViewStory || !page.targetId) return null; - return
{renderContent()}
; + if (type === 'communityFeed') return ; + if (type === 'globalFeed') return ; + + return null; }; export default AmityViewStoryPage; From 00f978f43d1d26892ce209dafed2b2b1f926b5e4 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Fri, 31 May 2024 18:01:44 +0700 Subject: [PATCH 087/300] fix: ASC-20521 - story delete condition (#360) * fix: story delete condition * fix: condition --- src/v4/social/pages/StoryPage/CommunityFeedStory.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx index 159309e4f..13b80aabb 100644 --- a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx @@ -84,20 +84,19 @@ export const CommunityFeedStory = ({ communityId }: CommunityFeedStoryProps) => const isModerator = checkStoryPermission(client, communityId); const confirmDeleteStory = (storyId: string) => { - const isLastStory = currentIndex === 0; + const isLastStory = currentIndex === stories.length - 1; confirm({ title: formatMessage({ id: 'storyViewer.action.confirmModal.title' }), content: formatMessage({ id: 'storyViewer.action.confirmModal.content' }), okText: formatMessage({ id: 'delete' }), onOk: async () => { - previousStory(); - if (isLastStory) { - onBack(); - } await StoryRepository.softDeleteStory(storyId); notification.success({ content: formatMessage({ id: 'storyViewer.notification.deleted' }), }); + if (isLastStory) { + onBack(); + } }, }); }; From 22062faf8d1cb63236200ecf47d8dc159512b205 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Tue, 4 Jun 2024 10:34:41 +0700 Subject: [PATCH 088/300] fix: ASC-20505 - condition for non member (#361) * fix: condition for non member * fix: hyperlink config * fix: condition --- src/i18n/en.json | 1 + .../HyperLinkConfig/HyperLinkConfig.tsx | 2 + .../social/elements/HyperLink/HyperLink.tsx | 2 +- .../Comment/UIComment.module.css | 1 + .../internal-components/Comment/UIComment.tsx | 64 ++++++++++--------- .../internal-components/Comment/index.tsx | 5 +- .../CommentList/CommentList.tsx | 1 - src/v4/social/pages/DraftsPage/DraftsPage.tsx | 24 +++---- 8 files changed, 55 insertions(+), 45 deletions(-) diff --git a/src/i18n/en.json b/src/i18n/en.json index fc61f27f0..aec686ee7 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -385,6 +385,7 @@ "storyViewer.commentComposeBar.submit": "Post", "storyDraft.button.shareStory": "Share story", + "storyDraft.notification.hyperlink.error": "Can’t add more than one link to your story.", "storyTab.title": "Story", diff --git a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx index 26b7f13d2..9a480ca44 100644 --- a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx +++ b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx @@ -12,6 +12,7 @@ import { Trash2Icon } from '~/icons'; import styles from './HyperLinkConfig.module.css'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; import { Button } from '~/v4/core/components/Button'; +import { useNotifications } from '~/v4/core/providers/NotificationProvider'; interface HyperLinkConfigProps { pageId: '*'; @@ -33,6 +34,7 @@ export const HyperLinkConfig = ({ onRemove, }: HyperLinkConfigProps) => { const { confirm } = useConfirmContext(); + const componentId = 'hyper_link_config_component'; const { getConfig } = useCustomization(); const componentConfig = getConfig(`${pageId}/${componentId}/*`); diff --git a/src/v4/social/elements/HyperLink/HyperLink.tsx b/src/v4/social/elements/HyperLink/HyperLink.tsx index e4dff3e56..f3252c1b0 100644 --- a/src/v4/social/elements/HyperLink/HyperLink.tsx +++ b/src/v4/social/elements/HyperLink/HyperLink.tsx @@ -3,7 +3,7 @@ import { LinkIcon } from '~/v4/social/icons'; import styles from './HyperLink.module.css'; interface LinkButtonProps extends React.AnchorHTMLAttributes { - href: string; + href?: string; } export const HyperLink: React.FC = ({ href, children, ...rest }) => { diff --git a/src/v4/social/internal-components/Comment/UIComment.module.css b/src/v4/social/internal-components/Comment/UIComment.module.css index 879e00e65..dbbade992 100644 --- a/src/v4/social/internal-components/Comment/UIComment.module.css +++ b/src/v4/social/internal-components/Comment/UIComment.module.css @@ -127,6 +127,7 @@ .reactionListButton { border: none; color: var(--asc-color-primary-default); + padding: 0; } .content { diff --git a/src/v4/social/internal-components/Comment/UIComment.tsx b/src/v4/social/internal-components/Comment/UIComment.tsx index 8ded27d94..927ff22d1 100644 --- a/src/v4/social/internal-components/Comment/UIComment.tsx +++ b/src/v4/social/internal-components/Comment/UIComment.tsx @@ -59,6 +59,7 @@ interface StyledCommentProps { }[]; }) => void; queryMentionees: QueryMentioneesFnType; + isMember?: boolean; isLiked?: boolean; isReported?: boolean; isReplyComment?: boolean; @@ -94,6 +95,7 @@ const UIComment = ({ isEditing, onChange, queryMentionees, + isMember = false, isBanned, isLiked, mentionees, @@ -145,8 +147,8 @@ const UIComment = ({
)} - {!isEditing && (canLike || canReply || options.length > 0) && ( -
+
+ {!isEditing && isMember && (canLike || canReply || options.length > 0) && (
{formatTimeAgo(createdAt)} @@ -188,36 +190,36 @@ const UIComment = ({
- {reactionsCount > 0 && ( - - )} -
- )} +
+ + )} +
); diff --git a/src/v4/social/internal-components/Comment/index.tsx b/src/v4/social/internal-components/Comment/index.tsx index 4f12418fe..cb08b0f57 100644 --- a/src/v4/social/internal-components/Comment/index.tsx +++ b/src/v4/social/internal-components/Comment/index.tsx @@ -184,6 +184,8 @@ export const Comment = ({ commentId, readonly, onClickReply }: CommentProps) => }); }; + const isMember = story?.community?.isJoined; + const options = [ canEdit ? { @@ -248,6 +250,7 @@ export const Comment = ({ commentId, readonly, onClickReply }: CommentProps) => isLiked={isLiked} isReported={isFlaggedByMe} isReplyComment={isReplyComment} + isMember={isMember} onChange={onChange} onClickOverflowMenu={toggleBottomSheet} options={options} @@ -312,7 +315,7 @@ export const Comment = ({ commentId, readonly, onClickReply }: CommentProps) => mountPoint={document.getElementById('asc-uikit-stories-viewer') as HTMLElement} detent="full-height" > - + ); diff --git a/src/v4/social/internal-components/CommentList/CommentList.tsx b/src/v4/social/internal-components/CommentList/CommentList.tsx index 00718965e..57b58a3a2 100644 --- a/src/v4/social/internal-components/CommentList/CommentList.tsx +++ b/src/v4/social/internal-components/CommentList/CommentList.tsx @@ -4,7 +4,6 @@ import useCommentsCollection from '~/social/hooks/collections/useCommentsCollect import { Comment } from '../Comment'; import styles from './CommentList.module.css'; import { ExpandIcon } from '~/v4/social/icons'; -import { Button } from '~/v4/core/components'; import { LoadMoreWrapper } from '~/v4/core/components/LoadMoreWrapper/LoadMoreWrapper'; interface CommentListProps { diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index 70163b802..ea6fcbe98 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -20,7 +20,7 @@ import { StoryRepository } from '@amityco/ts-sdk'; import { HyperLinkConfig } from '~/v4/social/components'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; -import { useNotifications } from '~/v4/core/providers/NotificationProvider'; +import { useNotificationData, useNotifications } from '~/v4/core/providers/NotificationProvider'; import { useNavigation } from '~/social/providers/NavigationProvider'; import { PageTypes } from '~/social/constants'; import { BaseVideoPreview } from '../../internal-components/VideoPreview'; @@ -164,6 +164,16 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor ]); }; + const handleOnClickHyperLinkActionButton = () => { + if (hyperLink[0]?.data?.url === '') { + setIsHyperLinkBottomSheetOpen(true); + return; + } + notification.info({ + content: formatMessage({ id: 'storyDraft.notification.hyperlink.error' }), + }); + }; + useEffect(() => { const extractColorsFromImage = async (fileTarget: File) => { const img = await readFileAsync(fileTarget); @@ -211,7 +221,7 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor setIsHyperLinkBottomSheetOpen(true)} + onClick={handleOnClickHyperLinkActionButton} />
@@ -250,15 +260,7 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor ) : null} {hyperLink[0]?.data?.url && (
- + setIsHyperLinkBottomSheetOpen(true)}> {hyperLink[0]?.data?.customText || hyperLink[0].data.url.replace(/^https?:\/\//, '')}
From 478f3a6555177743133711559572b3de061f1d46 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Tue, 4 Jun 2024 14:01:16 +0700 Subject: [PATCH 089/300] fix: hook (#362) --- src/i18n/en.json | 2 + src/v4/social/hooks/index.ts | 1 + src/v4/social/hooks/useCommentFlaggedByMe.tsx | 71 +++++++++++++++++++ ...yByStoryId.ts => useGetStoryByStoryId.tsx} | 0 .../internal-components/Comment/index.tsx | 3 +- 5 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 src/v4/social/hooks/useCommentFlaggedByMe.tsx rename src/v4/social/hooks/{useGetStoryByStoryId.ts => useGetStoryByStoryId.tsx} (100%) diff --git a/src/i18n/en.json b/src/i18n/en.json index aec686ee7..bf5d0424c 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -381,6 +381,8 @@ "storyViewer.commentSheet.disabled": "Comments are disabled for this story", "storyViewer.commentSheet.replyingTo": "Replying to", "storyViewer.toast.like.disabled": "Join community to interact with all stories", + "storyViewer.toast.comment.reported": "Comment reported", + "storyViewer.toast.comment.unreported": "Comment unreported", "storyViewer.commentComposeBar.submit": "Post", diff --git a/src/v4/social/hooks/index.ts b/src/v4/social/hooks/index.ts index 38695fed4..e19849fde 100644 --- a/src/v4/social/hooks/index.ts +++ b/src/v4/social/hooks/index.ts @@ -1,2 +1,3 @@ export * from './collections/useReactionsCollection'; export { useGetActiveStoriesByTarget } from './useGetActiveStories'; +export { useCommentFlaggedByMe } from './useCommentFlaggedByMe'; diff --git a/src/v4/social/hooks/useCommentFlaggedByMe.tsx b/src/v4/social/hooks/useCommentFlaggedByMe.tsx new file mode 100644 index 000000000..f318ebd33 --- /dev/null +++ b/src/v4/social/hooks/useCommentFlaggedByMe.tsx @@ -0,0 +1,71 @@ +import { CommentRepository } from '@amityco/ts-sdk'; +import { useQuery } from '@tanstack/react-query'; +import React, { useEffect, useState } from 'react'; +import { FormattedMessage } from 'react-intl'; +import { useNotifications } from '~/v4/core/providers/NotificationProvider'; + +export const useCommentFlaggedByMe = (commentId?: string) => { + const notification = useNotifications(); + const [isFlaggedByMe, setIsFlaggedByMe] = useState(false); + + const { data, isLoading, refetch } = useQuery({ + queryKey: ['asc-uikit', 'CommentRepository', 'isCommentFlaggedByMe', commentId], + queryFn: () => { + return CommentRepository.isCommentFlaggedByMe(commentId as string); + }, + enabled: commentId != null, + }); + + useEffect(() => { + if (data != null) { + setIsFlaggedByMe(data); + } + }, [data]); + + const flagComment = async () => { + if (commentId == null) return; + try { + setIsFlaggedByMe(true); + await CommentRepository.flagComment(commentId); + } catch (_error) { + setIsFlaggedByMe(false); + } finally { + refetch(); + } + }; + + const unflagComment = async () => { + if (commentId == null) return; + try { + setIsFlaggedByMe(false); + await CommentRepository.unflagComment(commentId); + } catch (_error) { + setIsFlaggedByMe(true); + } finally { + refetch(); + } + }; + + const toggleFlagComment = async () => { + if (commentId == null) return; + if (isFlaggedByMe) { + await unflagComment(); + notification.success({ + content: , + }); + } else { + await flagComment(); + notification.success({ + content: , + }); + } + }; + + return { + isLoading, + isFlaggedByMe, + flagComment, + unflagComment, + toggleFlagComment, + }; +}; diff --git a/src/v4/social/hooks/useGetStoryByStoryId.ts b/src/v4/social/hooks/useGetStoryByStoryId.tsx similarity index 100% rename from src/v4/social/hooks/useGetStoryByStoryId.ts rename to src/v4/social/hooks/useGetStoryByStoryId.tsx diff --git a/src/v4/social/internal-components/Comment/index.tsx b/src/v4/social/internal-components/Comment/index.tsx index cb08b0f57..f94a3928c 100644 --- a/src/v4/social/internal-components/Comment/index.tsx +++ b/src/v4/social/internal-components/Comment/index.tsx @@ -8,7 +8,6 @@ import { extractMetadata, isNonNullable, Mentioned, - Mentionees, Metadata, parseMentionsMarkup, } from '~/v4/helpers/utils'; @@ -17,7 +16,6 @@ import useSDK from '~/core/hooks/useSDK'; import useUser from '~/core/hooks/useUser'; import { CommentRepository, ReactionRepository } from '@amityco/ts-sdk'; -import useCommentFlaggedByMe from '~/social/hooks/useCommentFlaggedByMe'; import useCommentPermission from '~/social/hooks/useCommentPermission'; import useCommentSubscription from '~/social/hooks/useCommentSubscription'; import useImage from '~/core/hooks/useImage'; @@ -36,6 +34,7 @@ import { Button, BottomSheet, Typography } from '~/v4/core/components'; import styles from './Comment.module.css'; import { TrashIcon, PenIcon, FlagIcon } from '~/v4/social/icons'; import { LoadingIndicator } from '~/v4/social/internal-components/LoadingIndicator'; +import { useCommentFlaggedByMe } from '~/v4/social/hooks'; const REPLIES_PER_PAGE = 5; From 6efd829ea26d455fbbddd689922815b0a0a250f6 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Wed, 5 Jun 2024 11:43:34 +0700 Subject: [PATCH 090/300] fix: ASC-22947 - hyperlink config bottom sheet condition (#367) * fix: hyperlink action condition * fix: hyperlink css * fix: remove unused * fix: hyperlink config condition --- .../elements/HyperLink/HyperLink.module.css | 1 + .../ShareStoryButton/ShareStoryButton.tsx | 2 +- src/v4/social/pages/DraftsPage/DraftsPage.tsx | 25 ++++++++++++------- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/v4/social/elements/HyperLink/HyperLink.module.css b/src/v4/social/elements/HyperLink/HyperLink.module.css index e16c56d0e..ea8d3ae60 100644 --- a/src/v4/social/elements/HyperLink/HyperLink.module.css +++ b/src/v4/social/elements/HyperLink/HyperLink.module.css @@ -15,6 +15,7 @@ max-width: 200px; border: var(--asc-color-base-shade4); text-decoration: none; + cursor: pointer; } .hyperlinkIcon { diff --git a/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx b/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx index 3b66ac7b8..297d0dacc 100644 --- a/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx +++ b/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx @@ -4,7 +4,7 @@ import { useIntl } from 'react-intl'; import { isValidHttpUrl } from '~/utils'; import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; import { Icon } from '~/v4/core/components/Icon'; -import { backgroundImage as communityBackgroundImage } from '~/icons/Community'; +import { backgroundImage as communityBackgroundImage } from '~/v4/icons/Community'; import styles from './ShareStoryButton.module.css'; import { Avatar } from '~/v4/core/components'; diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index ea6fcbe98..f87d67ebf 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -5,7 +5,6 @@ import { readFileAsync } from '~/helpers'; import styles from './DraftsPage.module.css'; import { SubmitHandler } from 'react-hook-form'; -import Truncate from 'react-truncate-markup'; import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; import { @@ -20,7 +19,7 @@ import { StoryRepository } from '@amityco/ts-sdk'; import { HyperLinkConfig } from '~/v4/social/components'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; -import { useNotificationData, useNotifications } from '~/v4/core/providers/NotificationProvider'; +import { useNotifications } from '~/v4/core/providers/NotificationProvider'; import { useNavigation } from '~/social/providers/NavigationProvider'; import { PageTypes } from '~/social/constants'; import { BaseVideoPreview } from '../../internal-components/VideoPreview'; @@ -52,7 +51,15 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor data: { url: string; customText: string }; type: Amity.StoryItemType; }[] - >([]); + >([ + { + data: { + url: '', + customText: '', + }, + type: 'hyperlink' as Amity.StoryItemType, + }, + ]); const handleHyperLinkBottomSheetClose = () => { setIsHyperLinkBottomSheetOpen(false); @@ -165,13 +172,13 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor }; const handleOnClickHyperLinkActionButton = () => { - if (hyperLink[0]?.data?.url === '') { - setIsHyperLinkBottomSheetOpen(true); + if (hyperLink[0]?.data?.url) { + notification.info({ + content: formatMessage({ id: 'storyDraft.notification.hyperlink.error' }), + }); return; } - notification.info({ - content: formatMessage({ id: 'storyDraft.notification.hyperlink.error' }), - }); + setIsHyperLinkBottomSheetOpen(true); }; useEffect(() => { @@ -272,7 +279,7 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor onClose={handleHyperLinkBottomSheetClose} onSubmit={onSubmitHyperLink} onRemove={onRemoveHyperLink} - isHaveHyperLink={hyperLink.length > 0} + isHaveHyperLink={!!hyperLink?.[0]?.data?.url} />
From e887e78e7aa6cd5a324b3361c30ee9a8b544ee5f Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Wed, 5 Jun 2024 17:02:28 +0700 Subject: [PATCH 091/300] fix: ASC-22949 - video story bug when delete (#366) * fix: video story bug when delete * fix: remove console.log * fix: condition --- .../StoryViewer/Renderers/Video.tsx | 6 ----- src/v4/social/pages/DraftsPage/DraftsPage.tsx | 27 +++++++------------ .../pages/StoryPage/CommunityFeedStory.tsx | 10 ++++--- .../pages/StoryPage/GlobalFeedStory.tsx | 4 +-- 4 files changed, 17 insertions(+), 30 deletions(-) diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx index f2f7bafa7..014deea96 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx @@ -335,12 +335,6 @@ const storyContentStyles = { position: 'relative' as const, }; -const videoContainerStyles = { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', -}; - export const tester: Tester = (story) => { return { condition: story.type === 'video', diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index f87d67ebf..21353d317 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -5,6 +5,7 @@ import { readFileAsync } from '~/helpers'; import styles from './DraftsPage.module.css'; import { SubmitHandler } from 'react-hook-form'; +import Truncate from 'react-truncate-markup'; import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; import { @@ -19,7 +20,7 @@ import { StoryRepository } from '@amityco/ts-sdk'; import { HyperLinkConfig } from '~/v4/social/components'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; -import { useNotifications } from '~/v4/core/providers/NotificationProvider'; +import { useNotificationData, useNotifications } from '~/v4/core/providers/NotificationProvider'; import { useNavigation } from '~/social/providers/NavigationProvider'; import { PageTypes } from '~/social/constants'; import { BaseVideoPreview } from '../../internal-components/VideoPreview'; @@ -51,15 +52,7 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor data: { url: string; customText: string }; type: Amity.StoryItemType; }[] - >([ - { - data: { - url: '', - customText: '', - }, - type: 'hyperlink' as Amity.StoryItemType, - }, - ]); + >([]); const handleHyperLinkBottomSheetClose = () => { setIsHyperLinkBottomSheetOpen(false); @@ -172,13 +165,13 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor }; const handleOnClickHyperLinkActionButton = () => { - if (hyperLink[0]?.data?.url) { - notification.info({ - content: formatMessage({ id: 'storyDraft.notification.hyperlink.error' }), - }); + if (hyperLink[0]?.data?.url === '') { + setIsHyperLinkBottomSheetOpen(true); return; } - setIsHyperLinkBottomSheetOpen(true); + notification.info({ + content: formatMessage({ id: 'storyDraft.notification.hyperlink.error' }), + }); }; useEffect(() => { @@ -279,7 +272,7 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor onClose={handleHyperLinkBottomSheetClose} onSubmit={onSubmitHyperLink} onRemove={onRemoveHyperLink} - isHaveHyperLink={!!hyperLink?.[0]?.data?.url} + isHaveHyperLink={hyperLink?.[0]?.data?.url !== ''} />
@@ -287,7 +280,7 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor pageId="create_story_page" componentId="*" onClick={() => - onCreateStory(file, imageMode, {}, hyperLink.length > 0 ? hyperLink : []) + onCreateStory(file, imageMode, {}, hyperLink[0]?.data?.url ? hyperLink : []) } avatar={community.avatarFileUrl} /> diff --git a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx index 13b80aabb..6e719365e 100644 --- a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx @@ -25,6 +25,7 @@ import { ViewStoryContent, ViewStoryOverlay, } from '../../internal-components/StoryViewer/styles'; + import Stories from 'react-insta-stories'; import { renderers } from '../../internal-components/StoryViewer/Renderers'; import { AmityDraftStoryPage } from '..'; @@ -84,19 +85,18 @@ export const CommunityFeedStory = ({ communityId }: CommunityFeedStoryProps) => const isModerator = checkStoryPermission(client, communityId); const confirmDeleteStory = (storyId: string) => { - const isLastStory = currentIndex === stories.length - 1; + const isLastStory = currentIndex === 0; confirm({ title: formatMessage({ id: 'storyViewer.action.confirmModal.title' }), content: formatMessage({ id: 'storyViewer.action.confirmModal.content' }), okText: formatMessage({ id: 'delete' }), onOk: async () => { + previousStory(); + if (isLastStory) onBack(); await StoryRepository.softDeleteStory(storyId); notification.success({ content: formatMessage({ id: 'storyViewer.notification.deleted' }), }); - if (isLastStory) { - onBack(); - } }, }); }; @@ -221,6 +221,8 @@ export const CommunityFeedStory = ({ communityId }: CommunityFeedStoryProps) => setCurrentIndex(currentIndex + 1); }; + console.log(currentIndex); + useEffect(() => { if (stories[stories.length - 1]?.syncState === 'syncing') { setCurrentIndex(stories.length - 1); diff --git a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx index f5109aa64..4404ef090 100644 --- a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx @@ -90,9 +90,7 @@ export const GlobalFeedStory: React.FC = () => { okText: formatMessage({ id: 'delete' }), onOk: async () => { previousStory(); - if (isLastStory) { - onChangePage(PageTypes.NewsFeed); - } + if (isLastStory) onChangePage(PageTypes.NewsFeed); await StoryRepository.softDeleteStory(storyId); notification.success({ content: formatMessage({ id: 'storyViewer.notification.deleted' }), From 896cffbf33f986e919993fe9b84baff90d835ff5 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Wed, 5 Jun 2024 17:58:24 +0700 Subject: [PATCH 092/300] fix: close bottom sheet condition (#365) --- .../social/internal-components/StoryViewer/Renderers/Image.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index 55612a5cd..62fad1c09 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -225,7 +225,6 @@ export const renderer: CustomRenderer = ({ story, action, config }) => { className={styles.actionButton} onClick={() => { bottomSheetAction.action(); - closeBottomSheet(); }} variant="secondary" > From d85467c672be0bef68c6adf62fa0c71aad5cf9a7 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Wed, 5 Jun 2024 18:06:08 +0700 Subject: [PATCH 093/300] fix: ASC-20502 - comment condition (#363) * fix: comment condition * fix: story comment compose bar condition --- .../useCommunityMembersCollection.tsx | 15 +++++ .../internal-components/Comment/index.tsx | 8 ++- .../StoryCommentComposeBar.tsx | 62 +++++++++---------- 3 files changed, 53 insertions(+), 32 deletions(-) create mode 100644 src/v4/social/hooks/collections/useCommunityMembersCollection.tsx diff --git a/src/v4/social/hooks/collections/useCommunityMembersCollection.tsx b/src/v4/social/hooks/collections/useCommunityMembersCollection.tsx new file mode 100644 index 000000000..be5668978 --- /dev/null +++ b/src/v4/social/hooks/collections/useCommunityMembersCollection.tsx @@ -0,0 +1,15 @@ +import { CommunityRepository } from '@amityco/ts-sdk'; +import useLiveCollection from '~/core/hooks/useLiveCollection'; + +export default function useCommunityMembersCollection(communityId?: string, limit: number = 5) { + const { items, ...rest } = useLiveCollection({ + fetcher: CommunityRepository.Membership.getMembers, + params: { communityId: communityId as string, limit, memberships: ['member'] }, + shouldCall: () => !!communityId, + }); + + return { + members: items, + ...rest, + }; +} diff --git a/src/v4/social/internal-components/Comment/index.tsx b/src/v4/social/internal-components/Comment/index.tsx index f94a3928c..81b04ee87 100644 --- a/src/v4/social/internal-components/Comment/index.tsx +++ b/src/v4/social/internal-components/Comment/index.tsx @@ -6,6 +6,7 @@ import useMention from '~/v4/chat/hooks/useMention'; import { extractMetadata, + isCommunityMember, isNonNullable, Mentioned, Metadata, @@ -34,6 +35,7 @@ import { Button, BottomSheet, Typography } from '~/v4/core/components'; import styles from './Comment.module.css'; import { TrashIcon, PenIcon, FlagIcon } from '~/v4/social/icons'; import { LoadingIndicator } from '~/v4/social/internal-components/LoadingIndicator'; +import useCommunityMembersCollection from '~/v4/social/hooks/collections/useCommunityMembersCollection'; import { useCommentFlaggedByMe } from '~/v4/social/hooks'; const REPLIES_PER_PAGE = 5; @@ -62,6 +64,8 @@ interface CommentProps { export const Comment = ({ commentId, readonly, onClickReply }: CommentProps) => { const comment = useComment(commentId); const story = useGetStoryByStoryId(comment?.referenceId); + const { members } = useCommunityMembersCollection(story?.community?.communityId); + const [bottomSheet, setBottomSheet] = useState(false); const [selectedCommentId, setSelectedCommentId] = useState(''); const { confirm } = useConfirmContext(); @@ -183,7 +187,9 @@ export const Comment = ({ commentId, readonly, onClickReply }: CommentProps) => }); }; - const isMember = story?.community?.isJoined; + const { currentUserId } = useSDK(); + const currentMember = members.find((member) => member.userId === currentUserId); + const isMember = isCommunityMember(currentMember); const options = [ canEdit diff --git a/src/v4/social/internal-components/StoryCommentComposeBar/StoryCommentComposeBar.tsx b/src/v4/social/internal-components/StoryCommentComposeBar/StoryCommentComposeBar.tsx index 3b3c5878d..455a4ac17 100644 --- a/src/v4/social/internal-components/StoryCommentComposeBar/StoryCommentComposeBar.tsx +++ b/src/v4/social/internal-components/StoryCommentComposeBar/StoryCommentComposeBar.tsx @@ -60,38 +60,11 @@ export const StoryCommentComposeBar = ({ }); }; - if (isJoined && shouldAllowCreation) { - return ( - <> - {isReplying && ( -
-
- {' '} - {replyTo?.userId} -
- -
- )} - {!isReplying ? ( - handleAddComment(text, mentionees, metadata)} - /> - ) : ( - { - handleReplyToComment(replyText, mentionees, metadata); - onCancelReply?.(); - }} - /> - )} - - ); + if (!isJoined) { + return null; } - if (isJoined && shouldAllowCreation) { + if (!shouldAllowCreation) { return (
@@ -100,5 +73,32 @@ export const StoryCommentComposeBar = ({ ); } - return null; + return ( + <> + {isReplying && ( +
+
+ {' '} + {replyTo?.userId} +
+ +
+ )} + {!isReplying ? ( + handleAddComment(text, mentionees, metadata)} + /> + ) : ( + { + handleReplyToComment(replyText, mentionees, metadata); + onCancelReply?.(); + }} + /> + )} + + ); }; From 27c1aabcf6f86b93231504e71d0a1c0347af35b6 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Wed, 5 Jun 2024 18:18:19 +0700 Subject: [PATCH 094/300] fix: ASC-22484 - hyperlink config css (#345) * fix: hyperlink config css * fix: remove unused * fix: remove unused * fix: hyperlink * fix: color bg * fix: remove console log * fix: type * fix: type * fix: hyperlink --- src/i18n/en.json | 2 +- .../AmityLiveChatMessageComposeBar/index.tsx | 3 +- .../components/MessageQuickReaction/index.tsx | 7 +- .../BottomSheet/BottomSheet.module.css | 29 +- .../components/BottomSheet/BottomSheet.tsx | 27 +- src/v4/core/components/ConfirmModal/index.tsx | 4 +- .../components/ConfirmModal/styles.module.css | 4 + .../core/components/Modal/styles.module.css | 3 +- src/v4/core/hooks/uikit/index.tsx | 47 ++ .../core/providers/CustomizationProvider.tsx | 450 ++++++++++-------- src/v4/core/providers/ThemeProvider.tsx | 233 +++++---- .../HyperLinkConfig.module.css | 52 +- .../HyperLinkConfig/HyperLinkConfig.tsx | 88 +++- .../ActionButton/ActionButton.module.css | 15 + .../elements/ActionButton/ActionButton.tsx | 27 +- .../HyperLinkButton/HyperLinkButton.tsx | 44 +- src/v4/social/pages/DraftsPage/DraftsPage.tsx | 1 - 17 files changed, 599 insertions(+), 437 deletions(-) create mode 100644 src/v4/core/hooks/uikit/index.tsx create mode 100644 src/v4/social/elements/ActionButton/ActionButton.module.css diff --git a/src/i18n/en.json b/src/i18n/en.json index bf5d0424c..21c0ba99e 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -359,12 +359,12 @@ "storyCreation.hyperlink.form.linkTextLabel": "Customize link text", "storyCreation.hyperlink.form.linkTextPlaceholder": "Name your link", "storyCreation.hyperlink.form.linkTextDescription": "This text will show on the link instead of URL.", - "storyCreation.hyperlink.validation.invalidUrl": "Please enter a valid URL.", "storyCreation.hyperlink.form.removeButton": "Remove link", "storyCreation.hyperlink.removeConfirm.title": "Remove link", "storyCreation.hyperlink.removeConfirm.content": "This link will be removed from story.", "storyCreation.hyperlink.removeConfirm.cancel": "Cancel", "storyCreation.hyperlink.removeConfirm.confirm": "Remove", + "storyCreation.hyperlink.validation.error.invalidUrl": "Please enter a valid URL.", "storyCreation.hyperlink.validation.error.whitelisted": "Please enter a whitelisted URL.", "storyCreation.hyperlink.validation.error.blocked": "Your text contains a blocklisted word.", diff --git a/src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx b/src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx index b5ae3ec51..18441efca 100644 --- a/src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx +++ b/src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx @@ -129,7 +129,8 @@ export const AmityLiveChatMessageComposeBar = ({ multiline disabled={disabled} placeholder={ - componentConfig?.placeholder_text || + (typeof componentConfig?.placeholder_text === 'string' && + componentConfig?.placeholder_text) || formatMessage({ id: 'livechat.composebar.placeholder', }) diff --git a/src/v4/chat/components/MessageQuickReaction/index.tsx b/src/v4/chat/components/MessageQuickReaction/index.tsx index 33a350d58..4d59a2045 100644 --- a/src/v4/chat/components/MessageQuickReaction/index.tsx +++ b/src/v4/chat/components/MessageQuickReaction/index.tsx @@ -1,6 +1,6 @@ import React, { useCallback } from 'react'; import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; -import { useCustomReaction } from '~/v4/core/providers/CustomReactionProvider'; +import { AmityReactionType, useCustomReaction } from '~/v4/core/providers/CustomReactionProvider'; import { QuickReactionIcon } from '~/v4/icons/QuickReactionIcon'; import { selectMessageReaction } from '~/v4/utils/selectMessageReaction'; import styles from './styles.module.css'; @@ -30,7 +30,10 @@ export const MessageQuickReaction = ({ elementConfig.reaction && reactionConfig.find((config) => config.name === elementConfig.reaction) ) { - selectMessageReaction({ reactionName: elementConfig.reaction, message }); + selectMessageReaction({ + reactionName: elementConfig.reaction as AmityReactionType['name'], + message, + }); } onSelectReaction && onSelectReaction(); diff --git a/src/v4/core/components/BottomSheet/BottomSheet.module.css b/src/v4/core/components/BottomSheet/BottomSheet.module.css index b258fba24..73ad9606f 100644 --- a/src/v4/core/components/BottomSheet/BottomSheet.module.css +++ b/src/v4/core/components/BottomSheet/BottomSheet.module.css @@ -4,29 +4,24 @@ Note that you might need to use !important for style overrides since the inner s which have higher specificity. */ -.react-modal-sheet-container { - color: var(--asc-color-base-default); -} - -.react-modal-sheet-backdrop { - background-color: var(--asc-color-base-inverse, rgba(0, 0, 0, 0.5)); +.bottomSheet__container { + background-color: var(--asc-color-base-background); } -.react-modal-sheet-header { +.bottomSheet__header { + padding: 1rem; display: flex; - padding-bottom: 1rem; justify-content: center; - align-items: center; - gap: 0.5rem; - align-self: stretch; - background-color: var(--asc-color-base-background); - color: var(--asc-color-base-default); - border-bottom: 1px solid var(--asc-color-base-shade4); } -.react-modal-sheet-content { - display: flex; +.bottomSheet__drag-indicator { +} + +.bottomSheet__content { background-color: var(--asc-color-base-background); padding: 1rem; - color: var(--asc-color-base-default); +} + +.bottomSheet__backdrop { + background-color: rgba(0, 0, 0, 0.5); } diff --git a/src/v4/core/components/BottomSheet/BottomSheet.tsx b/src/v4/core/components/BottomSheet/BottomSheet.tsx index d74a40450..5b5653ab9 100644 --- a/src/v4/core/components/BottomSheet/BottomSheet.tsx +++ b/src/v4/core/components/BottomSheet/BottomSheet.tsx @@ -14,32 +14,29 @@ interface BottomSheetProps { headerTitle?: string; cancelText?: string; okText?: string; + className?: string; } export const BottomSheet = ({ children, headerTitle, ...props }: BottomSheetProps) => { return ( - + - {headerTitle && ( - - {headerTitle} - - )} - {children} + > + + {headerTitle && ( + + {headerTitle} + + )} + + {children} - + ); }; diff --git a/src/v4/core/components/ConfirmModal/index.tsx b/src/v4/core/components/ConfirmModal/index.tsx index 7bb1f5804..346ae463f 100644 --- a/src/v4/core/components/ConfirmModal/index.tsx +++ b/src/v4/core/components/ConfirmModal/index.tsx @@ -42,7 +42,7 @@ const Confirm = ({ } onCancel={onCancel} > -
{content}
+
{content}
); @@ -61,7 +61,7 @@ export const ConfirmComponent = () => { confirmData?.onOk && confirmData.onOk(); }; - return ; + return ; }; export default Confirm; diff --git a/src/v4/core/components/ConfirmModal/styles.module.css b/src/v4/core/components/ConfirmModal/styles.module.css index 75d341c64..da6c634b9 100644 --- a/src/v4/core/components/ConfirmModal/styles.module.css +++ b/src/v4/core/components/ConfirmModal/styles.module.css @@ -2,6 +2,10 @@ max-width: 22.5rem !important; } +.background { + background-color: var(--asc-color-base-background); +} + .footer { display: flex; justify-content: flex-end; diff --git a/src/v4/core/components/Modal/styles.module.css b/src/v4/core/components/Modal/styles.module.css index 90ffcdc37..53cafcec1 100644 --- a/src/v4/core/components/Modal/styles.module.css +++ b/src/v4/core/components/Modal/styles.module.css @@ -39,7 +39,6 @@ .modalWindow { margin: auto; - background-color: var(--asc-color-base-background); border-radius: var(--asc-border-radius-lg); max-width: 32.5rem; min-width: 20rem; @@ -71,4 +70,4 @@ .footer { padding: var(--asc-spacing-m2) var(--asc-spacing-s2); padding-top: var(--asc-spacing-xxs2); -} \ No newline at end of file +} diff --git a/src/v4/core/hooks/uikit/index.tsx b/src/v4/core/hooks/uikit/index.tsx new file mode 100644 index 000000000..af69bde3a --- /dev/null +++ b/src/v4/core/hooks/uikit/index.tsx @@ -0,0 +1,47 @@ +import { getDefaultConfig, useCustomization } from '~/v4/core/providers/CustomizationProvider'; +import { useGenerateStylesShadeColors } from '~/v4/core/providers/ThemeProvider'; + +export const useAmityElement = ({ + pageId, + componentId, + elementId, +}: { + pageId: string; + componentId: string; + elementId: string; +}) => { + const uiReference = `${pageId}/${componentId}/${elementId}`; + const { getConfig, isExcluded } = useCustomization(); + const config = getConfig(uiReference); + const defaultConfig = getDefaultConfig(uiReference); + const themeStyles = useGenerateStylesShadeColors(config); + const isComponentExcluded = isExcluded(uiReference); + const accessibilityId = uiReference; + + return { + config, + defaultConfig, + uiReference, + accessibilityId, + themeStyles, + isExcluded: isComponentExcluded, + }; +}; + +export const useAmityComponent = ({ + pageId, + componentId, +}: { + pageId: string; + componentId: string; +}) => { + const elementId = '*'; + return useAmityElement({ pageId, componentId, elementId }); +}; + +export const useAmityPage = ({ pageId }: { pageId: string }) => { + const componentId = '*'; + const elementId = '*'; + + return useAmityElement({ pageId, componentId, elementId }); +}; diff --git a/src/v4/core/providers/CustomizationProvider.tsx b/src/v4/core/providers/CustomizationProvider.tsx index fde68dca0..82bc01bcb 100644 --- a/src/v4/core/providers/CustomizationProvider.tsx +++ b/src/v4/core/providers/CustomizationProvider.tsx @@ -1,11 +1,18 @@ import React, { createContext, useContext, useState, useEffect } from 'react'; import { AmityReactionType } from './CustomReactionProvider'; +export type GetConfigReturnValue = IconConfiguration & + TextConfiguration & + ThemeConfiguration & + CustomConfiguration; + interface CustomizationContextValue { config: Config | null; parseConfig: (config: Config) => void; isExcluded: (path: string) => boolean; - getConfig: (path: string) => Record; + getConfig: ( + path: string, + ) => IconConfiguration & TextConfiguration & ThemeConfiguration & CustomConfiguration; } export type Theme = { @@ -35,6 +42,14 @@ export type Theme = { }; }; +type ThemeConfiguration = { + preferred_theme?: 'light' | 'dark' | 'default'; + theme?: { + light?: Partial>; + dark?: Partial>; + }; +}; + export interface Config { preferred_theme?: 'light' | 'dark' | 'default'; theme?: { @@ -44,202 +59,22 @@ export interface Config { excludes?: string[]; message_reactions?: AmityReactionType[]; customizations?: { - 'select_target_page/*/*'?: { - theme?: { - light?: { - primary_color?: string; - secondary_color?: string; - }; - }; - title?: string; - }; - 'select_target_page/*/back_button'?: { - back_icon?: string; - }; - 'camera_page/*/*'?: { - resolution?: string; - theme?: { - light?: { - primary_color?: string; - secondary_color?: string; - }; - }; - }; - 'camera_page/*/close_button'?: { - close_icon?: string; - background_color?: string; - }; - 'create_story_page/*/*'?: { - theme?: { - light?: { - primary_color?: string; - secondary_color?: string; - }; - }; - }; - 'create_story_page/*/back_button'?: { - back_icon?: string; - background_color?: string; - }; - 'create_story_page/*/aspect_ratio_button'?: { - aspect_ratio_icon?: string; - background_color?: string; - }; - 'create_story_page/*/story_hyperlink_button'?: { - hyperlink_button_icon?: string; - background_color?: string; - }; - 'create_story_page/*/hyper_link'?: { - hyper_link_icon?: string; - background_color?: string; - }; - 'create_story_page/*/share_story_button'?: { - share_icon?: string; - background_color?: string; - hide_avatar?: boolean; - }; - 'story_page/*/*'?: { - theme?: { - light?: { - primary_color?: string; - secondary_color?: string; - }; - }; - }; - 'story_page/*/progress_bar'?: { - progress_color?: string; - background_color?: string; - }; - 'story_page/*/overflow_menu'?: { - overflow_menu_icon?: string; - }; - 'story_page/*/close_button'?: { - close_icon?: string; - }; - 'story_page/*/story_impression_button'?: { - impression_icon?: string; - }; - 'story_page/*/story_comment_button'?: { - comment_icon?: string; - background_color?: string; - }; - 'story_page/*/story_reaction_button'?: { - reaction_icon?: string; - background_color?: string; - }; - 'story_page/*/create_new_story_button'?: { - create_new_story_icon?: string; - background_color?: string; - }; - 'story_page/*/speaker_button'?: { - mute_icon?: string; - unmute_icon?: string; - background_color?: string; - }; - '*/edit_comment_component/*'?: { - theme?: { - light_theme?: { - primary_color?: string; - secondary_color?: string; - }; - }; - }; - '*/edit_comment_component/cancel_button'?: { - cancel_icon?: string; - cancel_button_text?: string; - background_color?: string; - }; - '*/edit_comment_component/save_button'?: { - save_icon?: string; - save_button_text?: string; - background_color?: string; - }; - '*/hyper_link_config_component/*'?: { - theme?: { - light?: { - primary_color?: string; - secondary_color?: string; - }; - }; - }; - '*/hyper_link_config_component/done_button'?: { - done_icon?: string; - done_button_text?: string; - background_color?: string; - }; - '*/hyper_link_config_component/cancel_button'?: { - cancel_icon?: string; - cancel_button_text?: string; - }; - '*/comment_tray_component/*'?: { - theme?: { - light?: { - primary_color?: string; - secondary_color?: string; - }; - }; - }; - '*/story_tab_component/*'?: { - theme?: { - light?: { - primary_color?: string; - secondary_color?: string; - }; - }; - }; - '*/story_tab_component/story_ring'?: { - progress_color?: string[]; - background_color?: string; - }; - '*/story_tab_component/create_new_story_button'?: { - create_new_story_icon?: string; - background_color?: string; - }; - '*/*/close_button'?: { - close_icon?: string; - }; - 'live_chat/*/*'?: { - theme?: { - light?: { - primary_color?: string; - secondary_color?: string; - }; - dark?: { - primary_color?: string; - secondary_color?: string; - }; - }; - }; - 'live_chat/chat_header/*'?: { - theme?: { - light?: { - primary_color?: string; - secondary_color?: string; - }; - dark?: { - primary_color?: string; - secondary_color?: string; - }; - }; - }; - 'live_chat/message_list/*'?: { - theme?: { - light?: { - primary_color?: string; - secondary_color?: string; - }; - dark?: { - primary_color?: string; - secondary_color?: string; - }; - }; - }; - 'live_chat/message_composer/*'?: { - placeholder_text?: 'Write a message'; - }; + [key: string]: IconConfiguration & TextConfiguration & ThemeConfiguration & CustomConfiguration; }; } +type DefaultConfig = { + preferred_theme: 'light' | 'dark' | 'default'; + theme: { + light: Theme['light']; + dark: Theme['dark']; + }; + excludes: string[]; + customizations?: { + [key: string]: IconConfiguration & TextConfiguration & ThemeConfiguration & CustomConfiguration; + }; +}; + const CustomizationContext = createContext({ config: null, parseConfig: () => {}, @@ -260,7 +95,17 @@ interface CustomizationProviderProps { initialConfig: Config; } -export const defaultConfig: Config = { +type IconConfiguration = { + icon?: string; +}; +type TextConfiguration = { + text?: string; +}; +type CustomConfiguration = { + [key: string]: string | undefined | boolean | Array | number | Record; +}; + +export const defaultConfig: DefaultConfig = { preferred_theme: 'default', theme: { light: { @@ -396,9 +241,171 @@ export const defaultConfig: Config = { '*/*/close_button': { close_icon: 'close.png', }, + 'social_home_page/top_navigation/header_label': { + text: 'Community', + }, + 'social_home_page/top_navigation/global_search_button': { + icon: 'searchButtonIcon', + }, + 'social_home_page/top_navigation/post_creation_button': { + icon: 'postCreationIcon', + }, + 'social_home_page/*/newsfeed_button': { + text: 'Newsfeed', + }, + 'social_home_page/*/explore_button': { + text: 'Explore', + }, + 'social_home_page/*/my_communities_button': { + text: 'My Communities', + }, + 'social_home_page/empty_newsfeed/illustration': { + icon: 'emptyFeedIcon', + }, + 'social_home_page/empty_newsfeed/title': { + text: 'Your Feed is empty', + }, + 'social_home_page/empty_newsfeed/description': { + text: 'Find community or create your own', + }, + 'social_home_page/empty_newsfeed/explore_communities_button': { + icon: 'exploreCommunityIcon', + text: 'Explore Community', + }, + 'social_home_page/empty_newsfeed/create_community_button': { + icon: 'createCommunityIcon', + }, + 'social_home_page/my_communities/community_avatar': {}, + 'social_home_page/my_communities/community_display_name': {}, + 'social_home_page/my_communities/community_private_badge': { + icon: 'lockIcon', + }, + 'social_home_page/my_communities/community_official_badge': { + icon: 'officalBadgeIcon', + }, + 'social_home_page/my_communities/community_category_name': {}, + 'social_home_page/my_communities/community_members_count': {}, + 'social_home_page/newsfeed_component/*': {}, + 'social_home_page/global_feed_component/*': {}, + 'global_search_page/*/*': {}, + 'post_detail_page/*/back_button': { + icon: 'backButtonIcon', + }, + 'post_detail_page/*/menu_button': { + icon: 'menuIcon', + }, + '*/*/moderator_badge': { + icon: 'badgeIcon', + text: 'Moderator', + }, + '*/post_content/moderator_badge': { + icon: 'badgeIcon', + text: 'Moderator', + theme: { + light: { + primary_color: '#FA4D30', + secondary_color: '#292B32', + }, + dark: { + primary_color: '#00FF00', + secondary_color: '#292B32', + }, + }, + }, + '*/post_comment/*': { + preferred_theme: 'default', + theme: { + light: { + primary_color: '#FFC0CB', + secondary_color: '#292B32', + }, + dark: { + primary_color: '#FFFF00', + secondary_color: '#292B32', + }, + }, + }, + '*/post_content/timestamp': {}, + '*/post_content/menu_button': { + icon: 'menuIcon', + }, + '*/post_content/post_content_view_count': {}, + '*/post_content/reaction_button': { + icon: 'likeButtonIcon', + text: 'Like', + }, + '*/post_content/comment_button': { + icon: 'commentButtonIcon', + text: 'Comment', + }, + '*/post_content/share_button': { + icon: 'shareButtonIcon', + text: 'Share', + }, + 'social_global_search_page/*/*': {}, + 'social_global_search_page/top_search_bar/*': {}, + 'social_global_search_page/top_search_bar/search_icon': { + icon: 'search', + }, + 'social_global_search_page/top_search_bar/clear_button': { + icon: 'clear', + }, + 'social_global_search_page/top_search_bar/cancel_button': { + text: 'Cancel', + }, + 'social_global_search_page/community_search_result/community_avatar': {}, + 'social_global_search_page/community_search_result/community_display_name': {}, + 'social_global_search_page/community_search_result/community_private_badge': { + icon: 'lockIcon', + }, + 'social_global_search_page/community_search_result/community_official_badge': { + icon: 'officialBadgeIcon', + }, + 'social_global_search_page/community_search_result/community_category_name': {}, + 'social_global_search_page/community_search_result/community_members_count': {}, }, }; +export const getDefaultConfig: CustomizationContextValue['getConfig'] = (path: string) => { + const [page, component, element] = path.split('/'); + + const customizationKeys = (() => { + if (element !== '*') { + return [ + `${page}/${component}/${element}`, + `${page}/*/${element}`, + `${page}/${component}/*`, + `${page}/*/*`, + `*/${component}/${element}`, + `*/*/${element}`, + `*/${component}/*`, + `*/*/*`, + ]; + } else if (component !== '*') { + return [`${page}/${component}/*`, `${page}/*/*`, `*/${component}/*`, `*/*/*`]; + } else if (page !== '*') { + return [`${page}/*/*`, `*/*/*`]; + } + + return []; + })(); + + return new Proxy< + IconConfiguration & TextConfiguration & { theme?: Partial } & CustomConfiguration + >( + {}, + { + get(target, prop: string) { + for (const key of customizationKeys) { + if (defaultConfig?.customizations?.[key]?.[prop]) { + return defaultConfig.customizations[key][prop]; + } + } + }, + }, + ); +}; + export const CustomizationProvider: React.FC = ({ children, initialConfig, @@ -439,9 +446,56 @@ export const CustomizationProvider: React.FC = ({ }); }; - const getConfig = (path: string) => { - if (!config?.customizations) return {}; - return config?.customizations[path as keyof Config['customizations']] || {}; + const getConfig: CustomizationContextValue['getConfig'] = (path: string) => { + const [page, component, element] = path.split('/'); + + const customizationKeys = (() => { + if (element !== '*') { + return [ + `${page}/${component}/${element}`, + `${page}/*/${element}`, + `${page}/${component}/*`, + `${page}/*/*`, + `*/${component}/${element}`, + `*/*/${element}`, + `*/${component}/*`, + `*/*/*`, + ]; + } else if (component !== '*') { + return [`${page}/${component}/*`, `${page}/*/*`, `*/${component}/*`, `*/*/*`]; + } else if (page !== '*') { + return [`${page}/*/*`, `*/*/*`]; + } + + return []; + })(); + + return new Proxy< + IconConfiguration & TextConfiguration & { theme?: Partial } & CustomConfiguration + >( + {}, + { + get(target, prop: string) { + for (const key of customizationKeys) { + if (config?.customizations?.[key]?.[prop]) { + return config.customizations[key][prop]; + } + } + for (const key of customizationKeys) { + if (defaultConfig?.customizations?.[key]?.[prop]) { + return defaultConfig.customizations[key][prop]; + } + } + + if (prop === 'theme') { + return defaultConfig.theme; + } + if (prop === 'preferred_theme') { + return defaultConfig.preferred_theme; + } + }, + }, + ); }; const contextValue: CustomizationContextValue = { diff --git a/src/v4/core/providers/ThemeProvider.tsx b/src/v4/core/providers/ThemeProvider.tsx index 928d15037..646b3966c 100644 --- a/src/v4/core/providers/ThemeProvider.tsx +++ b/src/v4/core/providers/ThemeProvider.tsx @@ -1,15 +1,12 @@ -import React, { createContext, useEffect, useState } from 'react'; +import React, { createContext, useContext, useEffect, useMemo, useState } from 'react'; import { lighten, parseToHsl, darken, hslToColorString } from 'polished'; -import { useCustomization, Config, Theme } from './CustomizationProvider'; +import { defaultConfig, GetConfigReturnValue } from './CustomizationProvider'; const SHADE_PERCENTAGES = [0.25, 0.4, 0.5, 0.75]; -const setCSSVariable = (variable: string, value?: string) => { - if (!value) return; - document.documentElement.style.setProperty(variable, value); -}; +const generateShades = (hexColor?: string, isDarkMode = false): string[] => { + if (!hexColor) return Array(SHADE_PERCENTAGES.length).fill(''); -const generateShades = (hexColor: string, isDarkMode = false): string[] => { const hslColor = parseToHsl(hexColor); const shades = SHADE_PERCENTAGES.map((percentage) => { @@ -23,93 +20,104 @@ const generateShades = (hexColor: string, isDarkMode = false): string[] => { return shades; }; -const generatePaletteByConfig = ({ - themeConfig, - configId, - isDarkMode, -}: { - themeConfig: Theme['light'] | Theme['dark']; - configId?: string; - isDarkMode?: boolean; -}) => { - const primaryColorShades = generateShades(themeConfig.primary_color, isDarkMode); - const secondaryColorShades = generateShades(themeConfig.secondary_color, isDarkMode); - - const prefix = configId ? configId + '-' : ''; - - setCSSVariable(`--${prefix}asc-color-primary-default`, themeConfig.primary_color); - setCSSVariable(`--${prefix}asc-color-primary-shade1`, primaryColorShades[0]); - setCSSVariable(`--${prefix}asc-color-primary-shade2`, primaryColorShades[1]); - setCSSVariable(`--${prefix}asc-color-primary-shade3`, primaryColorShades[2]); - setCSSVariable(`--${prefix}asc-color-primary-shade4`, primaryColorShades[3]); - - setCSSVariable(`--${prefix}asc-color-secondary-default`, themeConfig.secondary_color); - setCSSVariable(`--${prefix}asc-color-secondary-shade1`, secondaryColorShades[0]); - setCSSVariable(`--${prefix}asc-color-secondary-shade2`, secondaryColorShades[1]); - setCSSVariable(`--${prefix}asc-color-secondary-shade3`, secondaryColorShades[2]); - setCSSVariable(`--${prefix}asc-color-secondary-shade4`, secondaryColorShades[3]); - - setCSSVariable(`--${prefix}asc-color-base-default`, themeConfig.base_color); - setCSSVariable(`--${prefix}asc-color-base-shade1`, themeConfig.base_shade1_color); - setCSSVariable(`--${prefix}asc-color-base-shade2`, themeConfig.base_shade2_color); - setCSSVariable(`--${prefix}asc-color-base-shade3`, themeConfig.base_shade3_color); - setCSSVariable(`--${prefix}asc-color-base-shade4`, themeConfig.base_shade4_color); - - setCSSVariable(`--${prefix}asc-color-alert`, themeConfig.alert_color); - setCSSVariable(`--${prefix}asc-color-base-background`, themeConfig.background_color); - setCSSVariable(`--${prefix}asc-color-base-inverse`, themeConfig.base_inverse_color); -}; +export function useGenerateStylesShadeColors(inputConfig?: GetConfigReturnValue) { + const currentTheme = useTheme(); + + const inputThemeConfig = inputConfig?.theme; -const generateComponentPalette = (config: Config, currentTheme: 'light' | 'dark') => { - const configurables = [ - { - pageId: 'live_chat', - componentIds: ['chat_header', 'message_list', 'message_composer'], - }, - ]; - - configurables.forEach((configurable) => { - const pageConfig = (config.customizations as { [key: string]: { theme: Theme } })?.[ - `${configurable.pageId}/*/*` - ]?.theme; - - if (pageConfig) { - const themeToGenerate = currentTheme === 'light' ? pageConfig.light : pageConfig.dark; - const configId = configurable.pageId.replace('_', '-'); - - if (themeToGenerate) { - generatePaletteByConfig({ - themeConfig: themeToGenerate, - configId, - isDarkMode: currentTheme === 'dark', - }); - } + const preferredTheme = useMemo(() => { + if (inputConfig?.preferred_theme && inputConfig?.preferred_theme !== 'default') { + return inputConfig.preferred_theme; } - if (configurable.componentIds.length === 0) return; - - configurable.componentIds.forEach((componentId) => { - const componentConfig = (config.customizations as { [key: string]: { theme: Theme } })?.[ - `${configurable.pageId}/${componentId}/*` - ]?.theme; - if (componentConfig) { - const themeToGenerate = - currentTheme === 'light' ? componentConfig.light : componentConfig.dark; - - const configId = - configurable.pageId.replace('_', '-') + '-' + componentId.replace('_', '-'); - - if (themeToGenerate) { - generatePaletteByConfig({ - themeConfig: themeToGenerate, - configId, - isDarkMode: currentTheme === 'dark', - }); - } - } - }); - }); -}; + return 'default'; + }, [inputConfig?.preferred_theme, currentTheme]); + + const generatedLightColors = (() => { + if (inputThemeConfig?.light) { + const lightThemeConfig = inputThemeConfig?.light || defaultConfig.theme.light; + + const lightPrimary = generateShades(lightThemeConfig.primary_color); + const lightSecondary = generateShades(lightThemeConfig.secondary_color); + return { + '--asc-color-primary-default': lightThemeConfig.primary_color, + '--asc-color-primary-shade1': lightPrimary[0], + '--asc-color-primary-shade2': lightPrimary[1], + '--asc-color-primary-shade3': lightPrimary[2], + '--asc-color-primary-shade4': lightPrimary[3], + + '--asc-color-secondary-default': lightThemeConfig.secondary_color, + '--asc-color-secondary-shade1': lightSecondary[0], + '--asc-color-secondary-shade2': lightSecondary[1], + '--asc-color-secondary-shade3': lightSecondary[2], + '--asc-color-secondary-shade4': lightSecondary[3], + '--asc-color-secondary-shade5': '#f9f9fa', + + '--asc-color-alert': '#fa4d30', + '--asc-color-black': '#000000', + '--asc-color-white': '#ffffff', + + '--asc-color-base-inverse': '#000000', + + '--asc-color-base-default': '#292b32', + '--asc-color-base-shade1': '#636878', + '--asc-color-base-shade2': '#898e9e', + '--asc-color-base-shade3': '#a5a9b5', + '--asc-color-base-shade4': '#ebecef', + '--asc-color-base-shade5': '#f9f9fa', + + '--asc-color-base-background': defaultConfig.theme.light.background_color, + }; + } + + return {}; + })(); + + const generatedDarkColors = (() => { + if (inputThemeConfig?.dark) { + const darkThemeConfig = inputThemeConfig?.dark || defaultConfig.theme.dark; + const darkPrimary = generateShades(darkThemeConfig.primary_color, true); + const darkSecondary = generateShades(darkThemeConfig.secondary_color, true); + + return { + '--asc-color-primary-default': darkThemeConfig.primary_color, + '--asc-color-primary-shade1': darkPrimary[0], + '--asc-color-primary-shade2': darkPrimary[1], + '--asc-color-primary-shade3': darkPrimary[2], + '--asc-color-primary-shade4': darkPrimary[3], + + '--asc-color-secondary-default': darkThemeConfig.secondary_color, + '--asc-color-secondary-shade1': darkSecondary[0], + '--asc-color-secondary-shade2': darkSecondary[1], + '--asc-color-secondary-shade3': darkSecondary[2], + '--asc-color-secondary-shade4': darkSecondary[3], + '--asc-color-secondary-shade5': '#f9f9fa', + + '--asc-color-alert': '#fa4d30', + '--asc-color-black': '#000000', + '--asc-color-white': '#ffffff', + + '--asc-color-base-inverse': '#ffffff', + + '--asc-color-base-default': '#ebecef', + '--asc-color-base-shade1': '#a5a9b5', + '--asc-color-base-shade2': '#6e7487', + '--asc-color-base-shade3': '#40434e', + '--asc-color-base-shade4': '#292b32', + '--asc-color-base-shade5': '#f9f9fa', + + '--asc-color-base-background': defaultConfig.theme.dark.background_color, + }; + } + return {}; + })(); + + const computedTheme = preferredTheme === 'default' ? currentTheme : preferredTheme; + + return { + ...(computedTheme === 'light' ? generatedLightColors : generatedDarkColors), + } as React.CSSProperties; +} export const ThemeContext = createContext<{ currentTheme: 'light' | 'dark'; @@ -120,43 +128,20 @@ export const ThemeContext = createContext<{ }); export const ThemeProvider: React.FC = ({ children }) => { - const { config } = useCustomization(); const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); const [currentTheme, setCurrentTheme] = useState<'light' | 'dark'>( - config?.preferred_theme && config?.preferred_theme !== 'default' - ? config.preferred_theme - : mediaQuery.matches - ? 'dark' - : 'light', + mediaQuery.matches ? 'dark' : 'light', ); useEffect(() => { - if (!config) return; - const themeToGenerate = currentTheme === 'light' ? config.theme?.light : config.theme?.dark; - - if (themeToGenerate) { - generatePaletteByConfig({ - themeConfig: themeToGenerate, - isDarkMode: currentTheme === 'dark', - }); - } - - generateComponentPalette(config, currentTheme); - }, [currentTheme, config]); - - useEffect(() => { - if (!config) return; + const handleChange = (e: MediaQueryListEvent) => { + setCurrentTheme(e.matches ? 'dark' : 'light'); + }; - if (config.preferred_theme === 'default') { - const handleChange = (e: MediaQueryListEvent) => { - setCurrentTheme(e.matches ? 'dark' : 'light'); - }; - - mediaQuery.addEventListener('change', handleChange); - return () => mediaQuery.removeEventListener('change', handleChange); - } - }, [config?.preferred_theme]); + mediaQuery.addEventListener('change', handleChange); + return () => mediaQuery.removeEventListener('change', handleChange); + }, []); const toggleTheme = () => { setCurrentTheme((prevTheme) => (prevTheme === 'light' ? 'dark' : 'light')); @@ -166,3 +151,9 @@ export const ThemeProvider: React.FC = ({ children }) => { {children} ); }; + +export const useTheme = () => { + const { currentTheme } = useContext(ThemeContext); + + return currentTheme; +}; diff --git a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css index 5e5e68d50..de86cacdf 100644 --- a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css +++ b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css @@ -1,5 +1,5 @@ .hyperlinkFormContainer { - border-radius: var(--asc-border-radius-md); + background-color: transparent; } .form { @@ -8,6 +8,44 @@ gap: var(--asc-spacing-l1); } +.bottomSheet { +} + +.bottomSheet .react-modal-sheet-backdrop { + background-color: rgba(0, 0, 0, 0.5); +} + +.bottomSheet .react-modal-sheet-container { + background-color: white; + border-top-left-radius: 16px; + border-top-right-radius: 16px; + max-height: 80%; + overflow: auto; +} + +.bottomSheet .react-modal-sheet-content { + background-color: var(--asc-color-base-background); +} + +.bottomSheet .react-modal-sheet-header { + padding: 16px; + display: flex; + justify-content: space-between; + align-items: center; +} + +.bottomSheet .react-modal-sheet-drag-indicator { + width: 40px; + height: 4px; + background-color: #ccc; + border-radius: 2px; + margin-bottom: 8px; +} + +.bottomSheet .react-modal-sheet-content { + padding: 16px; +} + .inputContainer { display: flex; flex-direction: column; @@ -73,10 +111,18 @@ color: var(--asc-color-base-default); } -.styledSecondaryButton { +.cancelButton { border: none; color: var(--asc-color-base-default); - background: var(--asc-color-base-background); +} + +.doneButton { + border: none; +} + +.doneButton:disabled { + cursor: not-allowed; + color: var(--asc-color-primary-shade2); } .removeIcon { diff --git a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx index 9a480ca44..0f25a55fd 100644 --- a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx +++ b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx @@ -12,10 +12,9 @@ import { Trash2Icon } from '~/icons'; import styles from './HyperLinkConfig.module.css'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; import { Button } from '~/v4/core/components/Button'; -import { useNotifications } from '~/v4/core/providers/NotificationProvider'; interface HyperLinkConfigProps { - pageId: '*'; + pageId: string; isHaveHyperLink: boolean; isOpen: boolean; onClose: () => void; @@ -36,9 +35,10 @@ export const HyperLinkConfig = ({ const { confirm } = useConfirmContext(); const componentId = 'hyper_link_config_component'; - const { getConfig } = useCustomization(); + const { getConfig, isExcluded } = useCustomization(); + const componentConfig = getConfig(`${pageId}/${componentId}/*`); - const componentTheme = componentConfig?.theme.light || {}; + const componentTheme = componentConfig?.theme?.light || {}; const cancelButtonConfig = getConfig(`*/hyper_link_config_component/cancel_button`); const doneButtonConfig = getConfig(`*/hyper_link_config_component/done_button`); @@ -47,11 +47,43 @@ export const HyperLinkConfig = ({ const { client } = useSDK(); const schema = z.object({ - url: z.string().refine(async (value) => { - if (!value) return true; - const hasWhitelistedUrls = await client?.validateUrls([value]).catch(() => false); - return hasWhitelistedUrls; - }, formatMessage({ id: 'storyCreation.hyperlink.validation.error.whitelisted' })), + url: z + .string() + .refine( + (value) => { + if (!value) return true; + try { + const urlObj = new URL(value); + return ['http:', 'https:'].includes(urlObj.protocol); + } catch (error) { + // Check if the value starts with "www." + if (value.startsWith('www.')) { + try { + const urlObj = new URL(`https://${value}`); + return ['http:', 'https:'].includes(urlObj.protocol); + } catch (error) { + return false; + } + } + return false; + } + }, + { + message: formatMessage({ id: 'storyCreation.hyperlink.validation.error.invalidUrl' }), + }, + ) + .refine( + async (value) => { + if (!value) return true; + // Prepend "https://" to the value if it starts with "www." + const urlToValidate = value.startsWith('www.') ? `https://${value}` : value; + const hasWhitelistedUrls = await client?.validateUrls([urlToValidate]).catch(() => false); + return hasWhitelistedUrls; + }, + { + message: formatMessage({ id: 'storyCreation.hyperlink.validation.error.whitelisted' }), + }, + ), customText: z .string() .optional() @@ -65,6 +97,7 @@ export const HyperLinkConfig = ({ type HyperLinkFormInputs = z.infer; const { + trigger, watch, register, handleSubmit, @@ -100,31 +133,34 @@ export const HyperLinkConfig = ({ rootId="asc-uikit-create-story" isOpen={isOpen} onClose={onClose} + className={styles.bottomSheet} >
- {formatMessage({ id: 'storyCreation.hyperlink.bottomSheet.title' })} @@ -150,7 +186,9 @@ export const HyperLinkConfig = ({ id="asc-uikit-hyperlink-input-url" placeholder={formatMessage({ id: 'storyCreation.hyperlink.form.urlPlaceholder' })} className={clsx(styles.input, errors?.url && styles.hasError)} - {...register('url')} + {...register('url', { + onChange: () => trigger('url'), + })} /> {errors?.url && {errors?.url?.message}}
diff --git a/src/v4/social/elements/ActionButton/ActionButton.module.css b/src/v4/social/elements/ActionButton/ActionButton.module.css new file mode 100644 index 000000000..5cca3c61b --- /dev/null +++ b/src/v4/social/elements/ActionButton/ActionButton.module.css @@ -0,0 +1,15 @@ +.actionButton { + width: 2rem; + height: 2rem; + display: flex; + align-items: center; + justify-content: center; + border: none; + background-color: rgba(0, 0, 0, 0.5); + color: var(--base-inverse-default); + cursor: pointer; + padding: 0.1875rem 0rem; + border-radius: 50%; + transition: background-color 0.3s; + flex-shrink: 0; +} diff --git a/src/v4/social/elements/ActionButton/ActionButton.tsx b/src/v4/social/elements/ActionButton/ActionButton.tsx index 12b2446ff..f32980862 100644 --- a/src/v4/social/elements/ActionButton/ActionButton.tsx +++ b/src/v4/social/elements/ActionButton/ActionButton.tsx @@ -1,26 +1,15 @@ import React from 'react'; -import styled from 'styled-components'; +import clsx from 'clsx'; +import styles from './ActionButton.module.css'; interface ActionButtonProps extends React.ButtonHTMLAttributes { icon: React.ReactNode; } -const StyledActionButton = styled.button` - width: 2rem; - height: 2rem; - display: flex; - align-items: center; - justify-content: center; - border: none; - background-color: ${({ theme }) => theme.v4.colors.actionButton.default}; - color: ${({ theme }) => theme.v4.colors.baseInverse.default}; - cursor: pointer; - padding: 0.1875rem 0rem; - border-radius: 50%; - transition: background-color 0.3s; - flex-shrink: 0; -`; - -export const ActionButton: React.FC = ({ icon, ...rest }) => { - return {icon}; +export const ActionButton: React.FC = ({ icon, className, ...rest }) => { + return ( + + ); }; diff --git a/src/v4/social/elements/HyperLinkButton/HyperLinkButton.tsx b/src/v4/social/elements/HyperLinkButton/HyperLinkButton.tsx index 51d784007..c7b005af1 100644 --- a/src/v4/social/elements/HyperLinkButton/HyperLinkButton.tsx +++ b/src/v4/social/elements/HyperLinkButton/HyperLinkButton.tsx @@ -1,50 +1,34 @@ import React from 'react'; -import { useTheme } from 'styled-components'; import { ActionButton } from '../ActionButton'; import { LinkIcon } from '~/icons'; -import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; +import { useAmityElement } from '~/v4/core/hooks/uikit/index'; -interface BackButtonProps { - pageId: 'create_story_page'; - componentId: '*'; +interface HyperLinkButtonProps { + pageId?: string; + componentId?: string; onClick: (e: React.MouseEvent) => void; } export const HyperLinkButton = ({ - pageId = 'create_story_page', + pageId = '*', componentId = '*', onClick = () => {}, -}: BackButtonProps) => { - const theme = useTheme(); +}: HyperLinkButtonProps) => { const elementId = 'story_hyperlink_button'; - const { getConfig, isExcluded } = useCustomization(); - const elementConfig = getConfig(`${pageId}/${componentId}/${elementId}`); - const backgroundColor = elementConfig?.background_color; - const customIcon = elementConfig?.hyperlink_button_icon; - const isElementExcluded = isExcluded(`${pageId}/${componentId}/${elementId}`); + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityElement({ + pageId, + componentId, + elementId, + }); - if (isElementExcluded) return null; - - const renderIcon = () => { - if (customIcon) { - if (customIcon.startsWith('http://') || customIcon.startsWith('https://')) { - return ( - {elementId} - ); - } - } - - return ; - }; + if (isExcluded) return null; return ( } onClick={onClick} - style={{ - backgroundColor: backgroundColor || theme.v4.colors.actionButton.default, - }} /> ); }; diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index 21353d317..593945cd9 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -5,7 +5,6 @@ import { readFileAsync } from '~/helpers'; import styles from './DraftsPage.module.css'; import { SubmitHandler } from 'react-hook-form'; -import Truncate from 'react-truncate-markup'; import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; import { From 52f83a3b68678ae390ebe0d2ca0ec550ecb424e9 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Wed, 5 Jun 2024 18:21:43 +0700 Subject: [PATCH 095/300] fix: ASC-20532 - deleted comment block (#364) * fix: deleted comment block * fix: css * fix: remove unused props * fix: css var --- .../components/CommentTray/CommentTray.tsx | 1 + .../collections/useCommentsCollection.ts | 38 +++++++++++++++++++ src/v4/social/icons/index.ts | 1 + src/v4/social/icons/minus_circle.tsx | 21 ++++++++++ .../Comment/Comment.module.css | 18 +++++++++ .../Comment/UIComment.module.css | 8 ++++ .../internal-components/Comment/index.tsx | 11 +++++- .../CommentList/CommentList.tsx | 6 ++- 8 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 src/v4/social/hooks/collections/useCommentsCollection.ts create mode 100644 src/v4/social/icons/minus_circle.tsx diff --git a/src/v4/social/components/CommentTray/CommentTray.tsx b/src/v4/social/components/CommentTray/CommentTray.tsx index 5dd1d940d..0e8c62583 100644 --- a/src/v4/social/components/CommentTray/CommentTray.tsx +++ b/src/v4/social/components/CommentTray/CommentTray.tsx @@ -42,6 +42,7 @@ export const CommentTray = ({ onClickReply={onClickReply} shouldAllowInteraction={shouldAllowInteraction} limit={REPLIES_PER_PAGE} + includeDeleted />
diff --git a/src/v4/social/hooks/collections/useCommentsCollection.ts b/src/v4/social/hooks/collections/useCommentsCollection.ts new file mode 100644 index 000000000..b642f9be5 --- /dev/null +++ b/src/v4/social/hooks/collections/useCommentsCollection.ts @@ -0,0 +1,38 @@ +import { CommentRepository } from '@amityco/ts-sdk'; + +import useLiveCollection from '~/core/hooks/useLiveCollection'; + +type useCommentsParams = { + parentId?: string | null; + referenceId?: string | null; + referenceType: Amity.CommentReferenceType; + limit?: number; + shouldCall?: () => boolean; + includeDeleted?: boolean; +}; + +export default function useCommentsCollection({ + parentId, + referenceId, + referenceType, + limit = 10, + shouldCall = () => true, + includeDeleted = false, +}: useCommentsParams) { + const { items, ...rest } = useLiveCollection({ + fetcher: CommentRepository.getComments, + params: { + parentId, + referenceId: referenceId as string, + referenceType, + limit, + includeDeleted, + }, + shouldCall: () => shouldCall() && !!referenceId && !!referenceType, + }); + + return { + comments: items, + ...rest, + }; +} diff --git a/src/v4/social/icons/index.ts b/src/v4/social/icons/index.ts index ee745d9a9..bbc1d791b 100644 --- a/src/v4/social/icons/index.ts +++ b/src/v4/social/icons/index.ts @@ -6,3 +6,4 @@ export { default as TrashIcon } from './trash'; export { default as FlagIcon } from './flag'; export { default as ChevronDownIcon } from './chevron_down'; export { default as LinkIcon } from './link'; +export { default as MinusCircleIcon } from './minus_circle'; diff --git a/src/v4/social/icons/minus_circle.tsx b/src/v4/social/icons/minus_circle.tsx new file mode 100644 index 000000000..852e2252d --- /dev/null +++ b/src/v4/social/icons/minus_circle.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +function Icon(props: React.SVGProps) { + return ( + + + + ); +} + +export default Icon; diff --git a/src/v4/social/internal-components/Comment/Comment.module.css b/src/v4/social/internal-components/Comment/Comment.module.css index 327387304..e7bf63915 100644 --- a/src/v4/social/internal-components/Comment/Comment.module.css +++ b/src/v4/social/internal-components/Comment/Comment.module.css @@ -19,3 +19,21 @@ .replyContainer { margin-left: 3.25rem; } + +.deletedCommentBlock { + display: flex; + justify-content: flex-start; + align-items: center; + gap: var(--asc-spacing-s2); + padding: var(--asc-spacing-s2) var(--asc-spacing-m1); + border-radius: var(--asc-border-radius-sm); + text-align: center; + color: var(--asc-color-base-shade2); + background-color: var(--asc-color-base-background); + border-top: 1px solid var(--asc-color-base-shade4); +} + +.deletedCommentBlock:first-child { + padding: 0 var(--asc-spacing-m1) var(--asc-spacing-s2) var(--asc-spacing-m1); + border-top: none; +} diff --git a/src/v4/social/internal-components/Comment/UIComment.module.css b/src/v4/social/internal-components/Comment/UIComment.module.css index dbbade992..fe9e36cd3 100644 --- a/src/v4/social/internal-components/Comment/UIComment.module.css +++ b/src/v4/social/internal-components/Comment/UIComment.module.css @@ -144,3 +144,11 @@ gap: 0.125rem; color: var(--asc-color-base-default); } + +.deletedCommentBlock { + padding: var(--asc-spacing-m1); + background-color: var(--asc-color-base-background); + border-radius: var(--asc-border-radius-sm); + text-align: center; + color: var(--asc-color-base-shade2); +} diff --git a/src/v4/social/internal-components/Comment/index.tsx b/src/v4/social/internal-components/Comment/index.tsx index 81b04ee87..00fc0a068 100644 --- a/src/v4/social/internal-components/Comment/index.tsx +++ b/src/v4/social/internal-components/Comment/index.tsx @@ -33,7 +33,7 @@ import { useNotifications } from '~/v4/core/providers/NotificationProvider'; import { Button, BottomSheet, Typography } from '~/v4/core/components'; import styles from './Comment.module.css'; -import { TrashIcon, PenIcon, FlagIcon } from '~/v4/social/icons'; +import { TrashIcon, PenIcon, FlagIcon, MinusCircleIcon } from '~/v4/social/icons'; import { LoadingIndicator } from '~/v4/social/internal-components/LoadingIndicator'; import useCommunityMembersCollection from '~/v4/social/hooks/collections/useCommunityMembersCollection'; import { useCommentFlaggedByMe } from '~/v4/social/hooks'; @@ -223,6 +223,15 @@ export const Comment = ({ commentId, readonly, onClickReply }: CommentProps) => if (comment == null) return null; + if (comment?.isDeleted) { + return isReplyComment ? null : ( +
+ + +
+ ); + } + const renderedComment = ( void; style?: React.CSSProperties; shouldAllowInteraction?: boolean; + includeDeleted?: boolean; } export const CommentList = ({ @@ -27,12 +29,14 @@ export const CommentList = ({ isExpanded = true, onClickReply, shouldAllowInteraction, + includeDeleted = false, }: CommentListProps) => { const { comments, hasMore, loadMore } = useCommentsCollection({ parentId, referenceId, referenceType, limit, + includeDeleted, }); const { formatMessage } = useIntl(); From 14171c3b3089142bdedcf6118dd281fb8e1450e3 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Thu, 6 Jun 2024 11:20:33 +0700 Subject: [PATCH 096/300] fix: category card responsive styles (#372) --- .../community/CategoryCommunitiesList/styles.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/social/components/community/CategoryCommunitiesList/styles.tsx b/src/social/components/community/CategoryCommunitiesList/styles.tsx index d3cbafe43..0e42ded29 100644 --- a/src/social/components/community/CategoryCommunitiesList/styles.tsx +++ b/src/social/components/community/CategoryCommunitiesList/styles.tsx @@ -2,10 +2,9 @@ import styled from 'styled-components'; import EmptyState from '~/core/components/EmptyState'; export const Grid = styled.div` - display: flex; - flex-direction: row; - flex-wrap: wrap; - gap: 16px; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 1rem; `; export const ListEmptyState = styled(EmptyState)` From 2349f87b383d2620651428bf5d61286e2fee3a6f Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Thu, 6 Jun 2024 13:48:05 +0700 Subject: [PATCH 097/300] fix: ASC-22947 - story can't add hyperlink (#373) * fix: hyperlink action condition * fix: hyperlink css * fix: remove unused * fix: hyperlink config condition * fix: css --- .../core/components/Modal/styles.module.css | 2 ++ src/v4/social/pages/DraftsPage/DraftsPage.tsx | 22 +++++++++++++------ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/v4/core/components/Modal/styles.module.css b/src/v4/core/components/Modal/styles.module.css index 53cafcec1..9e1d71960 100644 --- a/src/v4/core/components/Modal/styles.module.css +++ b/src/v4/core/components/Modal/styles.module.css @@ -42,6 +42,8 @@ border-radius: var(--asc-border-radius-lg); max-width: 32.5rem; min-width: 20rem; + /* TOFIX --asc-color-base-background is not defined some how */ + background-color: var(--asc-color-white); } .modalWindow:focus { diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index 593945cd9..09ba86a42 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -19,7 +19,7 @@ import { StoryRepository } from '@amityco/ts-sdk'; import { HyperLinkConfig } from '~/v4/social/components'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; -import { useNotificationData, useNotifications } from '~/v4/core/providers/NotificationProvider'; +import { useNotifications } from '~/v4/core/providers/NotificationProvider'; import { useNavigation } from '~/social/providers/NavigationProvider'; import { PageTypes } from '~/social/constants'; import { BaseVideoPreview } from '../../internal-components/VideoPreview'; @@ -51,7 +51,15 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor data: { url: string; customText: string }; type: Amity.StoryItemType; }[] - >([]); + >([ + { + data: { + url: '', + customText: '', + }, + type: 'hyperlink' as Amity.StoryItemType, + }, + ]); const handleHyperLinkBottomSheetClose = () => { setIsHyperLinkBottomSheetOpen(false); @@ -164,13 +172,13 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor }; const handleOnClickHyperLinkActionButton = () => { - if (hyperLink[0]?.data?.url === '') { - setIsHyperLinkBottomSheetOpen(true); + if (hyperLink[0]?.data?.url) { + notification.info({ + content: formatMessage({ id: 'storyDraft.notification.hyperlink.error' }), + }); return; } - notification.info({ - content: formatMessage({ id: 'storyDraft.notification.hyperlink.error' }), - }); + setIsHyperLinkBottomSheetOpen(true); }; useEffect(() => { From 8e3f30c4677c4e1f7beece9a402275648dead7b1 Mon Sep 17 00:00:00 2001 From: Bonn Date: Thu, 6 Jun 2024 19:01:23 +0700 Subject: [PATCH 098/300] fix: ASC-00000 - bring back v3 code (#358) * Revert "fix: ASC-21481 - remove unnecessary request to prevent rate limit (#230)" This reverts commit e9c82ec82889773dc875f812e7688e4363d20569. # Conflicts: # pnpm-lock.yaml # src/core/providers/UiKitProvider/index.tsx # src/social/components/CommentList/index.tsx # src/social/components/CommentList/styles.tsx # src/social/components/EngagementBar/UIEngagementBar.tsx * fix: reply comment text * Revert "fix: ASC-21481 - remove unnecessary request to prevent rate limit (#230)" This reverts commit e9c82ec82889773dc875f812e7688e4363d20569. # Conflicts: # pnpm-lock.yaml # src/core/providers/UiKitProvider/index.tsx # src/social/components/CommentList/index.tsx # src/social/components/CommentList/styles.tsx # src/social/components/EngagementBar/UIEngagementBar.tsx * fix: reply comment text * fix: undefined .length --- src/social/components/CommentList/index.tsx | 17 +++++----- src/social/components/CommentList/styles.tsx | 10 ------ .../EngagementBar/UIEngagementBar.tsx | 4 +-- src/social/components/Feed/index.tsx | 1 + src/social/components/post/Post/index.tsx | 31 ++++++++++--------- .../collections/useCommentsCollection.ts | 4 +-- 6 files changed, 27 insertions(+), 40 deletions(-) diff --git a/src/social/components/CommentList/index.tsx b/src/social/components/CommentList/index.tsx index 0f4c71f02..5699d3225 100644 --- a/src/social/components/CommentList/index.tsx +++ b/src/social/components/CommentList/index.tsx @@ -1,7 +1,9 @@ import React, { memo } from 'react'; import { useIntl } from 'react-intl'; + import Comment from '~/social/components/Comment'; -import { NoCommentsContainer, TabIcon, TabIconContainer } from './styles'; + +import { TabIcon, TabIconContainer } from './styles'; import LoadMoreWrapper from '../LoadMoreWrapper'; import usePostSubscription from '~/social/hooks/usePostSubscription'; import { SubscriptionLevels } from '@amityco/ts-sdk'; @@ -11,6 +13,7 @@ interface CommentListProps { parentId?: string; referenceId?: string; referenceType: Amity.CommentReferenceType; + // filterByParentId?: boolean; readonly?: boolean; isExpanded?: boolean; limit?: number; @@ -21,6 +24,8 @@ const CommentList = ({ referenceId, referenceType, limit = 5, + // TODO: breaking change + // filterByParentId = false, readonly = false, isExpanded = true, }: CommentListProps) => { @@ -41,7 +46,7 @@ const CommentList = ({ }); const loadMoreText = isReplyComment - ? formatMessage({ id: 'collapsible.viewMoreReplies' }) + ? formatMessage({ id: 'collapsible.viewMoreReplies' }, { count: comments.length }) : formatMessage({ id: 'collapsible.viewMoreComments' }); const prependIcon = isReplyComment ? ( @@ -50,14 +55,6 @@ const CommentList = ({ ) : null; - if (comments.length === 0 && referenceType === 'story' && !isReplyComment) { - return ( - - {formatMessage({ id: 'storyViewer.commentSheet.empty' })} - - ); - } - if (comments.length === 0) return null; return ( diff --git a/src/social/components/CommentList/styles.tsx b/src/social/components/CommentList/styles.tsx index b50a2ba33..ce7e260c3 100644 --- a/src/social/components/CommentList/styles.tsx +++ b/src/social/components/CommentList/styles.tsx @@ -7,13 +7,3 @@ export const TabIconContainer = styled.div` display: flex; margin-right: 8px; `; - -export const NoCommentsContainer = styled.div` - ${({ theme }) => theme.typography.body}; - color: ${({ theme }) => theme.palette.base.shade2}; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - height: 100%; -`; diff --git a/src/social/components/EngagementBar/UIEngagementBar.tsx b/src/social/components/EngagementBar/UIEngagementBar.tsx index 08340c882..328a3c158 100644 --- a/src/social/components/EngagementBar/UIEngagementBar.tsx +++ b/src/social/components/EngagementBar/UIEngagementBar.tsx @@ -73,7 +73,7 @@ const UIEngagementBar = ({ - {latestComments.length > 0 ? ( + {latestComments?.length > 0 ? ( ) : null} @@ -91,7 +91,7 @@ const UIEngagementBar = ({ - {latestComments.length > 0 ? ( + {latestComments?.length > 0 ? ( { const post = usePost(postId); - const avatarFileUrl = useImage({ fileId: post?.creator?.avatarFileId, imageSize: 'small' }); - - const { isFlaggedByMe, flagPost, unflagPost } = usePostFlaggedByMe(post); - + const postedUser = useUser(post?.postedUserId); + const avatarFileUrl = useImage({ fileId: postedUser?.avatarFileId, imageSize: 'small' }); + const childrenPosts = usePostByIds(post?.children); + const { userRoles } = useSDK(); + const { isFlaggedByMe, toggleFlagPost } = usePostFlaggedByMe(post); const postRenderFn = usePostRenderer(post?.dataType); + const { currentUserId } = useSDK(); usePostSubscription({ postId, @@ -40,9 +43,7 @@ const Post = ({ postId, className, hidePostTarget, readonly, onDeleted }: PostPr shouldSubscribe: () => !!post, }); - const pollPost = (post?.latestComments || []).find( - (childPost: Amity.Post) => childPost.dataType === 'poll', - ); + const pollPost = (childrenPosts || []).find((childPost) => childPost.dataType === 'poll'); const poll = usePoll((pollPost?.data as Amity.ContentDataPoll)?.pollId); const isPollClosed = poll?.status === 'closed'; @@ -74,21 +75,21 @@ const Post = ({ postId, className, hidePostTarget, readonly, onDeleted }: PostPr return ( <> {postRenderFn({ - childrenPosts: post?.latestComments || [], + childrenPosts: childrenPosts || [], handleClosePoll, isPollClosed, avatarFileUrl, - user: post?.creator, + user: postedUser, poll, className, - currentUserId: post?.postedUserId || undefined, + currentUserId: currentUserId || undefined, hidePostTarget, post, - userRoles: post?.creator?.roles || [], + userRoles, readonly, isFlaggedByMe, - handleReportPost: flagPost, - handleUnreportPost: unflagPost, + handleReportPost: toggleFlagPost, + handleUnreportPost: toggleFlagPost, handleApprovePost, handleDeclinePost, handleDeletePost, diff --git a/src/social/hooks/collections/useCommentsCollection.ts b/src/social/hooks/collections/useCommentsCollection.ts index a65f100c7..ae64c716d 100644 --- a/src/social/hooks/collections/useCommentsCollection.ts +++ b/src/social/hooks/collections/useCommentsCollection.ts @@ -7,7 +7,6 @@ type useCommentsParams = { referenceId?: string | null; referenceType: Amity.CommentReferenceType; limit?: number; - shouldCall?: () => boolean; // breaking changes // first?: number; // last?: number; @@ -18,7 +17,6 @@ export default function useCommentsCollection({ referenceId, referenceType, limit = 10, - shouldCall = () => true, }: useCommentsParams) { const { items, ...rest } = useLiveCollection({ fetcher: CommentRepository.getComments, @@ -28,7 +26,7 @@ export default function useCommentsCollection({ referenceType, limit, }, - shouldCall: () => shouldCall() && !!referenceId && !!referenceType, + shouldCall: () => !!referenceId && !!referenceType, }); return { From 59a97e6173141e548a1ee2778935fd242782bf1d Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Fri, 7 Jun 2024 13:25:06 +0700 Subject: [PATCH 099/300] fix: video should pause when click item in action menu (#375) --- .../social/internal-components/StoryViewer/Renderers/Video.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx index 014deea96..f536ea8c0 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx @@ -266,7 +266,6 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler className={rendererStyles.actionButton} onClick={() => { bottomSheetAction.action(); - closeBottomSheet(); }} variant="secondary" > From 7a67b43b1e1408fd2814750f5f6adcb9c298ddda Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Fri, 7 Jun 2024 14:47:22 +0700 Subject: [PATCH 100/300] fix: hyperlink text color (#376) --- src/v4/social/elements/HyperLink/HyperLink.module.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v4/social/elements/HyperLink/HyperLink.module.css b/src/v4/social/elements/HyperLink/HyperLink.module.css index ea8d3ae60..0fca5ea50 100644 --- a/src/v4/social/elements/HyperLink/HyperLink.module.css +++ b/src/v4/social/elements/HyperLink/HyperLink.module.css @@ -30,7 +30,7 @@ align-items: center; max-width: calc(100% - 2rem); overflow: hidden; - color: var(--asc-color-secondary-shade4); + color: var(--asc-color-secondary-default); } .text { From c80ef32e3b88a0a5ccd407f7f75d8366d9b3e152 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Fri, 7 Jun 2024 14:51:38 +0700 Subject: [PATCH 101/300] fix: ASC-21507 - reset form when confirm remove hyperlink (#368) * fix: hyperlink action condition * fix: hyperlink css * fix: remove unused * fix: hyperlink config condition * fix: reset form when confirm remove hyperlink --- src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx index 0f25a55fd..e806c0d35 100644 --- a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx +++ b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx @@ -102,6 +102,7 @@ export const HyperLinkConfig = ({ register, handleSubmit, formState: { errors }, + reset, } = useForm({ resolver: zodResolver(schema), }); @@ -112,6 +113,7 @@ export const HyperLinkConfig = ({ }; const confirmDiscardHyperlink = () => { + reset(); onRemove(); onClose(); }; From d9ae11655b067377202215154bf7b34290e43de3 Mon Sep 17 00:00:00 2001 From: Bonn Date: Mon, 10 Jun 2024 13:56:46 +0700 Subject: [PATCH 102/300] chore: ASC-22035 - customizations (#378) * feat: ThemeProvider, CustomizationProvider and IconComponent * chore: remove unused code * chore: useAmityComponentProps * feat: useAmityPage, useAmityComponent, useAmityElement * chore: update foundation * fix: uikit hook bug * fix: update core foundation (theme, modal) * fix: default color palette * fix: types * fix: fix isExcluded * chore: remove unused code * fix: typing * fix: fix a testing workflow * chore: Apply suggestions from code review Co-authored-by: Pitchaya T. <33589608+ptchayap@users.noreply.github.com> --------- Co-authored-by: Pitchaya T. <33589608+ptchayap@users.noreply.github.com> --- .github/workflows/testing.yaml | 2 +- .../AmityLiveChatMessageList/index.tsx | 1 - src/v4/core/IconComponent.tsx | 19 + src/v4/core/components/ConfirmModal/index.tsx | 76 ++-- src/v4/core/components/Modal/index.tsx | 15 +- .../core/components/Typography/Typography.tsx | 40 +- .../collections/useAllUsersCollection.ts | 15 + src/v4/core/hooks/collections/useCommunity.ts | 24 + .../collections/useFollowersCollection.ts | 26 ++ .../collections/useFollowingsCollection.ts | 26 ++ .../core/hooks/collections/useGlobalFeed.ts | 69 +++ .../hooks/collections/useUsersCollection.ts | 75 ++++ src/v4/core/hooks/objects/usePost.ts | 18 + src/v4/core/hooks/objects/useUser.ts | 21 + .../subscriptions/useCommentSubscription.ts | 20 + .../useCommunityReactionSubscription.ts | 19 + .../subscriptions/useCommunitySubscription.ts | 22 + .../subscriptions/usePostSubscription.ts | 22 + .../subscriptions/useReactionSubscription.ts | 33 ++ .../hooks/subscriptions/useSubscription.ts | 49 ++ .../useUserReactionSubscription.ts | 19 + .../subscriptions/useUserSubscription.ts | 22 + src/v4/core/hooks/uikit/index.ts | 49 ++ src/v4/core/hooks/useIntersectionObserver.ts | 25 ++ src/v4/core/hooks/useLiveCollection.ts | 79 ++++ src/v4/core/hooks/useLiveObject.ts | 79 ++++ src/v4/core/hooks/useSDK.ts | 6 + src/v4/core/providers/AmityUIKitProvider.tsx | 87 ++-- src/v4/core/providers/ConfirmProvider.tsx | 11 +- .../core/providers/CustomizationProvider.tsx | 80 ++-- src/v4/core/providers/NavigationProvider.tsx | 425 ++++++++++++++++++ .../core/providers/PageBehaviorProvider.tsx | 92 +++- .../SDKConnectorFetcherProvider.tsx | 96 ++++ .../SDKConnectorLiveCollectionProvider.tsx | 99 ++++ .../SDKConnectorLiveObjectProvider.tsx | 99 ++++ .../SDKConnectorSubscribersProvider.tsx | 68 +++ .../providers/SDKConnectorProvider/index.tsx | 21 + src/v4/core/providers/SDKProvider.tsx | 11 + src/v4/core/providers/ThemeProvider.tsx | 17 +- src/v4/social/pages/DraftsPage/DraftsPage.tsx | 4 +- src/v4/styles/global.css | 64 ++- 41 files changed, 1874 insertions(+), 171 deletions(-) create mode 100644 src/v4/core/IconComponent.tsx create mode 100644 src/v4/core/hooks/collections/useAllUsersCollection.ts create mode 100644 src/v4/core/hooks/collections/useCommunity.ts create mode 100644 src/v4/core/hooks/collections/useFollowersCollection.ts create mode 100644 src/v4/core/hooks/collections/useFollowingsCollection.ts create mode 100644 src/v4/core/hooks/collections/useGlobalFeed.ts create mode 100644 src/v4/core/hooks/collections/useUsersCollection.ts create mode 100644 src/v4/core/hooks/objects/usePost.ts create mode 100644 src/v4/core/hooks/objects/useUser.ts create mode 100644 src/v4/core/hooks/subscriptions/useCommentSubscription.ts create mode 100644 src/v4/core/hooks/subscriptions/useCommunityReactionSubscription.ts create mode 100644 src/v4/core/hooks/subscriptions/useCommunitySubscription.ts create mode 100644 src/v4/core/hooks/subscriptions/usePostSubscription.ts create mode 100644 src/v4/core/hooks/subscriptions/useReactionSubscription.ts create mode 100644 src/v4/core/hooks/subscriptions/useSubscription.ts create mode 100644 src/v4/core/hooks/subscriptions/useUserReactionSubscription.ts create mode 100644 src/v4/core/hooks/subscriptions/useUserSubscription.ts create mode 100644 src/v4/core/hooks/uikit/index.ts create mode 100644 src/v4/core/hooks/useIntersectionObserver.ts create mode 100644 src/v4/core/hooks/useLiveCollection.ts create mode 100644 src/v4/core/hooks/useLiveObject.ts create mode 100644 src/v4/core/hooks/useSDK.ts create mode 100644 src/v4/core/providers/NavigationProvider.tsx create mode 100644 src/v4/core/providers/SDKConnectorProvider/SDKConnectorFetcherProvider.tsx create mode 100644 src/v4/core/providers/SDKConnectorProvider/SDKConnectorLiveCollectionProvider.tsx create mode 100644 src/v4/core/providers/SDKConnectorProvider/SDKConnectorLiveObjectProvider.tsx create mode 100644 src/v4/core/providers/SDKConnectorProvider/SDKConnectorSubscribersProvider.tsx create mode 100644 src/v4/core/providers/SDKConnectorProvider/index.tsx create mode 100644 src/v4/core/providers/SDKProvider.tsx diff --git a/.github/workflows/testing.yaml b/.github/workflows/testing.yaml index c55fc3726..82036f8fe 100644 --- a/.github/workflows/testing.yaml +++ b/.github/workflows/testing.yaml @@ -26,7 +26,7 @@ jobs: - name: Get pnpm store directory id: pnpm-cache run: | - echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT + echo "pnpm_cache_dir=$(pnpm store path | tr -d '\n')" >> $GITHUB_OUTPUT - uses: actions/cache@v3 name: Setup pnpm cache diff --git a/src/v4/chat/components/AmityLiveChatMessageList/index.tsx b/src/v4/chat/components/AmityLiveChatMessageList/index.tsx index 34cbb6e9f..ce87e4ae6 100644 --- a/src/v4/chat/components/AmityLiveChatMessageList/index.tsx +++ b/src/v4/chat/components/AmityLiveChatMessageList/index.tsx @@ -64,7 +64,6 @@ export const AmityLiveChatMessageList = ({ content: formatMessage({ id: 'livechat.delete.message.error' }), }), }), - theme: 'dark', }); }; diff --git a/src/v4/core/IconComponent.tsx b/src/v4/core/IconComponent.tsx new file mode 100644 index 000000000..cc32144e3 --- /dev/null +++ b/src/v4/core/IconComponent.tsx @@ -0,0 +1,19 @@ +export interface IconComponentProps { + defaultIcon: () => JSX.Element; + imgIcon: () => JSX.Element; + defaultIconName?: string; + configIconName?: string; +} + +export const IconComponent = ({ + defaultIcon, + imgIcon, + defaultIconName, + configIconName, +}: IconComponentProps) => { + if (defaultIconName === configIconName) { + return defaultIcon(); + } + + return imgIcon(); +}; diff --git a/src/v4/core/components/ConfirmModal/index.tsx b/src/v4/core/components/ConfirmModal/index.tsx index 346ae463f..b06aa3a73 100644 --- a/src/v4/core/components/ConfirmModal/index.tsx +++ b/src/v4/core/components/ConfirmModal/index.tsx @@ -1,12 +1,22 @@ -import React from 'react'; +import React, { ReactNode } from 'react'; import Modal from '~/v4/core/components/Modal'; import { Button } from '~/v4/core/components/Button'; import clsx from 'clsx'; import styles from './styles.module.css'; -import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; +import { ConfirmType, useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; +import { useAmityElement } from '~/v4/core/hooks/uikit/index'; + +interface ConfirmProps extends ConfirmType { + className?: string; + okText?: ReactNode; + cancelText?: ReactNode; + type?: 'confirm' | 'info'; +} const Confirm = ({ - 'data-qa-anchor': dataQaAnchor = '', + pageId = '*', + componentId = '*', + elementId = '*', className, title, content, @@ -15,36 +25,42 @@ const Confirm = ({ cancelText = 'Cancel', onCancel, type = 'confirm', -}: any) => ( - - {type === 'confirm' && ( +}: ConfirmProps) => { + const { accessibilityId, themeStyles } = useAmityElement({ pageId, componentId, elementId }); + return ( + + {type === 'confirm' && ( + + )} - )} - -
- } - onCancel={onCancel} - > -
{content}
- -); +
+ } + onCancel={onCancel} + > +
{content}
+ + ); +}; export const ConfirmComponent = () => { const { confirmData, closeConfirm } = useConfirmContext(); diff --git a/src/v4/core/components/Modal/index.tsx b/src/v4/core/components/Modal/index.tsx index 4fc246cbf..938359bc8 100644 --- a/src/v4/core/components/Modal/index.tsx +++ b/src/v4/core/components/Modal/index.tsx @@ -2,9 +2,12 @@ import React, { ReactNode, useEffect, useRef } from 'react'; import styles from './styles.module.css'; import clsx from 'clsx'; import Close from '~/v4/icons/Close'; +import { useAmityElement } from '../../hooks/uikit'; export interface ModalProps { - 'data-qa-anchor'?: string; + pageId?: string; + componentId?: string; + elementId?: string; size?: 'small' | ''; className?: string; onOverlayClick?: () => void; @@ -16,7 +19,9 @@ export interface ModalProps { } const Modal = ({ - 'data-qa-anchor': dataQaAnchor = '', + pageId = '*', + componentId = '*', + elementId = '*', size = '', onOverlayClick = () => {}, onCancel, @@ -24,6 +29,8 @@ const Modal = ({ footer, children, }: ModalProps) => { + const { accessibilityId, themeStyles } = useAmityElement({ pageId, componentId, elementId }); + const modalRef = useRef(null); // auto focus to prevent scroll on background (when focus kept on trigger button) useEffect(() => { @@ -31,10 +38,10 @@ const Modal = ({ }, [modalRef?.current]); return ( -
+
diff --git a/src/v4/core/components/Typography/Typography.tsx b/src/v4/core/components/Typography/Typography.tsx index 7eff4074b..4c0d08c68 100644 --- a/src/v4/core/components/Typography/Typography.tsx +++ b/src/v4/core/components/Typography/Typography.tsx @@ -5,6 +5,7 @@ import typography from '~/v4/styles/typography.module.css'; interface TypographyProps { children: React.ReactNode; className?: string; + style?: React.CSSProperties; } const Typography: React.FC & { @@ -15,85 +16,92 @@ const Typography: React.FC & { BodyBold: React.FC; Caption: React.FC; CaptionBold: React.FC; -} = ({ children, className = '', ...props }) => { +} = ({ children, className = '', style, ...rest }) => { return ( -
+
{children}
); }; -Typography.Heading = ({ children, className = '', ...props }) => { +Typography.Heading = ({ children, className = '', style, ...rest }) => { return (

{children}

); }; -Typography.Title = ({ children, className = '', ...props }) => { +Typography.Title = ({ children, className = '', style, ...rest }) => { return (

{children}

); }; -Typography.Subtitle = ({ children, className = '', ...props }) => { +Typography.Subtitle = ({ children, className = '', style, ...rest }) => { return (

{children}

); }; -Typography.Body = ({ children, className = '', ...props }) => { +Typography.Body = ({ children, className = '', style, ...rest }) => { return (

{children}

); }; -Typography.BodyBold = ({ children, className = '', ...props }) => { +Typography.BodyBold = ({ children, className = '', style, ...rest }) => { return (

{children}

); }; -Typography.Caption = ({ children, className = '', ...props }) => { +Typography.Caption = ({ children, className = '', style, ...rest }) => { return (

{children}

); }; -Typography.CaptionBold = ({ children, className = '', ...props }) => { +Typography.CaptionBold = ({ children, className = '', style, ...rest }) => { return (

{children}

diff --git a/src/v4/core/hooks/collections/useAllUsersCollection.ts b/src/v4/core/hooks/collections/useAllUsersCollection.ts new file mode 100644 index 000000000..ba6870538 --- /dev/null +++ b/src/v4/core/hooks/collections/useAllUsersCollection.ts @@ -0,0 +1,15 @@ +import { UserRepository } from '@amityco/ts-sdk'; + +import useLiveCollection from '~/v4/core/hooks/useLiveCollection'; + +export default function useAllUsersCollection() { + const { items, ...rest } = useLiveCollection({ + fetcher: UserRepository.getUsers, + params: {}, + }); + + return { + users: items, + ...rest, + }; +} diff --git a/src/v4/core/hooks/collections/useCommunity.ts b/src/v4/core/hooks/collections/useCommunity.ts new file mode 100644 index 000000000..afdc7803a --- /dev/null +++ b/src/v4/core/hooks/collections/useCommunity.ts @@ -0,0 +1,24 @@ +import { CommunityRepository } from '@amityco/ts-sdk'; + +import useLiveObject from '~/v4/core/hooks/useLiveObject'; + +const useCommunity = ({ + communityId, + shouldCall = () => true, +}: { + communityId: string | null | undefined; + shouldCall?: () => boolean; +}) => { + const { item, ...rest } = useLiveObject({ + fetcher: CommunityRepository.getCommunity, + params: communityId, + shouldCall: () => shouldCall() && !!communityId, + }); + + return { + community: item, + ...rest, + }; +}; + +export default useCommunity; diff --git a/src/v4/core/hooks/collections/useFollowersCollection.ts b/src/v4/core/hooks/collections/useFollowersCollection.ts new file mode 100644 index 000000000..b6e0999f4 --- /dev/null +++ b/src/v4/core/hooks/collections/useFollowersCollection.ts @@ -0,0 +1,26 @@ +import { UserRepository } from '@amityco/ts-sdk'; +import useLiveCollection from '~/v4/core/hooks/useLiveCollection'; + +type FollowStatusInput = Amity.QueryFollowers['status']; + +export default function useFollowersCollection({ + userId, + status, +}: { + userId?: string | null; + status: FollowStatusInput; +}) { + const { items, ...rest } = useLiveCollection({ + fetcher: UserRepository.Relationship.getFollowers, + params: { + userId: userId as string, + status: status ?? undefined, + }, + shouldCall: () => !!userId, + }); + + return { + followers: items, + ...rest, + }; +} diff --git a/src/v4/core/hooks/collections/useFollowingsCollection.ts b/src/v4/core/hooks/collections/useFollowingsCollection.ts new file mode 100644 index 000000000..6c506670e --- /dev/null +++ b/src/v4/core/hooks/collections/useFollowingsCollection.ts @@ -0,0 +1,26 @@ +import { UserRepository } from '@amityco/ts-sdk'; +import useLiveCollection from '~/v4/core/hooks/useLiveCollection'; + +type FollowStatusInput = Amity.QueryFollowings['status']; + +export default function useFollowingsCollection({ + userId, + status, +}: { + userId?: string | null; + status: FollowStatusInput; +}) { + const { items, ...rest } = useLiveCollection({ + fetcher: UserRepository.Relationship.getFollowings, + params: { + userId: userId as string, + status: status ?? undefined, + }, + shouldCall: () => !!userId, + }); + + return { + followings: items, + ...rest, + }; +} diff --git a/src/v4/core/hooks/collections/useGlobalFeed.ts b/src/v4/core/hooks/collections/useGlobalFeed.ts new file mode 100644 index 000000000..b7afa6c72 --- /dev/null +++ b/src/v4/core/hooks/collections/useGlobalFeed.ts @@ -0,0 +1,69 @@ +import { FeedRepository } from '@amityco/ts-sdk'; +import { useEffect, useMemo, useState } from 'react'; + +const useGlobalFeed = () => { + const [items, setItems] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [queryToken, setQueryToken] = useState(null); + const [loadMoreHasBeenCalled, setLoadMoreHasBeenCalled] = useState(false); + + async function fetchMore() { + try { + setIsLoading(true); + const newPosts = await FeedRepository.queryGlobalFeed({ + limit: 10, + queryToken: queryToken || undefined, + }); + setQueryToken(newPosts.paging.next || null); + setItems((prevItems) => [...prevItems, ...newPosts.data]); + } finally { + setIsLoading(false); + } + } + + useEffect(() => { + fetchMore(); + + return () => { + setItems([]); + setQueryToken(null); + }; + }, []); + + const prependItem = (post: Amity.Post) => { + setItems((prevItems) => [post, ...prevItems]); + }; + + const removeItem = (postId: string) => { + const newItems = items.filter((item) => item.postId !== postId); + setItems(newItems); + }; + + const hasMore = useMemo(() => queryToken !== null, [queryToken]); + + const loadMore = () => { + setLoadMoreHasBeenCalled(true); + if (hasMore) { + fetchMore(); + } + }; + + const refetch = () => { + setItems([]); + setQueryToken(null); + fetchMore(); + }; + + return { + posts: items, + isLoading, + prependItem, + removeItem, + loadMore, + hasMore, + loadMoreHasBeenCalled, + refetch, + }; +}; + +export default useGlobalFeed; diff --git a/src/v4/core/hooks/collections/useUsersCollection.ts b/src/v4/core/hooks/collections/useUsersCollection.ts new file mode 100644 index 000000000..3c74df077 --- /dev/null +++ b/src/v4/core/hooks/collections/useUsersCollection.ts @@ -0,0 +1,75 @@ +import { UserRepository } from '@amityco/ts-sdk'; +import { useEffect, useRef, useState } from 'react'; + +import useLiveCollection from '~/v4/core/hooks/useLiveCollection'; + +const MINIMUM_STRING_LENGTH_TO_TRIGGER_QUERY = 1; + + +export const useUserQueryByDisplayName = ( + displayName: string, + minLength: number = MINIMUM_STRING_LENGTH_TO_TRIGGER_QUERY, +) => { + const [items, setItems] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [hasMore, setHasMore] = useState(false); + const [loadMoreHasBeenCalled, setLoadMoreHasBeenCalled] = useState(false); + const loadMoreRef = useRef<(() => void) | null>(null); + const unSubRef = useRef<(() => void) | null>(null); + + const loadMore = () => { + if (loadMoreRef.current) { + loadMoreRef.current(); + setLoadMoreHasBeenCalled(true); + } + }; + + useEffect(() => { + if (displayName.length < minLength) return; + + if (unSubRef.current) { + unSubRef.current(); + unSubRef.current = null; + } + + const unSubFn = UserRepository.searchUserByDisplayName( + { displayName, limit: 10 }, + (response) => { + setHasMore(response.hasNextPage || false); + setIsLoading(response.loading); + loadMoreRef.current = response.onNextPage || null; + setItems(response.data); + }, + ); + unSubRef.current = unSubFn; + + return () => { + unSubRef.current?.(); + unSubRef.current = null; + }; + }, [displayName, minLength]); + + return { + users: items, + isLoading, + hasMore, + loadMore, + loadMoreHasBeenCalled, + }; +}; + +// breaking changes +export default function useUsersCollection( + term: Parameters[0], + shouldCall: () => boolean = () => true, +) { + const { items, ...rest } = useLiveCollection({ + fetcher: UserRepository.getUsers, + params: term, + }); + + return { + users: items, + ...rest, + }; +} diff --git a/src/v4/core/hooks/objects/usePost.ts b/src/v4/core/hooks/objects/usePost.ts new file mode 100644 index 000000000..47190cb98 --- /dev/null +++ b/src/v4/core/hooks/objects/usePost.ts @@ -0,0 +1,18 @@ +import { PostRepository } from '@amityco/ts-sdk'; + +import useLiveObject from '~/v4/core/hooks/useLiveObject'; + +const usePost = (postId?: string) => { + const { item, ...rest } = useLiveObject({ + fetcher: PostRepository.getPost, + params: postId as string, + shouldCall: () => !!postId, + }); + + return { + post: item, + ...rest, + }; +}; + +export default usePost; diff --git a/src/v4/core/hooks/objects/useUser.ts b/src/v4/core/hooks/objects/useUser.ts new file mode 100644 index 000000000..cc8ff3dba --- /dev/null +++ b/src/v4/core/hooks/objects/useUser.ts @@ -0,0 +1,21 @@ +/* eslint-disable no-nested-ternary */ + +import { SubscriptionLevels, UserRepository } from '@amityco/ts-sdk'; + +import useLiveObject from '~/v4/core/hooks/useLiveObject'; +import useUserSubscription from '~/social/hooks/useUserSubscription'; + +const useUser = (userId?: string | null) => { + useUserSubscription({ + userId, + level: SubscriptionLevels.USER, + }); + + return useLiveObject({ + fetcher: UserRepository.getUser, + params: userId, + shouldCall: () => !!userId, + }); +}; + +export default useUser; diff --git a/src/v4/core/hooks/subscriptions/useCommentSubscription.ts b/src/v4/core/hooks/subscriptions/useCommentSubscription.ts new file mode 100644 index 000000000..2b19a9c1e --- /dev/null +++ b/src/v4/core/hooks/subscriptions/useCommentSubscription.ts @@ -0,0 +1,20 @@ +import { CommentRepository, getCommentTopic } from '@amityco/ts-sdk'; +import useSubscription from './useSubscription'; + +export default function useCommentSubscription({ + commentId, + shouldSubscribe = () => true, + callback, +}: { + commentId?: string | null; + shouldSubscribe?: () => boolean; + callback?: Amity.Listener; +}) { + return useSubscription({ + fetcher: CommentRepository.getComment, + params: commentId, + callback, + shouldSubscribe: () => !!commentId && shouldSubscribe(), + getSubscribedTopic: ({ data: comment }) => getCommentTopic(comment), + }); +} diff --git a/src/v4/core/hooks/subscriptions/useCommunityReactionSubscription.ts b/src/v4/core/hooks/subscriptions/useCommunityReactionSubscription.ts new file mode 100644 index 000000000..2c27916bb --- /dev/null +++ b/src/v4/core/hooks/subscriptions/useCommunityReactionSubscription.ts @@ -0,0 +1,19 @@ +import { SubscriptionLevels } from '@amityco/ts-sdk'; +import useCommunitySubscription from './useCommunitySubscription'; + +export default function useCommunityReactionSubscription({ + communityId, + shouldSubscribe = () => true, + callback, +}: { + communityId?: string | null; + shouldSubscribe?: () => boolean; + callback?: Amity.Listener; +}) { + return useCommunitySubscription({ + communityId, + level: SubscriptionLevels.POST_AND_COMMENT, + shouldSubscribe, + callback, + }); +} diff --git a/src/v4/core/hooks/subscriptions/useCommunitySubscription.ts b/src/v4/core/hooks/subscriptions/useCommunitySubscription.ts new file mode 100644 index 000000000..6397daad6 --- /dev/null +++ b/src/v4/core/hooks/subscriptions/useCommunitySubscription.ts @@ -0,0 +1,22 @@ +import { CommunityRepository, getCommunityTopic } from '@amityco/ts-sdk'; +import useSubscription from './useSubscription'; + +export default function useCommunitySubscription({ + communityId, + level, + shouldSubscribe = () => true, + callback, +}: { + communityId?: string | null; + level: Parameters[1]; + shouldSubscribe?: () => boolean; + callback?: Amity.Listener; +}) { + return useSubscription({ + fetcher: CommunityRepository.getCommunity, + params: communityId, + callback, + shouldSubscribe: () => !!communityId && shouldSubscribe(), + getSubscribedTopic: ({ data: community }) => getCommunityTopic(community, level), + }); +} diff --git a/src/v4/core/hooks/subscriptions/usePostSubscription.ts b/src/v4/core/hooks/subscriptions/usePostSubscription.ts new file mode 100644 index 000000000..212aa515a --- /dev/null +++ b/src/v4/core/hooks/subscriptions/usePostSubscription.ts @@ -0,0 +1,22 @@ +import { PostRepository, getPostTopic } from '@amityco/ts-sdk'; +import useSubscription from './useSubscription'; + +export default function usePostSubscription({ + postId, + level, + shouldSubscribe = () => true, + callback, +}: { + postId?: string | null; + level: Parameters[1]; + shouldSubscribe?: () => boolean; + callback?: Amity.Listener; +}) { + return useSubscription({ + fetcher: PostRepository.getPost, + params: postId, + callback, + shouldSubscribe: () => !!postId && shouldSubscribe(), + getSubscribedTopic: ({ data: post }) => getPostTopic(post, level), + }); +} diff --git a/src/v4/core/hooks/subscriptions/useReactionSubscription.ts b/src/v4/core/hooks/subscriptions/useReactionSubscription.ts new file mode 100644 index 000000000..984ef4fc6 --- /dev/null +++ b/src/v4/core/hooks/subscriptions/useReactionSubscription.ts @@ -0,0 +1,33 @@ +import useCommunityReactionSubscription from './useCommunityReactionSubscription'; +import useUserReactionSubscription from './useUserReactionSubscription'; + +export default function useReactionSubscription({ + targetId, + targetType, + callback, + shouldSubscribe = () => true, +}: { + targetId?: string | null; + targetType: 'user' | 'community'; + shouldSubscribe?: () => boolean; + callback?: Amity.Listener; +}) { + const { unsubscribe: userUnsubscribe } = useUserReactionSubscription({ + userId: targetId, + shouldSubscribe: () => shouldSubscribe() && targetType === 'user', + callback, + }); + + const { unsubscribe: communityUnsubscribe } = useCommunityReactionSubscription({ + communityId: targetId, + shouldSubscribe: () => shouldSubscribe() && targetType === 'community', + callback, + }); + + return { + unsubscribe() { + userUnsubscribe(); + communityUnsubscribe(); + }, + }; +} diff --git a/src/v4/core/hooks/subscriptions/useSubscription.ts b/src/v4/core/hooks/subscriptions/useSubscription.ts new file mode 100644 index 000000000..d3b5c515b --- /dev/null +++ b/src/v4/core/hooks/subscriptions/useSubscription.ts @@ -0,0 +1,49 @@ +import { useEffect, useRef } from 'react'; +import useLiveObject from '~/v4/core/hooks/useLiveObject'; +import { useSDKSubscribersConnector } from '~/v4/core/providers/SDKConnectorProvider/SDKConnectorSubscribersProvider'; + +export default function useSubscription({ + fetcher, + params, + callback = () => {}, + options, + shouldSubscribe = () => true, + getSubscribedTopic, +}: { + fetcher: ( + params: TParams, + callback: Amity.LiveObjectCallback, + options?: Amity.LiveObjectOptions, + ) => Amity.Unsubscriber; + params: TParams | undefined | null; + callback?: Amity.Listener; + options?: Amity.LiveObjectOptions; + shouldSubscribe?: () => boolean; + getSubscribedTopic: (response: Amity.LiveObject) => string; +}) { + const { subscribe } = useSDKSubscribersConnector(); + const unsubscribeTopicRef = useRef<(() => void) | null>(null); + useLiveObject({ + fetcher, + params, + callback: (response) => { + const { error, loading } = response; + if (loading) return; + if (error) throw error; + const { unsubscribe } = subscribe({ topic: getSubscribedTopic(response), callback }); + unsubscribeTopicRef.current = unsubscribe; + }, + options, + shouldCall: shouldSubscribe, + }); + + useEffect(() => { + return () => { + unsubscribeTopicRef.current?.(); + }; + }, [unsubscribeTopicRef]); + + return { + unsubscribe: () => unsubscribeTopicRef.current?.(), + }; +} diff --git a/src/v4/core/hooks/subscriptions/useUserReactionSubscription.ts b/src/v4/core/hooks/subscriptions/useUserReactionSubscription.ts new file mode 100644 index 000000000..3911f269c --- /dev/null +++ b/src/v4/core/hooks/subscriptions/useUserReactionSubscription.ts @@ -0,0 +1,19 @@ +import { SubscriptionLevels } from '@amityco/ts-sdk'; +import useUserSubscription from './useUserSubscription'; + +export default function useUserReactionSubscription({ + userId, + callback, + shouldSubscribe, +}: { + userId?: string | null; + shouldSubscribe?: () => boolean; + callback?: Amity.Listener; +}) { + return useUserSubscription({ + userId, + level: SubscriptionLevels.POST_AND_COMMENT, + shouldSubscribe, + callback, + }); +} diff --git a/src/v4/core/hooks/subscriptions/useUserSubscription.ts b/src/v4/core/hooks/subscriptions/useUserSubscription.ts new file mode 100644 index 000000000..792f18805 --- /dev/null +++ b/src/v4/core/hooks/subscriptions/useUserSubscription.ts @@ -0,0 +1,22 @@ +import { UserRepository, getUserTopic } from '@amityco/ts-sdk'; +import useSubscription from './useSubscription'; + +export default function useUserSubscription({ + userId, + level, + shouldSubscribe = () => true, + callback, +}: { + userId?: string | null; + level: Parameters[1]; + shouldSubscribe?: () => boolean; + callback?: Amity.Listener; +}) { + return useSubscription({ + fetcher: UserRepository.getUser, + params: userId, + callback, + shouldSubscribe: () => !!userId && shouldSubscribe(), + getSubscribedTopic: ({ data: user }) => getUserTopic(user, level), + }); +} diff --git a/src/v4/core/hooks/uikit/index.ts b/src/v4/core/hooks/uikit/index.ts new file mode 100644 index 000000000..4e215a2cd --- /dev/null +++ b/src/v4/core/hooks/uikit/index.ts @@ -0,0 +1,49 @@ +import { getDefaultConfig, useCustomization } from '~/v4/core/providers/CustomizationProvider'; +import { useGenerateStylesShadeColors, useTheme } from '~/v4/core/providers/ThemeProvider'; + +export const useAmityElement = ({ + pageId, + componentId, + elementId, +}: { + pageId: string; + componentId: string; + elementId: string; +}) => { + const uiReference = `${pageId}/${componentId}/${elementId}`; + const { getConfig, isExcluded } = useCustomization(); + const config = getConfig(uiReference); + const defaultConfig = getDefaultConfig(uiReference); + const themeStyles = useGenerateStylesShadeColors(config); + const isComponentExcluded = isExcluded(uiReference); + const accessibilityId = uiReference; + const currentTheme = useTheme(); + + return { + currentTheme, + config, + defaultConfig, + uiReference, + accessibilityId, + themeStyles, + isExcluded: isComponentExcluded, + }; +}; + +export const useAmityComponent = ({ + pageId, + componentId, +}: { + pageId: string; + componentId: string; +}) => { + const elementId = '*'; + return useAmityElement({ pageId, componentId, elementId }); +}; + +export const useAmityPage = ({ pageId }: { pageId: string }) => { + const componentId = '*'; + const elementId = '*'; + + return useAmityElement({ pageId, componentId, elementId }); +}; diff --git a/src/v4/core/hooks/useIntersectionObserver.ts b/src/v4/core/hooks/useIntersectionObserver.ts new file mode 100644 index 000000000..5dc33f156 --- /dev/null +++ b/src/v4/core/hooks/useIntersectionObserver.ts @@ -0,0 +1,25 @@ +import { MutableRefObject, useEffect } from 'react'; + +const useIntersectionObserver = ({ + ref, + onIntersect, + options, +}: { + ref: MutableRefObject; + onIntersect: () => void; + options?: IntersectionObserverInit; +}) => { + useEffect(() => { + if (!ref?.current) return; + + const observer = new IntersectionObserver( + (entries) => entries[0]?.isIntersecting && onIntersect(), + options, + ); + observer.observe(ref.current); + + return () => observer.disconnect(); + }, [ref, onIntersect, options]); +}; + +export default useIntersectionObserver; diff --git a/src/v4/core/hooks/useLiveCollection.ts b/src/v4/core/hooks/useLiveCollection.ts new file mode 100644 index 000000000..dcb5ae63a --- /dev/null +++ b/src/v4/core/hooks/useLiveCollection.ts @@ -0,0 +1,79 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import { useSDKLiveCollectionConnector } from '~/v4/core/providers/SDKConnectorProvider'; + +function useLiveCollection({ + fetcher, + params, + callback = () => {}, + config, + shouldCall = () => true, +}: { + fetcher: ( + params: Amity.LiveCollectionParams, + callback: Amity.LiveCollectionCallback, + config?: Amity.LiveCollectionConfig, + ) => Amity.Unsubscriber; + params: Amity.LiveCollectionParams; + callback?: Amity.LiveCollectionCallback; + config?: Amity.LiveCollectionConfig; + shouldCall?: () => boolean; +}): { + items: TCallback[]; + isLoading: boolean; + hasMore: boolean; + loadMore: () => void; + error: Error | null; + loadMoreHasBeenCalled: boolean; +} { + const { subscribe } = useSDKLiveCollectionConnector(); + const [loadMoreHasBeenCalled, setLoadMoreHasBeenCalled] = useState(false); + const [isLoading, setIsLoading] = useState(shouldCall ? shouldCall() : true); + const [items, setItems] = useState([]); + const [error, setError] = useState(null); + const [hasMore, setHasMore] = useState(false); + const loadMoreFnRef = useRef<(() => void) | null>(null); + + const loadMore = useCallback(() => { + if (loadMoreFnRef.current) { + setLoadMoreHasBeenCalled(true); + loadMoreFnRef.current?.(); + } + }, [loadMoreFnRef, loadMoreHasBeenCalled, isLoading, setIsLoading]); + + const callbackFn = useCallback( + (response) => { + if (!shouldCall()) return; + if (response.data) setItems(response.data); + setIsLoading(response.loading); + setHasMore(response.hasNextPage); + setError(response.error); + loadMoreFnRef.current = response.onNextPage; + callback(response); + }, + [shouldCall, setItems, setIsLoading, setHasMore, loadMoreFnRef, callback], + ); + + useEffect(() => { + if (!shouldCall()) return; + const { unsubscribe } = subscribe({ + fetcher, + params, + callback: callbackFn, + }); + + return () => { + unsubscribe(); + }; + }, [params, shouldCall]); + + return { + items, + hasMore, + isLoading, + loadMore, + error, + loadMoreHasBeenCalled, + }; +} + +export default useLiveCollection; diff --git a/src/v4/core/hooks/useLiveObject.ts b/src/v4/core/hooks/useLiveObject.ts new file mode 100644 index 000000000..78c145e31 --- /dev/null +++ b/src/v4/core/hooks/useLiveObject.ts @@ -0,0 +1,79 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import { useSDKLiveObjectConnector } from '~/v4/core/providers/SDKConnectorProvider'; +import { subscribeTopic } from '@amityco/ts-sdk'; + +function useLiveObject({ + fetcher, + params, + callback = () => {}, + options, + shouldCall = () => true, + getSubscribedTopic, +}: { + fetcher: ( + params: TParams, + callback: Amity.LiveObjectCallback, + options?: Amity.LiveObjectOptions, + ) => Amity.Unsubscriber; + params: TParams | undefined | null; + callback?: Amity.LiveObjectCallback; + options?: Amity.LiveObjectOptions; + shouldCall?: () => boolean; + getSubscribedTopic?: () => string; +}) { + const { subscribe } = useSDKLiveObjectConnector(); + const [item, setItem] = useState(null); + const [origin, setOrigin] = useState(undefined); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const unsubscribeTopicRef = useRef<(() => void) | null>(null); + + const callbackFn: Amity.LiveObjectCallback = useCallback( + (response) => { + if (shouldCall && !shouldCall()) return; + if (params == null) return; + setIsLoading(response.loading); + if (response.data) setItem(response.data); + setOrigin(response.origin); + setError(response.error); + callback(response); + }, + [shouldCall, callback], + ); + + useEffect(() => { + if (getSubscribedTopic) { + unsubscribeTopicRef.current = subscribeTopic(getSubscribedTopic()); + } + + return () => { + unsubscribeTopicRef.current?.(); + }; + }, [getSubscribedTopic]); + + useEffect(() => { + if (params == null) return; + if (shouldCall && !shouldCall()) return; + + const { unsubscribe } = subscribe({ + fetcher, + params, + callback: callbackFn, + options, + }); + + return () => { + unsubscribe(); + }; + }, [params, shouldCall]); + + return { + item, + origin, + isLoading: isLoading || (item == null && error == null), + error, + }; +} + +export default useLiveObject; diff --git a/src/v4/core/hooks/useSDK.ts b/src/v4/core/hooks/useSDK.ts new file mode 100644 index 000000000..42ecb62f2 --- /dev/null +++ b/src/v4/core/hooks/useSDK.ts @@ -0,0 +1,6 @@ +import { useContext } from 'react'; +import { SDKContext } from '~/v4/core/providers/SDKProvider'; + +export const useSDK = () => useContext(SDKContext); + +export default useSDK; diff --git a/src/v4/core/providers/AmityUIKitProvider.tsx b/src/v4/core/providers/AmityUIKitProvider.tsx index 7e8188e2b..ef46f8bde 100644 --- a/src/v4/core/providers/AmityUIKitProvider.tsx +++ b/src/v4/core/providers/AmityUIKitProvider.tsx @@ -5,10 +5,12 @@ import '../../styles/global.css'; import React, { useEffect, useMemo, useState } from 'react'; import useUser from '~/core/hooks/useUser'; -import SDKConnectorProvider from '~/core/providers/SDKConnectorProvider'; -import { SDKContext } from '~/core/providers/SDKProvider'; +import SDKConnectorProviderV3 from '~/core/providers/SDKConnectorProvider'; +import SDKConnectorProvider from '~/v4/core/providers/SDKConnectorProvider'; +import { SDKContext } from '~/v4/core/providers/SDKProvider'; +import { SDKContext as SDKContextV3 } from '~/core/providers/SDKProvider'; import PostRendererProvider from '~/social/providers/PostRendererProvider'; -import NavigationProvider from '~/social/providers/NavigationProvider'; +import NavigationProvider from './NavigationProvider'; import ConfigProvider from '~/social/providers/ConfigProvider'; import { ConfirmComponent } from '~/v4/core/components/ConfirmModal'; @@ -22,7 +24,7 @@ import { ThemeProvider as StyledThemeProvider } from 'styled-components'; import buildGlobalTheme from '~/core/providers/UiKitProvider/theme'; import { defaultConfig, Config, CustomizationProvider } from './CustomizationProvider'; import { ThemeProvider } from './ThemeProvider'; -import { PageBehaviorProvider } from './PageBehaviorProvider'; +import { PageBehavior, PageBehaviorProvider } from './PageBehaviorProvider'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { UIStyles } from '~/core/providers/UiKitProvider/styles'; import AmityUIKitManager from '../AmityUIKitManager'; @@ -58,10 +60,7 @@ interface AmityUIKitProviderProps { onEditUser?: (userId: string) => void; onMessageUser?: (userId: string) => void; }; - pageBehavior?: { - onCloseAction?: () => void; - onClickHyperLink?: () => void; - }; + pageBehavior?: PageBehavior; onConnectionStatusChange?: (state: Amity.SessionStates) => void; onConnected?: () => void; onDisconnected?: () => void; @@ -139,40 +138,44 @@ const AmityUIKitProvider: React.FC = ({ - - - - - - - - - - - - - {children} - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + {children} + + + + + + + + + + + + + + + + + + diff --git a/src/v4/core/providers/ConfirmProvider.tsx b/src/v4/core/providers/ConfirmProvider.tsx index b290db3c9..21a33772a 100644 --- a/src/v4/core/providers/ConfirmProvider.tsx +++ b/src/v4/core/providers/ConfirmProvider.tsx @@ -1,7 +1,7 @@ import React, { createContext, ReactNode, useContext, useState } from 'react'; import { PrimaryButton } from '~/core/components/Button/styles'; -type ConfirmType = { +export type ConfirmType = { onCancel?: () => void; onOk?: () => void; type?: 'confirm' | 'info'; @@ -11,8 +11,9 @@ type ConfirmType = { content?: ReactNode; okText?: ReactNode; cancelText?: ReactNode; - 'data-qa-anchor'?: string; - theme?: 'light' | 'dark'; + pageId?: string; + componentId?: string; + elementId?: string; }; interface ConfirmContextProps { @@ -24,8 +25,8 @@ interface ConfirmContextProps { export const ConfirmContext = createContext({ confirmData: null, - confirm: () => {}, - info: () => {}, + confirm: (data: ConfirmType) => {}, + info: (data: ConfirmType) => {}, closeConfirm: () => {}, }); diff --git a/src/v4/core/providers/CustomizationProvider.tsx b/src/v4/core/providers/CustomizationProvider.tsx index 82bc01bcb..29d18c066 100644 --- a/src/v4/core/providers/CustomizationProvider.tsx +++ b/src/v4/core/providers/CustomizationProvider.tsx @@ -92,7 +92,7 @@ export const useCustomization = () => { interface CustomizationProviderProps { children: React.ReactNode; - initialConfig: Config; + initialConfig: Config | undefined; } type IconConfiguration = { @@ -274,6 +274,7 @@ export const defaultConfig: DefaultConfig = { }, 'social_home_page/empty_newsfeed/create_community_button': { icon: 'createCommunityIcon', + text: 'Create Community', }, 'social_home_page/my_communities/community_avatar': {}, 'social_home_page/my_communities/community_display_name': {}, @@ -298,33 +299,6 @@ export const defaultConfig: DefaultConfig = { icon: 'badgeIcon', text: 'Moderator', }, - '*/post_content/moderator_badge': { - icon: 'badgeIcon', - text: 'Moderator', - theme: { - light: { - primary_color: '#FA4D30', - secondary_color: '#292B32', - }, - dark: { - primary_color: '#00FF00', - secondary_color: '#292B32', - }, - }, - }, - '*/post_comment/*': { - preferred_theme: 'default', - theme: { - light: { - primary_color: '#FFC0CB', - secondary_color: '#292B32', - }, - dark: { - primary_color: '#FFFF00', - secondary_color: '#292B32', - }, - }, - }, '*/post_content/timestamp': {}, '*/post_content/menu_button': { icon: 'menuIcon', @@ -408,7 +382,7 @@ export const getDefaultConfig: CustomizationContextValue['getConfig'] = (path: s export const CustomizationProvider: React.FC = ({ children, - initialConfig, + initialConfig = {}, }) => { const [config, setConfig] = useState(null); @@ -421,16 +395,6 @@ export const CustomizationProvider: React.FC = ({ }, [initialConfig]); const validateConfig = (config: Config): boolean => { - // Check if mandatory fields are present - if ( - !config?.preferred_theme || - !config?.theme || - !config?.excludes || - !config?.customizations - ) { - return false; - } - return true; }; @@ -439,11 +403,34 @@ export const CustomizationProvider: React.FC = ({ }; const isExcluded = (path: string) => { - if (!config) return false; - return !!config.excludes?.some((exclude) => { - const regex = new RegExp(`^${exclude.replace(/\*/g, '.*')}$`); - return regex.test(path); - }); + const [page, component, element] = path.split('/'); + + const customizationKeys = (() => { + if (element !== '*') { + return [ + `${page}/${component}/${element}`, + `${page}/*/${element}`, + `${page}/${component}/*`, + `${page}/*/*`, + `*/${component}/${element}`, + `*/*/${element}`, + `*/${component}/*`, + `*/*/*`, + ]; + } else if (component !== '*') { + return [`${page}/${component}/*`, `${page}/*/*`, `*/${component}/*`, `*/*/*`]; + } else if (page !== '*') { + return [`${page}/*/*`, `*/*/*`]; + } + + return []; + })(); + + return ( + config?.excludes?.some((excludedPath) => { + return customizationKeys.some((key) => key === excludedPath); + }) || false + ); }; const getConfig: CustomizationContextValue['getConfig'] = (path: string) => { @@ -481,6 +468,11 @@ export const CustomizationProvider: React.FC = ({ return config.customizations[key][prop]; } } + + if (prop === 'theme' && !!config?.theme) { + return config.theme; + } + for (const key of customizationKeys) { if (defaultConfig?.customizations?.[key]?.[prop]) { return defaultConfig.customizations[key][prop]; diff --git a/src/v4/core/providers/NavigationProvider.tsx b/src/v4/core/providers/NavigationProvider.tsx new file mode 100644 index 000000000..cc1c1e1ae --- /dev/null +++ b/src/v4/core/providers/NavigationProvider.tsx @@ -0,0 +1,425 @@ +import React, { createContext, useCallback, useContext, useState, useMemo, ReactNode } from 'react'; + +export enum PageTypes { + Explore = 'explore', + NewsFeed = 'newsFeed', + CommunityFeed = 'communityFeed', + CommunityEdit = 'communityEdit', + Category = 'category', + UserFeed = 'userFeed', + UserEdit = 'userEdit', + ViewStory = 'viewStory', + SocialHomePage = 'SocialHomePage', + PostDetailPage = 'PostDetailPage', + CommunityProfilePage = 'CommunityProfilePage', + UserProfilePage = 'UserProfilePage', +} + +type Page = + | { + type: PageTypes.Explore | PageTypes.NewsFeed; + context: { communityId?: string }; + } + | { + type: PageTypes.CommunityFeed; + context: { + communityId: string; + isNewCommunity: boolean; + }; + } + | { + type: PageTypes.CommunityEdit; + context: { + communityId: string; + tab: string; + }; + } + | { + type: PageTypes.Category; + context: { + categoryId: string; + communityId?: string; + }; + } + | { + type: PageTypes.UserFeed | PageTypes.UserEdit; + context: { + userId: string; + communityId?: string; + }; + } + | { + type: PageTypes.ViewStory; + context: { + storyId: string; + targetId?: string; + communityId?: string; + targetIds?: string[]; + storyType?: 'communityFeed' | 'globalFeed'; + }; + } + | { + type: PageTypes.PostDetailPage; + context: { + postId: string; + communityId?: string; + }; + } + | { type: PageTypes.CommunityProfilePage; context: { communityId: string } } + | { type: PageTypes.UserProfilePage; context: { userId: string; communityId?: string } } + | { type: PageTypes.SocialHomePage; context: { communityId?: string } }; + +type ContextValue = { + page: Page; + onChangePage: (type: string) => void; + onClickCategory: (categoryId: string) => void; + onClickCommunity: (communityId: string) => void; + onClickUser: (userId: string, pageType?: string) => void; + onClickStory: ( + storyId: string, + storyType: 'communityFeed' | 'globalFeed', + targetId?: string[], + ) => void; + onCommunityCreated: (communityId: string) => void; + onEditCommunity: (communityId: string, tab?: string) => void; + onEditUser: (userId: string) => void; + onMessageUser: (userId: string) => void; + onBack: () => void; + goToUserProfilePage: (userId: string) => void; + goToPostDetailPage: (postId: string) => void; + goToCommunityProfilePage: (communityId: string) => void; + setNavigationBlocker?: ( + params: + | { + title: ReactNode; + content: ReactNode; + okText: ReactNode; + } + | null + | undefined, + ) => void; +}; + +let defaultValue: ContextValue = { + page: { type: PageTypes.SocialHomePage, context: { communityId: undefined } }, + onChangePage: (type: string) => {}, + onClickCategory: (categoryId: string) => {}, + onClickCommunity: (communityId: string) => {}, + onClickUser: (userId: string) => {}, + onClickStory: ( + storyId: string, + storyType: 'communityFeed' | 'globalFeed', + targetId?: string[], + ) => {}, + onCommunityCreated: (communityId: string) => {}, + onEditCommunity: (communityId: string) => {}, + onEditUser: (userId: string) => {}, + onMessageUser: (userId: string) => {}, + goToUserProfilePage: (userId: string) => {}, + goToPostDetailPage: (postId: string) => {}, + goToCommunityProfilePage: (communityId: string) => {}, + setNavigationBlocker: () => {}, + onBack: () => {}, +}; + +if (process.env.NODE_ENV !== 'production') { + defaultValue = { + page: { type: PageTypes.SocialHomePage, context: { communityId: undefined } }, + onChangePage: (type) => console.log(`NavigationContext onChangePage(${type})`), + onClickCategory: (categoryId) => + console.log(`NavigationContext onClickCategory(${categoryId})`), + onClickCommunity: (communityId) => + console.log(`NavigationContext onClickCommunity(${communityId})`), + onClickUser: (userId) => console.log(`NavigationContext onClickUser(${userId})`), + onClickStory: (storyId, storyType, targetIds) => + console.log(`NavigationContext onClickStory(${storyId}, ${storyType}, ${targetIds})`), + onCommunityCreated: (communityId) => + console.log(`NavigationContext onCommunityCreated(${communityId})`), + onEditCommunity: (communityId) => + console.log(`NavigationContext onEditCommunity({${communityId})`), + onEditUser: (userId) => console.log(`NavigationContext onEditUser(${userId})`), + onMessageUser: (userId) => console.log(`NavigationContext onMessageUser(${userId})`), + onBack: () => console.log('NavigationContext onBack()'), + goToUserProfilePage: (userId) => + console.log(`NavigationContext goToUserProfilePage(${userId})`), + goToPostDetailPage: (postId) => console.log(`NavigationContext goToPostDetailPage(${postId})`), + goToCommunityProfilePage: (communityId) => + console.log(`NavigationContext goToCommunityProfilePage(${communityId})`), + }; +} + +export const NavigationContext = createContext(defaultValue); + +export const useNavigation = () => useContext(NavigationContext); + +interface NavigationProviderProps { + askForConfirmation?: (params: { + title: React.ReactNode; + content: React.ReactNode; + okText: React.ReactNode; + onSuccess: () => void; + onCancel: () => void; + }) => void; + children: React.ReactNode; + onChangePage?: (data: { type: string; [x: string]: string | boolean }) => void; + onClickCategory?: (categoryId: string) => void; + onClickCommunity?: (communityId: string) => void; + onClickUser?: (userId: string) => void; + onClickStory?: ( + storyId: string, + storyType: 'communityFeed' | 'globalFeed', + targetId?: string[], + ) => void; + onCommunityCreated?: (communityId: string) => void; + onEditCommunity?: (communityId: string, options?: { tab?: string }) => void; + onEditUser?: (userId: string) => void; + onMessageUser?: (userId: string) => void; + onBack?: () => void; +} + +export default function NavigationProvider({ + askForConfirmation, + children, + onChangePage: onChangePageProp, + onClickCategory, + onClickCommunity, + onClickUser, + onCommunityCreated, + onEditCommunity, + onEditUser, + onMessageUser, + onBack, +}: NavigationProviderProps) { + const [pages, setPages] = useState([ + { type: PageTypes.SocialHomePage, context: { communityId: undefined } }, + ]); + const currentPage = useMemo(() => pages[pages.length - 1], [pages]); + const [navigationBlocker, setNavigationBlocker] = useState< + | { + title: ReactNode; + content: ReactNode; + okText: ReactNode; + } + | null + | undefined + >(); + + const confirmation = askForConfirmation ?? confirm; + + const pushPage = useCallback(async (newPage) => { + setPages((prevState) => [...prevState, newPage]); + }, []); + + const popPage = () => { + setPages((prevState) => (prevState.length > 1 ? prevState.slice(0, -1) : prevState)); + }; + + const onChangePage = onChangePageProp + ? async (data: { type: string; [x: string]: string | boolean }) => { + onChangePageProp(data); + } + : null; + + const handleChangePage = useCallback( + (type) => { + pushPage({ type }); + }, + [pushPage], + ); + + const handleClickCommunity = useCallback( + (communityId) => { + const next = { + type: PageTypes.CommunityFeed, + communityId, + }; + + if (onChangePage) return onChangePage(next); + if (onClickCommunity) return onClickCommunity(communityId); + + pushPage(next); + }, + [onChangePage, onClickCommunity, pushPage], + ); + + const handleCommunityCreated = useCallback( + (communityId) => { + const next = { + type: PageTypes.CommunityFeed, + communityId, + isNewCommunity: true, + }; + + if (onChangePage) return onChangePage(next); + if (onCommunityCreated) return onCommunityCreated(communityId); + + pushPage(next); + }, + [onChangePage, onCommunityCreated, pushPage], + ); + + const handleClickCategory = useCallback( + (categoryId) => { + const next = { + type: PageTypes.Category, + categoryId, + }; + + if (onChangePage) return onChangePage(next); + if (onClickCategory) return onClickCategory(categoryId); + + pushPage(next); + }, + [onChangePage, onClickCategory, pushPage], + ); + + const handleClickUser = useCallback( + (userId, pageType) => { + const next = { + type: pageType ?? PageTypes.UserFeed, + userId, + }; + + if (onChangePage) return onChangePage(next); + if (onClickUser) return onClickUser(userId); + + pushPage(next); + }, + [onChangePage, onClickUser, pushPage], + ); + + const handleEditUser = useCallback( + (userId) => { + const next = { + type: PageTypes.UserEdit, + userId, + }; + + if (onChangePage) return onChangePage(next); + if (onEditUser) return onEditUser(userId); + + pushPage(next); + }, + [onChangePage, onEditUser, pushPage], + ); + + const handleEditCommunity = useCallback( + (communityId, tab) => { + const next = { + type: PageTypes.CommunityEdit, + communityId, + tab, + }; + + if (onChangePage) return onChangePage(next); + if (onEditCommunity) return onEditCommunity(communityId, { tab }); + + pushPage(next); + }, + [onChangePage, onEditCommunity, pushPage], + ); + + const handleMessageUser = useCallback( + (userId) => { + const next = { + type: 'conversation', + userId, + }; + + if (onChangePage) return onChangePage(next); + if (onMessageUser) return onMessageUser(userId); + }, + [onChangePage, onMessageUser], + ); + + const handleBack = useCallback(() => { + if (onBack) { + onBack(); + } + popPage(); + }, [onChangePage, onBack, popPage]); + + const handleClickStory = useCallback( + (targetId, storyType, targetIds) => { + const next = { + type: PageTypes.ViewStory, + targetId, + storyType, + targetIds, + }; + + if (onChangePage) return onChangePage(next); + + pushPage(next); + }, + [onChangePage, pushPage], + ); + + const goToUserProfilePage = useCallback( + (userId) => { + const next = { + type: PageTypes.UserProfilePage, + context: { + userId, + }, + }; + + pushPage(next); + }, + [onChangePage, pushPage], + ); + + const goToPostDetailPage = useCallback( + (postId) => { + const next = { + type: PageTypes.PostDetailPage, + context: { + postId, + }, + }; + + console.log('postId', postId); + + pushPage(next); + }, + [onChangePage, pushPage], + ); + + const goToCommunityProfilePage = useCallback( + (communityId) => { + const next = { + type: PageTypes.CommunityProfilePage, + context: { + communityId, + }, + }; + + pushPage(next); + }, + [onChangePage, pushPage], + ); + + return ( + + {children} + + ); +} diff --git a/src/v4/core/providers/PageBehaviorProvider.tsx b/src/v4/core/providers/PageBehaviorProvider.tsx index eeefde75f..daad33e25 100644 --- a/src/v4/core/providers/PageBehaviorProvider.tsx +++ b/src/v4/core/providers/PageBehaviorProvider.tsx @@ -1,46 +1,90 @@ -import React, { useMemo, useContext } from 'react'; -import { useNavigation } from '~/social/providers/NavigationProvider'; +import React, { useContext } from 'react'; +import { useNavigation } from '~/v4/core/providers/NavigationProvider'; -interface NavigationBehavior { - onCloseAction(): void; +export interface PageBehavior { + AmityDraftStoryPageBehavior: { + onCloseAction(): void; + }; onClickHyperLink(): void; -} - -interface PageBehavior { - navigationBehavior: NavigationBehavior; + AmitySocialHomePageBehavior: Record; + AmityGlobalFeedComponentBehavior: { + goToPostDetailPage: (context: { postId: string }) => void; + }; + AmityPostDetailPageBehavior: Record; + AmityPostContentComponentBehavior: { + goToCommunityProfilePage: (context: { communityId: string }) => void; + goToUserProfilePage: (context: { userId: string }) => void; + }; + AmitySocialGlobalSearchPageBehavior: Record; + AmityCommunitySearchResultComponentBehavior: { + goToCommunityProfilePage: (context: { communityId: string }) => void; + }; } const PageBehaviorContext = React.createContext(undefined); interface PageBehaviorProviderProps { children: React.ReactNode; - pageBehavior?: Partial; + pageBehavior?: Partial; } export const PageBehaviorProvider: React.FC = ({ children, pageBehavior = {}, }) => { - const { onBack } = useNavigation(); - const defaultNavigationBehavior: NavigationBehavior = { - onCloseAction: () => { - onBack(); + const { onBack, goToPostDetailPage, goToCommunityProfilePage, goToUserProfilePage } = + useNavigation(); + const navigationBehavior: PageBehavior = { + AmityDraftStoryPageBehavior: { + onCloseAction: () => { + if (pageBehavior?.AmityDraftStoryPageBehavior?.onCloseAction) { + return pageBehavior.AmityDraftStoryPageBehavior.onCloseAction(); + } + onBack(); + }, }, onClickHyperLink: () => {}, + AmitySocialHomePageBehavior: {}, + AmityGlobalFeedComponentBehavior: { + goToPostDetailPage: (context: { postId: string }) => { + if (pageBehavior?.AmityGlobalFeedComponentBehavior?.goToPostDetailPage) { + return pageBehavior?.AmityGlobalFeedComponentBehavior.goToPostDetailPage(context); + } + goToPostDetailPage(context.postId); + }, + }, + AmityPostDetailPageBehavior: {}, + AmityPostContentComponentBehavior: { + goToCommunityProfilePage: (context: { communityId: string }) => { + if (pageBehavior?.AmityPostContentComponentBehavior?.goToCommunityProfilePage) { + return pageBehavior.AmityPostContentComponentBehavior.goToCommunityProfilePage(context); + } + goToCommunityProfilePage(context.communityId); + }, + goToUserProfilePage: (context: { userId: string }) => { + if (pageBehavior?.AmityPostContentComponentBehavior?.goToUserProfilePage) { + return pageBehavior.AmityPostContentComponentBehavior.goToUserProfilePage(context); + } + goToUserProfilePage(context.userId); + }, + }, + AmitySocialGlobalSearchPageBehavior: {}, + AmityCommunitySearchResultComponentBehavior: { + goToCommunityProfilePage: (context: { communityId: string }) => { + if (pageBehavior?.AmityCommunitySearchResultComponentBehavior?.goToCommunityProfilePage) { + return pageBehavior.AmityCommunitySearchResultComponentBehavior.goToCommunityProfilePage( + context, + ); + } + goToCommunityProfilePage(context.communityId); + }, + }, }; - const pageBehaviorMemo = useMemo(() => { - const mergedNavigationBehavior: NavigationBehavior = { - ...defaultNavigationBehavior, - ...pageBehavior, - }; - return { - navigationBehavior: mergedNavigationBehavior, - }; - }, []); - return ( - {children} + + {children} + ); }; diff --git a/src/v4/core/providers/SDKConnectorProvider/SDKConnectorFetcherProvider.tsx b/src/v4/core/providers/SDKConnectorProvider/SDKConnectorFetcherProvider.tsx new file mode 100644 index 000000000..d543c862a --- /dev/null +++ b/src/v4/core/providers/SDKConnectorProvider/SDKConnectorFetcherProvider.tsx @@ -0,0 +1,96 @@ +import React, { createContext, useContext, useEffect, useRef } from 'react'; +const SDKConnectorFetcherContext = createContext({ + fetch: async ({ + fetchFn, + params, + }: { + fetchFn: (...args: TArgs[]) => Promise; + params: TArgs[]; + }) => { + const response = await Promise.resolve({} as TResponse); + return response; + }, +}); + +export const useSDKFetcherConnector = () => useContext(SDKConnectorFetcherContext); + +export default function SDKConnectorFetcherProvider({ children }: { children: React.ReactNode }) { + const fetcherPromiseArrayMap = useRef< + Record< + string, + Array<{ + resolve: (value: any) => void; + reject: (reason?: unknown) => void; + }> + > + >({}); + const fetcherResponse = useRef>({}); + + function getFetcherKey(fnName: string, params: Array) { + return `${fnName}.${JSON.stringify(params)}`; + } + + const fetch = ({ + fetchFn, + params, + }: { + fetchFn: (...args: TArgs[]) => Promise; + params: TArgs[]; + }) => { + const key = getFetcherKey(fetchFn.name, params); + + if (fetcherPromiseArrayMap.current[key]?.length > 0) { + if (fetcherResponse.current[key] != null) { + return Promise.resolve(fetcherResponse.current[key] as TResponse); + } + let resolveFn: (value: TResponse) => void = () => {}; + let rejectFn: (reason?: unknown) => void = () => {}; + const promise = new Promise((resolve, reject) => { + resolveFn = resolve; + rejectFn = reject; + }); + fetcherPromiseArrayMap.current[key].push({ + resolve: resolveFn, + reject: rejectFn, + }); + + return promise; + } + + let resolveFn: (value: TResponse) => void = () => {}; + let rejectFn: (reason?: unknown) => void = () => {}; + const promise = new Promise((resolve, reject) => { + resolveFn = resolve; + rejectFn = reject; + }); + fetcherPromiseArrayMap.current[key] = [ + { + resolve: resolveFn, + reject: rejectFn, + }, + ]; + + fetchFn(...params) + .then((response) => { + fetcherResponse.current[key] = response; + fetcherPromiseArrayMap.current[key].forEach(({ resolve }) => resolve(response)); + }) + .catch((error) => { + fetcherPromiseArrayMap.current[key].forEach(({ reject }) => reject(error)); + }) + .finally(() => { + setTimeout(() => { + fetcherResponse.current[key] = null; + fetcherPromiseArrayMap.current[key] = []; + }, 500); + }); + + return promise; + }; + + return ( + + {children} + + ); +} diff --git a/src/v4/core/providers/SDKConnectorProvider/SDKConnectorLiveCollectionProvider.tsx b/src/v4/core/providers/SDKConnectorProvider/SDKConnectorLiveCollectionProvider.tsx new file mode 100644 index 000000000..9fee6b430 --- /dev/null +++ b/src/v4/core/providers/SDKConnectorProvider/SDKConnectorLiveCollectionProvider.tsx @@ -0,0 +1,99 @@ +import React, { createContext, useContext, useEffect, useRef, useState } from 'react'; +import useSDK from '~/v4/core/hooks/useSDK'; +const SDKConnectorLiveCollectionContext = createContext({ + subscribe: ({ + fetcher, + params, + callback, + config, + }: { + fetcher: ( + params: Amity.LiveCollectionParams, + callback: Amity.LiveCollectionCallback, + config?: Amity.LiveCollectionConfig, + ) => Amity.Unsubscriber; + params: Amity.LiveCollectionParams; + callback: Amity.LiveCollectionCallback; + config?: Amity.LiveCollectionConfig; + }) => { + return { unsubscribe: () => {} }; + }, +}); + +export const useSDKLiveCollectionConnector = () => useContext(SDKConnectorLiveCollectionContext); + +export default function SDKConnectorLiveCollectionProvider({ + children, +}: { + children: React.ReactNode; +}) { + const subscriberMap = useRef>>>({}); + const unsubscribeFnMap = useRef void>>({}); + const responseMap = useRef>>({}); + const { currentUserId } = useSDK(); + + function getSubscriberKey(fnName: string, params: Amity.LiveCollectionParams) { + return `${currentUserId}.${fnName}.${JSON.stringify(params)}`; + } + + useEffect(() => { + return () => { + Object.values(unsubscribeFnMap.current).forEach((unsubscribeFn) => unsubscribeFn()); + }; + }, []); + + const subscribe = ({ + fetcher, + params, + callback, + config, + }: { + fetcher: ( + params: Amity.LiveCollectionParams, + callback: Amity.LiveCollectionCallback, + config?: Amity.LiveCollectionConfig, + ) => Amity.Unsubscriber; + params: Amity.LiveCollectionParams; + callback: Amity.LiveCollectionCallback; + config?: Amity.LiveCollectionConfig; + }) => { + if (currentUserId == null) return { unsubscribe() {} }; + const key = getSubscriberKey(fetcher.name, params); + + if (subscriberMap.current[key] && responseMap.current[key]) { + callback?.(responseMap.current[key] as Amity.LiveCollection); + subscriberMap.current[key].push(callback as Amity.LiveCollectionCallback); + } else { + subscriberMap.current[key] = [callback as Amity.LiveCollectionCallback]; + + const unsubscribeFn = fetcher( + params, + (response) => { + responseMap.current[key] = response; + const subscribers = subscriberMap.current[key]; + (subscribers || []).forEach((subscriber) => subscriber(response)); + }, + config, + ); + + unsubscribeFnMap.current[key] = unsubscribeFn; + } + + return { + unsubscribe() { + const callbackFn = subscriberMap.current[key].find((subscriber) => subscriber === callback); + if (callbackFn) { + subscriberMap.current[key] = subscriberMap.current[key].filter( + (subscriber) => subscriber !== callbackFn, + ); + } + }, + }; + }; + + return ( + + {children} + + ); +} diff --git a/src/v4/core/providers/SDKConnectorProvider/SDKConnectorLiveObjectProvider.tsx b/src/v4/core/providers/SDKConnectorProvider/SDKConnectorLiveObjectProvider.tsx new file mode 100644 index 000000000..3bb681722 --- /dev/null +++ b/src/v4/core/providers/SDKConnectorProvider/SDKConnectorLiveObjectProvider.tsx @@ -0,0 +1,99 @@ +import React, { createContext, useContext, useEffect, useRef, useState } from 'react'; +import useSDK from '~/v4/core/hooks/useSDK'; +const SDKConnectorLiveObjectContext = createContext({ + subscribe: ({ + fetcher, + params, + callback = () => {}, + options, + }: { + fetcher: ( + params: TParams, + callback: Amity.LiveObjectCallback, + options?: Amity.LiveObjectOptions, + ) => Amity.Unsubscriber; + params: TParams; + callback?: Amity.LiveObjectCallback; + options?: Amity.LiveObjectOptions; + }) => { + return { unsubscribe: () => {} }; + }, +}); + +export const useSDKLiveObjectConnector = () => useContext(SDKConnectorLiveObjectContext); + +export default function SDKConnectorLiveObjectProvider({ + children, +}: { + children: React.ReactNode; +}) { + const subscriberMap = useRef>>>({}); + const unsubscribeFnMap = useRef void>>({}); + const responseMap = useRef>>({}); + const { currentUserId } = useSDK(); + + function getSubscriberKey(fnName: string, params: TParams) { + return `${currentUserId}.${fnName}.${JSON.stringify(params)}`; + } + + useEffect(() => { + return () => { + Object.values(unsubscribeFnMap.current).forEach((unsubscribeFn) => unsubscribeFn()); + }; + }, []); + + const subscribe = ({ + fetcher, + params, + callback = () => {}, + options, + }: { + fetcher: ( + params: TParams, + callback: Amity.LiveObjectCallback, + options?: Amity.LiveObjectOptions, + ) => Amity.Unsubscriber; + params: TParams; + callback?: Amity.LiveObjectCallback; + options?: Amity.LiveObjectOptions; + }) => { + if (currentUserId == null) return { unsubscribe() {} }; + const key = getSubscriberKey(fetcher.name, params); + + if (subscriberMap.current[key]) { + callback(responseMap.current[key] as Amity.LiveObject); + subscriberMap.current[key].push(callback as Amity.LiveObjectCallback); + } else { + subscriberMap.current[key] = [callback as Amity.LiveObjectCallback]; + + const unsubscribeFn = fetcher( + params, + (response) => { + responseMap.current[key] = response; + const subscribers = subscriberMap.current[key]; + (subscribers || []).forEach((subscriber) => subscriber(response)); + }, + options, + ); + + unsubscribeFnMap.current[key] = unsubscribeFn; + } + + return { + unsubscribe() { + const callbackFn = subscriberMap.current[key].find((subscriber) => subscriber === callback); + if (callbackFn) { + subscriberMap.current[key] = subscriberMap.current[key].filter( + (subscriber) => subscriber !== callbackFn, + ); + } + }, + }; + }; + + return ( + + {children} + + ); +} diff --git a/src/v4/core/providers/SDKConnectorProvider/SDKConnectorSubscribersProvider.tsx b/src/v4/core/providers/SDKConnectorProvider/SDKConnectorSubscribersProvider.tsx new file mode 100644 index 000000000..869153066 --- /dev/null +++ b/src/v4/core/providers/SDKConnectorProvider/SDKConnectorSubscribersProvider.tsx @@ -0,0 +1,68 @@ +import { subscribeTopic } from '@amityco/ts-sdk'; +import React, { createContext, useContext, useEffect, useRef } from 'react'; +const SDKConnectorSubscribersContext = createContext({ + subscribe: ({ + topic, + callback, + }: { + topic: string; + callback: Amity.Listener; + }) => { + return { unsubscribe: () => {} }; + }, +}); + +export const useSDKSubscribersConnector = () => useContext(SDKConnectorSubscribersContext); + +export default function SDKConnectorSubscribersProvider({ + children, +}: { + children: React.ReactNode; +}) { + const callbackMap = useRef>({}); + const unsubscribeFnMap = useRef void>>({}); + + useEffect(() => { + return () => { + Object.values(unsubscribeFnMap.current).forEach((unsubscribeFn) => unsubscribeFn()); + }; + }, []); + + const subscribe = ({ + topic, + callback, + }: { + topic: string; + callback: Amity.Listener; + }) => { + if (callbackMap.current[topic]) { + callbackMap.current[topic].push(callback); + } else { + callbackMap.current[topic] = [callback]; + + const unsubscribeFn = subscribeTopic(topic, (...args) => { + const callbacks = callbackMap.current[topic]; + (callbacks || []).forEach((cb) => cb(...args)); + }); + + unsubscribeFnMap.current[topic] = unsubscribeFn; + } + + return { + unsubscribe() { + const callbackFn = callbackMap.current[topic].find((cb) => cb === callback); + if (callbackFn) { + callbackMap.current[topic] = callbackMap.current[topic].filter( + (subscriber) => subscriber !== callbackFn, + ); + } + }, + }; + }; + + return ( + + {children} + + ); +} diff --git a/src/v4/core/providers/SDKConnectorProvider/index.tsx b/src/v4/core/providers/SDKConnectorProvider/index.tsx new file mode 100644 index 000000000..6c6cbe406 --- /dev/null +++ b/src/v4/core/providers/SDKConnectorProvider/index.tsx @@ -0,0 +1,21 @@ +import React, { ReactNode } from 'react'; +import SDKConnectorLiveCollectionProvider from '~/v4/core/providers/SDKConnectorProvider/SDKConnectorLiveCollectionProvider'; +import SDKConnectorLiveObjectProvider from '~/v4/core/providers/SDKConnectorProvider/SDKConnectorLiveObjectProvider'; +import SDKConnectorFetcherProvider from '~/v4/core/providers/SDKConnectorProvider/SDKConnectorFetcherProvider'; +import SDKConnectorSubscribersProvider from '~/v4/core/providers/SDKConnectorProvider/SDKConnectorSubscribersProvider'; + +export { useSDKLiveCollectionConnector } from '~/v4/core/providers/SDKConnectorProvider/SDKConnectorLiveCollectionProvider'; +export { useSDKLiveObjectConnector } from '~/v4/core/providers/SDKConnectorProvider/SDKConnectorLiveObjectProvider'; +export { useSDKFetcherConnector } from '~/v4/core/providers/SDKConnectorProvider/SDKConnectorFetcherProvider'; + +export default function SDKConnectorProvider({ children }: { children: ReactNode }) { + return ( + + + + {children} + + + + ); +} diff --git a/src/v4/core/providers/SDKProvider.tsx b/src/v4/core/providers/SDKProvider.tsx new file mode 100644 index 000000000..c9e904b46 --- /dev/null +++ b/src/v4/core/providers/SDKProvider.tsx @@ -0,0 +1,11 @@ +import { createContext } from 'react'; + +export const SDKContext = createContext<{ + client?: Amity.Client | null; + currentUserId?: string | null; + userRoles: string[]; +}>({ + client: null, + currentUserId: undefined, + userRoles: [], +}); diff --git a/src/v4/core/providers/ThemeProvider.tsx b/src/v4/core/providers/ThemeProvider.tsx index 646b3966c..80f568bdb 100644 --- a/src/v4/core/providers/ThemeProvider.tsx +++ b/src/v4/core/providers/ThemeProvider.tsx @@ -7,6 +7,22 @@ const SHADE_PERCENTAGES = [0.25, 0.4, 0.5, 0.75]; const generateShades = (hexColor?: string, isDarkMode = false): string[] => { if (!hexColor) return Array(SHADE_PERCENTAGES.length).fill(''); + if (isDarkMode === true && hexColor === defaultConfig.theme.dark.primary_color) { + return ['#4a82f2', '#a0bdf8', '#d9e5fc', '#ffffff']; + } + + if (isDarkMode === false && hexColor === defaultConfig.theme.light.primary_color) { + return ['#4a82f2', '#a0bdf8', '#d9e5fc', '#ffffff']; + } + + if (isDarkMode === true && hexColor === defaultConfig.theme.dark.secondary_color) { + return ['#a5a9b5', '#6e7487', '#40434e', '#292b32']; + } + + if (isDarkMode === false && hexColor === defaultConfig.theme.light.secondary_color) { + return ['#636878', '#898e9e', '#a5a9b5', '#ebecef']; + } + const hslColor = parseToHsl(hexColor); const shades = SHADE_PERCENTAGES.map((percentage) => { @@ -78,7 +94,6 @@ export function useGenerateStylesShadeColors(inputConfig?: GetConfigReturnValue) const darkThemeConfig = inputThemeConfig?.dark || defaultConfig.theme.dark; const darkPrimary = generateShades(darkThemeConfig.primary_color, true); const darkSecondary = generateShades(darkThemeConfig.secondary_color, true); - return { '--asc-color-primary-default': darkThemeConfig.primary_color, '--asc-color-primary-shade1': darkPrimary[0], diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index 09ba86a42..8aae06fb8 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -41,7 +41,7 @@ type HyperLinkFormInputs = { const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStoryPageProps) => { const { page, onChangePage, onClickCommunity } = useNavigation(); const { file, setFile } = useStoryContext(); - const { navigationBehavior } = usePageBehavior(); + const { AmityDraftStoryPageBehavior } = usePageBehavior(); const [isHyperLinkBottomSheetOpen, setIsHyperLinkBottomSheetOpen] = useState(false); const { confirm } = useConfirmContext(); const notification = useNotifications(); @@ -140,7 +140,7 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor okText: formatMessage({ id: 'delete' }), onOk: () => { setFile(null); - navigationBehavior.onCloseAction(); + AmityDraftStoryPageBehavior.onCloseAction(); }, }); }; diff --git a/src/v4/styles/global.css b/src/v4/styles/global.css index 579508fd3..097e23ccb 100644 --- a/src/v4/styles/global.css +++ b/src/v4/styles/global.css @@ -1,6 +1,6 @@ @import url('../styles/typography.module.css'); -/* +/* The @supports rule is used to check if the browser supports the 'font-variation-settings' CSS property. If supported, it means the browser can handle variable fonts. In this case, if the browser supports 'font-variation-settings', we set the font-family to 'Inter var', which is the variable font version of Inter. @@ -122,3 +122,65 @@ 0px 2px 4px 0px rgba(40, 41, 61, 0.04); } +@media (prefers-color-scheme: light) { + :root { + --asc-color-alert: #fa4d30; + --asc-color-black: #000000; + --asc-color-white: #ffffff; + + --asc-color-primary-default: #1054de; + --asc-color-primary-shade1: #4a82f2; + --asc-color-primary-shade2: #a0bdf8; + --asc-color-primary-shade3: #d9e5fc; + --asc-color-primary-shade4: #ffffff; + + --asc-color-base-inverse: #000000; + + --asc-color-base-default: #292b32; + --asc-color-base-shade1: #636878; + --asc-color-base-shade2: #898e9e; + --asc-color-base-shade3: #a5a9b5; + --asc-color-base-shade4: #ebecef; + --asc-color-base-shade5: #f9f9fa; + + --asc-color-secondary-default: #292b32; + --asc-color-secondary-shade1: #636878; + --asc-color-secondary-shade2: #898e9e; + --asc-color-secondary-shade3: #a5a9b5; + --asc-color-secondary-shade4: #ebecef; + --asc-color-secondary-shade5: #f9f9fa; + + --asc-color-base-background: #ffffff; + } +} +@media (prefers-color-scheme: dark) { + :root { + --asc-color-alert: #fa4d30; + --asc-color-black: #000000; + --asc-color-white: #ffffff; + + --asc-color-primary-default: #1054de; + --asc-color-primary-shade1: #4a82f2; + --asc-color-primary-shade2: #a0bdf8; + --asc-color-primary-shade3: #d9e5fc; + --asc-color-primary-shade4: #ffffff; + + --asc-color-base-inverse: #ffffff; + + --asc-color-base-default: #ebecef; + --asc-color-base-shade1: #a5a9b5; + --asc-color-base-shade2: #6e7487; + --asc-color-base-shade3: #40434e; + --asc-color-base-shade4: #292b32; + --asc-color-base-shade5: #f9f9fa; + + --asc-color-secondary-default: #292b32; + --asc-color-secondary-shade1: #636878; + --asc-color-secondary-shade2: #898e9e; + --asc-color-secondary-shade3: #a5a9b5; + --asc-color-secondary-shade4: #ebecef; + --asc-color-secondary-shade5: #f9f9fa; + + --asc-color-base-background: #191919; + } +} From 2f0f1f21da819a15530ab5f7253e9fa44267d495 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan Date: Mon, 10 Jun 2024 16:20:55 +0700 Subject: [PATCH 103/300] fix: story tab show with member only (#383) --- src/social/components/CommunityInfo/UICommunityInfo.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/social/components/CommunityInfo/UICommunityInfo.tsx b/src/social/components/CommunityInfo/UICommunityInfo.tsx index eec09c233..9e62095c9 100644 --- a/src/social/components/CommunityInfo/UICommunityInfo.tsx +++ b/src/social/components/CommunityInfo/UICommunityInfo.tsx @@ -142,7 +142,9 @@ const UICommunityInfo = ({ )} - + {isJoined && !canEditCommunity && ( + + )} {isJoined && canEditCommunity && ( + ) : null} + {showDeletePostButton ? ( + + ) : null} +
+ ); +}; + +interface PostTitleProps { + post: Amity.Post; + pageId?: string; +} + +const PostTitle = ({ pageId, post }: PostTitleProps) => { + const componentId = 'post_content'; + const { getConfig } = useCustomization(); + const uiReference = `${pageId}/${componentId}/*`; + const config = getConfig(uiReference); + const themeStyles = useGenerateStylesShadeColors(config); + + const { community: targetCommunity } = useCommunity({ + communityId: post.targetId, + shouldCall: () => post.targetType === 'community', + }); + + const { user: postedUser } = useUser(post.postedUserId); + + if (targetCommunity) { + return ( +
+ + {postedUser?.displayName} + + {targetCommunity && ( + <> + + + {targetCommunity.displayName} + {' '} + + )} +
+ ); + } + + return ( + + {postedUser?.displayName} + + ); +}; + +const useMutateAddReaction = ({ + postId, + reactionByMe, +}: { + postId: string; + reactionByMe: string | null; +}) => + useMutation({ + mutationFn: async (reactionKey: string) => { + if (reactionByMe) { + try { + await ReactionRepository.removeReaction('post', postId, reactionByMe); + } catch { + console.log("Can't remove reaction."); + } + } + return ReactionRepository.addReaction('post', postId, reactionKey); + }, + }); + +const useMutateRemoveReaction = ({ + postId, + reactionsByMe, +}: { + postId: string; + reactionsByMe: string[]; +}) => + useMutation({ + mutationFn: async () => { + return Promise.all( + reactionsByMe.map((reaction) => { + try { + return ReactionRepository.removeReaction('post', postId, reaction); + } catch (e) { + console.log("Can't remove reaction."); + } + }), + ); + }, + }); + +const ChildrenPostContent = ({ + post, + onImageClick, + onVideoClick, +}: { + post: Amity.Post[]; + onImageClick: (imageIndex: number) => void; + onVideoClick: () => void; +}) => { + return ( + <> + + + + ); +}; + +interface PostContentProps { + pageId?: string; + post: Amity.Post; + type: 'feed' | 'detail'; + onClick?: () => void; +} + +export const PostContent = ({ + pageId = '*', + post: initialPost, + type, + onClick, +}: PostContentProps) => { + const componentId = 'post_content'; + const { themeStyles } = useAmityComponent({ + pageId, + componentId, + }); + + const { post: postData } = usePost(initialPost.postId); + + const post = postData || initialPost; + + const [isImageViewerOpen, setIsImageViewerOpen] = useState(false); + const [isVideoViewerOpen, setIsVideoViewerOpen] = useState(false); + const [clickedImageIndex, setClickedImageIndex] = useState(null); + const [showReactionList, setShowReactionList] = useState(false); + const [showMenu, setShowMenu] = useState(false); + + useEffect(() => { + if (post) { + post.analytics?.markAsViewed(); + } + }, [post]); + + const { community: targetCommunity } = useCommunity({ + communityId: post?.targetId, + shouldCall: () => post?.targetType === 'community', + }); + + const { isCommunityModerator } = usePostPermissions({ + post, + community: targetCommunity, + }); + + const reactionByMe = useMemo(() => { + if (post == null || post.myReactions?.length === 0) return null; + return post.myReactions[0]; + }, [post.myReactions]); + + const { mutateAsync: mutateAddReactionAsync } = useMutateAddReaction({ + postId: post.postId, + reactionByMe, + }); + + const { mutateAsync: mutateRemoveReactionAsync } = useMutateRemoveReaction({ + postId: post.postId, + reactionsByMe: post.myReactions, + }); + + const handleReactionClick = (reactionKey: string) => { + if (post.myReactions?.length > 0) { + mutateRemoveReactionAsync(); + } else { + mutateAddReactionAsync(reactionKey); + } + }; + + const openImageViewer = (imageIndex: number) => { + setIsImageViewerOpen(true); + setClickedImageIndex(imageIndex); + }; + + const closeImageViewer = () => { + setIsImageViewerOpen(false); + setClickedImageIndex(null); + }; + + const hasLike = post?.reactions.like > 0; + const hasLove = post?.reactions.love > 0; + const hasFire = post?.reactions.fire > 0; + const hasHappy = post?.reactions.happy > 0; + const hasCrying = post?.reactions.crying > 0; + + const hasReaction = hasLike || hasLove || hasFire || hasHappy || hasCrying; + + return ( + <> +
+
+
+ +
+
+
+ +
+
+ {!isCommunityModerator ? ( +
+ + + • + +
+ ) : null} + onClick?.()} + > + + +
+
+
+ {type === 'feed' ? ( + setShowMenu(true)} + /> + ) : null} +
+
+
+
+ + {post.children.length > 0 ? ( + setIsVideoViewerOpen(true)} + /> + ) : null} +
+ {type === 'detail' ? ( +
+
setShowReactionList(true)} + > + {hasReaction ? ( +
+ {hasCrying && ( + + )} + {hasHappy && ( + + )} + {hasFire && ( + + )} + {hasLove && ( + + )} + {hasLike && ( + + )} +
+ ) : null} + + {`${post?.reactionsCount || 0} ${post?.reactionsCount === 1 ? 'like' : 'likes'}`} + +
+ + + {`${post?.commentsCount || 0} ${ + post?.commentsCount === 1 ? 'comment' : 'comments' + }`} + +
+ ) : null} +
+
+
+ + +
+
+ +
+
+
+
+ {isImageViewerOpen && typeof clickedImageIndex === 'number' ? ( + + ) : null} + {isVideoViewerOpen ? ( + setIsVideoViewerOpen(false)} /> + ) : null} + setShowReactionList(false)} + > + + + setShowMenu(false)}> + setShowMenu(false)} /> + + + ); +}; diff --git a/src/v4/social/components/PostContent/PostContentSkeleton.module.css b/src/v4/social/components/PostContent/PostContentSkeleton.module.css new file mode 100644 index 000000000..bf8d440aa --- /dev/null +++ b/src/v4/social/components/PostContent/PostContentSkeleton.module.css @@ -0,0 +1,86 @@ +.postContentSkeleton { + display: flex; + flex-direction: column; + gap: 1rem; + height: 14rem; + background-color: var(--asc-color-base-background); +} + +.postContentSkeleton__bar { + display: grid; + grid-template-columns: min-content 1fr min-content; + align-items: center; + gap: 0.5rem; +} + +.postContentSkeleton__bar__information { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.postContentSkeleton__content { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.postContentSkeleton__animation { + animation: skeleton-pulse 1.5s ease-in-out infinite; +} + +.postContentSkeleton__bar__userAvatar { + width: 2.5rem; + height: 2.5rem; + border-radius: 50%; + background-color: var(--asc-color-base-shade4); +} + +.postContentSkeleton__bar__information__title { + border-radius: 0.75rem; + background-color: var(--asc-color-base-shade4); + width: 7.5rem; + height: 0.5rem; +} + +.postContentSkeleton__bar__information__subtitle { + border-radius: 0.75rem; + background-color: var(--asc-color-base-shade4); + width: 5.5rem; + height: 0.5rem; +} + +.postContentSkeleton__content__bar1 { + border-radius: 0.75rem; + background-color: var(--asc-color-base-shade4); + width: 15rem; + height: 0.5rem; +} + +.postContentSkeleton__content__bar2 { + border-radius: 0.18.5625rem; + background-color: var(--asc-color-base-shade4); + width: 18.5625rem; + height: 0.5rem; +} + +.postContentSkeleton__content__bar3 { + border-radius: 0; + background-color: var(--asc-color-base-shade4); + width: 11.25rem; + height: 0.5rem; +} + +@keyframes skeleton-pulse { + 0% { + opacity: 0.6; + } + + 50% { + opacity: 1; + } + + 100% { + opacity: 0.6; + } +} diff --git a/src/v4/social/components/PostContent/PostContentSkeleton.stories.tsx b/src/v4/social/components/PostContent/PostContentSkeleton.stories.tsx new file mode 100644 index 000000000..fb912430d --- /dev/null +++ b/src/v4/social/components/PostContent/PostContentSkeleton.stories.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import useOnePost from '~/mock/useOnePost'; + +import { PostContentSkeleton } from './PostContentSkeleton'; + +export default { + title: 'v4-social/components/PostContentSkeleton', +}; + +export const PostContentSkeletonStory = { + render: () => { + const [post] = useOnePost(); + + if (post == null) return null; + + return ; + }, + + name: 'PostContentSkeleton', +}; diff --git a/src/v4/social/components/PostContent/PostContentSkeleton.tsx b/src/v4/social/components/PostContent/PostContentSkeleton.tsx new file mode 100644 index 000000000..db149bcb4 --- /dev/null +++ b/src/v4/social/components/PostContent/PostContentSkeleton.tsx @@ -0,0 +1,75 @@ +import React from 'react'; +import clsx from 'clsx'; +import styles from './PostContentSkeleton.module.css'; +import { useAmityComponent } from '~/v4/core/hooks/uikit'; + +interface PostContentSkeletonProps { + pageId?: string; +} + +export const PostContentSkeleton = ({ pageId = '*' }: PostContentSkeletonProps) => { + const componentId = 'post_content'; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityComponent({ + pageId, + componentId, + }); + + return ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ); +}; diff --git a/src/v4/social/components/PostContent/TextContent/TextContent.module.css b/src/v4/social/components/PostContent/TextContent/TextContent.module.css new file mode 100644 index 000000000..a5e1ad3b7 --- /dev/null +++ b/src/v4/social/components/PostContent/TextContent/TextContent.module.css @@ -0,0 +1,15 @@ +.postContent { + overflow-wrap: break-word; + color: var(--asc-color-base-default); + white-space: pre-wrap; +} + +.mentionHighlightTag { + cursor: pointer; + color: var(--asc-color-primary-default); +} + +.postContent__readmore { + cursor: pointer; + color: var(--asc-color-primary-default); +} diff --git a/src/v4/social/components/PostContent/TextContent/TextContent.tsx b/src/v4/social/components/PostContent/TextContent/TextContent.tsx new file mode 100644 index 000000000..f3df4b392 --- /dev/null +++ b/src/v4/social/components/PostContent/TextContent/TextContent.tsx @@ -0,0 +1,69 @@ +import React, { useState, useMemo, ReactNode } from 'react'; + +import { Linkify } from '~/v4/social/internal-components/Linkify'; +import { Mentioned, findChunks, processChunks } from '~/v4/helpers/utils'; +import { Typography } from '~/v4/core/components'; +import styles from './TextContent.module.css'; + +interface MentionHighlightTagProps { + children: ReactNode; + mentionee: Mentioned; +} + +const MentionHighlightTag = ({ children }: MentionHighlightTagProps) => { + return {children}; +}; + +const MAX_TEXT_LENGTH = 500; + +interface TextContentProps { + text?: string; + mentionees?: Mentioned[]; +} + +export const TextContent = ({ text = '', mentionees }: TextContentProps) => { + const needReadMore = text.length > MAX_TEXT_LENGTH; + + const [isReadMoreClick, setIsReadMoreClick] = useState(false); + + const isShowReadMore = needReadMore && !isReadMoreClick; + + const chunks = useMemo(() => { + if (isShowReadMore) { + return processChunks(text.substring(0, MAX_TEXT_LENGTH), findChunks(mentionees)); + } + return processChunks(text, findChunks(mentionees)); + }, [mentionees, text, isShowReadMore]); + + if (!text) { + return null; + } + + return ( + + <> + {chunks.map((chunk) => { + const key = `${text}-${chunk.start}-${chunk.end}`; + const sub = text.substring(chunk.start, chunk.end); + if (chunk.highlight) { + const mentionee = mentionees?.find((m) => m.index === chunk.start); + if (mentionee) { + return ( + + {sub} + + ); + } + return {sub}; + } + return {sub}; + })} + + {isShowReadMore ? ( + setIsReadMoreClick(true)}> + ...Read more + + ) : null} + + ); +}; diff --git a/src/v4/social/components/PostContent/TextContent/index.tsx b/src/v4/social/components/PostContent/TextContent/index.tsx new file mode 100644 index 000000000..a576260f0 --- /dev/null +++ b/src/v4/social/components/PostContent/TextContent/index.tsx @@ -0,0 +1 @@ +export { TextContent } from './TextContent'; diff --git a/src/v4/social/components/PostContent/VideoContent/VideoContent.module.css b/src/v4/social/components/PostContent/VideoContent/VideoContent.module.css new file mode 100644 index 000000000..0a92b8917 --- /dev/null +++ b/src/v4/social/components/PostContent/VideoContent/VideoContent.module.css @@ -0,0 +1,103 @@ +.videoContent { + display: grid; + border-radius: 0.5rem; + gap: 0.25rem; + width: 100%; + overflow: hidden; +} + +.videoContent[data-videos-amount='1'] { + grid-template: 'video1' / minmax(0, 1fr); +} + +.videoContent[data-videos-amount='2'] { + grid-template: + 'video1 video2' 50% + 'video1 video2' 50% + / 50% 50%; +} + +.videoContent[data-videos-amount='3'] { + grid-template: + 'video1 video1 video1 video1' 66% + 'video2 video2 video3 video3' 33% + / 25% 25% 25% 25%; +} + +.videoContent[data-videos-amount='4'] { + grid-template: + 'video1 video1 video1 video1 video1 video1' 66% + 'video2 video2 video3 video3 video4 video4' 33% + / minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr); +} + +.videoContent__video { + object-fit: cover; + object-position: center; + height: 100%; + width: 100%; +} + +.videoContent__videoContainer { + cursor: pointer; + position: relative; +} + +.videoContent__videoContainer:nth-child(1) { + grid-area: video1; +} + +.videoContent__videoContainer:nth-child(2) { + grid-area: video2; +} + +.videoContent__videoContainer:nth-child(3) { + grid-area: video3; +} + +.videoContent__videoContainer:nth-child(4) { + grid-area: video4; +} + +.videoContent__videoCover { + position: absolute; + inset: 0; + border-radius: 0 0 0.5rem; + background-color: rgb(0 0 0 / 50%); + color: var(--asc-color-white); + text-align: center; + display: flex; + justify-content: center; + align-items: center; +} + +.videoContent__playButtonCover { + position: absolute; + inset: 0; + border-radius: 0 0 0.5rem; + text-align: center; + display: flex; + justify-content: center; + align-items: center; +} + +.videoContent__playButton { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + border-radius: 50%; + width: 2.5rem; + height: 2.5rem; + background-color: var(--asc-color-secondary-default); + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; +} + +.videoContent__playButton__svg { + width: 1.5rem; + height: 1.5rem; + fill: var(--asc-color-secondary-shade4); +} diff --git a/src/v4/social/components/PostContent/VideoContent/VideoContent.tsx b/src/v4/social/components/PostContent/VideoContent/VideoContent.tsx new file mode 100644 index 000000000..a59d6aa32 --- /dev/null +++ b/src/v4/social/components/PostContent/VideoContent/VideoContent.tsx @@ -0,0 +1,99 @@ +import React, { useMemo } from 'react'; +import useImage from '~/v4/core/hooks/useImage'; +import usePostByIds from '~/v4/core/hooks/usePostByIds'; +import { Typography } from '~/v4/core/components'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import styles from './VideoContent.module.css'; + +const PlayButtonSvg = (props: React.SVGProps) => ( + + + + + + + + + + +); + +interface VideoContentProps { + pageId?: string; + componentId?: string; + elementId?: string; + post: Amity.Post<'video'>; + onVideoClick: (index: number) => void; +} + +const Video = ({ fileId }: { fileId: string }) => { + const videoThumbnailUrl = useImage({ + fileId, + }); + + return {fileId}; +}; + +export const VideoContent = ({ + pageId = '*', + componentId = '*', + elementId = '*', + post, + onVideoClick, +}: VideoContentProps) => { + const { themeStyles } = useAmityElement({ + pageId, + componentId, + elementId, + }); + + const first4Videos = useMemo(() => post.children.slice(0, 4), [post.children]); + + const posts = usePostByIds(first4Videos); + + const videoPosts = posts.filter((post) => post.dataType === 'video'); + + const videoLeftCount = Math.max(0, post.children.length - 4); + + if (videoPosts.length === 0) { + return null; + } + + return ( +
+
+ {videoPosts.map((post, index) => ( +
onVideoClick(index)}> +
+ ))} +
+
+ ); +}; diff --git a/src/v4/social/components/PostContent/VideoContent/index.tsx b/src/v4/social/components/PostContent/VideoContent/index.tsx new file mode 100644 index 000000000..cd6e44415 --- /dev/null +++ b/src/v4/social/components/PostContent/VideoContent/index.tsx @@ -0,0 +1 @@ +export { VideoContent } from './VideoContent'; diff --git a/src/v4/social/components/PostContent/index.tsx b/src/v4/social/components/PostContent/index.tsx new file mode 100644 index 000000000..90e2e17f6 --- /dev/null +++ b/src/v4/social/components/PostContent/index.tsx @@ -0,0 +1,2 @@ +export { PostContent } from './PostContent'; +export { PostContentSkeleton } from './PostContentSkeleton'; diff --git a/src/v4/social/elements/ClearButton/ClearButton.module.css b/src/v4/social/elements/ClearButton/ClearButton.module.css new file mode 100644 index 000000000..b409458e9 --- /dev/null +++ b/src/v4/social/elements/ClearButton/ClearButton.module.css @@ -0,0 +1,8 @@ +.clearButton { + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + width: 1.5rem; + height: 1.5rem; +} diff --git a/src/v4/social/elements/ClearButton/ClearButton.stories.tsx b/src/v4/social/elements/ClearButton/ClearButton.stories.tsx new file mode 100644 index 000000000..42a0f70ab --- /dev/null +++ b/src/v4/social/elements/ClearButton/ClearButton.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { ClearButton } from './ClearButton'; + +export default { + title: 'v4-social/elements/ClearButton', +}; + +export const ClearButtonStory = { + render: () => { + return ; + }, + + name: 'ClearButton', +}; diff --git a/src/v4/social/elements/ClearButton/ClearButton.tsx b/src/v4/social/elements/ClearButton/ClearButton.tsx new file mode 100644 index 000000000..8149990de --- /dev/null +++ b/src/v4/social/elements/ClearButton/ClearButton.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import { IconComponent } from '~/v4/core/IconComponent'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; + +import styles from './ClearButton.module.css'; + +function ClearButtonSvg(props: React.SVGProps) { + return ( + + + + ); +} + +interface ClearButtonProps { + pageId?: string; + componentId?: string; + defaultClassName?: string; + imgClassName?: string; + onClick?: (e: React.MouseEvent) => void; +} + +export const ClearButton = ({ + pageId = '*', + componentId = '*', + defaultClassName, + imgClassName, + onClick = () => {}, +}: ClearButtonProps) => { + const elementId = 'clear_button'; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + return ( + + ); +}; diff --git a/src/v4/social/elements/ClearButton/index.tsx b/src/v4/social/elements/ClearButton/index.tsx new file mode 100644 index 000000000..f8bef72ad --- /dev/null +++ b/src/v4/social/elements/ClearButton/index.tsx @@ -0,0 +1 @@ +export { ClearButton } from './ClearButton'; diff --git a/src/v4/social/elements/CommentButton/CommentButton.module.css b/src/v4/social/elements/CommentButton/CommentButton.module.css index 1284a6a70..c0e03d3c4 100644 --- a/src/v4/social/elements/CommentButton/CommentButton.module.css +++ b/src/v4/social/elements/CommentButton/CommentButton.module.css @@ -1,27 +1,15 @@ -.uiCommentButton { - font-family: Inter, sans-serif; - font-weight: var(--asc-text-font-weight-bold); - color: var(--asc-color-white); +.commentButton { display: flex; align-items: center; - justify-content: space-between; - gap: var(--asc-spacing-xxs2); - border-radius: 1.5rem; - padding: var(--asc-spacing-s1) var(--asc-spacing-s2); - background-color: var(--asc-color-base-default); + justify-content: start; + gap: 0.25rem; cursor: pointer; - border: none; } -.uiRemoteImageButton { - width: var(--asc-spacing-m3); - height: var(--asc-spacing-m3); - cursor: pointer; - border: none; - outline: none; - padding: var(--asc-spacing-none); - margin: var(--asc-spacing-none); - display: flex; - align-items: center; - justify-content: center; +.commentButton__icon { + fill: var(--asc-color-base-shade2); +} + +.commentButton__text { + color: var(--asc-color-base-shade2); } diff --git a/src/v4/social/elements/CommentButton/CommentButton.stories.tsx b/src/v4/social/elements/CommentButton/CommentButton.stories.tsx new file mode 100644 index 000000000..8bc4fe5ca --- /dev/null +++ b/src/v4/social/elements/CommentButton/CommentButton.stories.tsx @@ -0,0 +1,19 @@ +import React from 'react'; + +import { CommentButton } from './CommentButton'; + +export default { + title: 'v4-social/elements/CommentButton', +}; + +export const CommentButtonStory = { + render: () => { + return ( +
+ +
+ ); + }, + + name: 'CommentButton', +}; diff --git a/src/v4/social/elements/CommentButton/CommentButton.tsx b/src/v4/social/elements/CommentButton/CommentButton.tsx index 447b1a543..06e48cccf 100644 --- a/src/v4/social/elements/CommentButton/CommentButton.tsx +++ b/src/v4/social/elements/CommentButton/CommentButton.tsx @@ -1,63 +1,60 @@ import React from 'react'; -import { Icon } from '~/v4/core/components'; -import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; -import { isValidHttpUrl } from '~/utils'; -import { useTheme } from 'styled-components'; +import clsx from 'clsx'; +import { Typography } from '~/v4/core/components'; +import { IconComponent } from '~/v4/core/IconComponent'; import styles from './CommentButton.module.css'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; -interface ReactButtonProps { - pageId: 'story_page'; - componentId: '*'; - onClick?: (e: React.MouseEvent) => void; - style?: React.CSSProperties; - children?: React.ReactNode; - 'data-qa-anchor'?: string; +const CommentSvg = (props: React.SVGProps) => ( + + + +); + +interface CommentButtonProps { + pageId?: string; + componentId?: string; + commentsCount?: number; + defaultIconClassName?: string; + imgIconClassName?: string; } -export const CommentButton = ({ - pageId = 'story_page', +export function CommentButton({ + pageId = '*', componentId = '*', - onClick = () => {}, - style, - children, - ...props -}: ReactButtonProps) => { - const theme = useTheme(); - const { getConfig, isExcluded } = useCustomization(); - const elementConfig = getConfig(`${pageId}/*/story_comment_button`); - const isElementExcluded = isExcluded(`${pageId}/*/story_comment_button`); - const backgroundColor = elementConfig?.background_color; - const commentIcon = elementConfig?.comment_icon; - - if (isElementExcluded) return null; + commentsCount, + defaultIconClassName, + imgIconClassName, +}: CommentButtonProps) { + const elementId = 'comment_button'; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference } = useAmityElement({ + pageId, + componentId, + elementId, + }); - const isRemoteImage = commentIcon && isValidHttpUrl(commentIcon); + if (isExcluded) return null; - return isRemoteImage ? ( - - ) : ( - + return ( +
+ ( + + )} + imgIcon={() => {uiReference}} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} + /> + + {typeof commentsCount === 'number' ? commentsCount : config.text} + +
); -}; +} diff --git a/src/v4/social/elements/CommentButton/index.ts b/src/v4/social/elements/CommentButton/index.tsx similarity index 100% rename from src/v4/social/elements/CommentButton/index.ts rename to src/v4/social/elements/CommentButton/index.tsx diff --git a/src/v4/social/elements/MenuButton/MenuButton.module.css b/src/v4/social/elements/MenuButton/MenuButton.module.css new file mode 100644 index 000000000..2aed04dc5 --- /dev/null +++ b/src/v4/social/elements/MenuButton/MenuButton.module.css @@ -0,0 +1,3 @@ +.menuButton { + fill: var(--asc-color-base-default); +} diff --git a/src/v4/social/elements/MenuButton/MenuButton.stories.tsx b/src/v4/social/elements/MenuButton/MenuButton.stories.tsx new file mode 100644 index 000000000..d95e59df1 --- /dev/null +++ b/src/v4/social/elements/MenuButton/MenuButton.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { MenuButton } from './MenuButton'; + +export default { + title: 'v4-social/elements/MenuButton', +}; + +export const MenuButtonStory = { + render: () => { + return ; + }, + + name: 'MenuButton', +}; diff --git a/src/v4/social/elements/MenuButton/MenuButton.tsx b/src/v4/social/elements/MenuButton/MenuButton.tsx new file mode 100644 index 000000000..605a08409 --- /dev/null +++ b/src/v4/social/elements/MenuButton/MenuButton.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { IconComponent } from '~/v4/core/IconComponent'; +import styles from './MenuButton.module.css'; + +const EllipsisH = ({ ...props }: React.SVGProps) => ( + + + +); + +export interface MenuButtonProps { + pageId?: string; + componentId?: string; + onClick?: () => void; +} + +export function MenuButton({ pageId = '*', componentId = '*', onClick }: MenuButtonProps) { + const elementId = 'menu_button'; + const { isExcluded, accessibilityId, themeStyles, config, defaultConfig, uiReference } = + useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + return ( +
+ } + imgIcon={() => {uiReference}} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} + /> +
+ ); +} diff --git a/src/v4/social/elements/MenuButton/index.tsx b/src/v4/social/elements/MenuButton/index.tsx new file mode 100644 index 000000000..8e515ddf3 --- /dev/null +++ b/src/v4/social/elements/MenuButton/index.tsx @@ -0,0 +1 @@ +export { MenuButton } from './MenuButton'; diff --git a/src/v4/social/elements/ModeratorBadge/ModeratorBadge.module.css b/src/v4/social/elements/ModeratorBadge/ModeratorBadge.module.css new file mode 100644 index 000000000..752a3b660 --- /dev/null +++ b/src/v4/social/elements/ModeratorBadge/ModeratorBadge.module.css @@ -0,0 +1,28 @@ +.moderatorBadge { + display: flex; + padding: 0 0.375rem 0 0.25rem; + justify-content: center; + align-items: center; + gap: 0.0625rem; + background-color: var(--asc-color-primary-shade3); + border-radius: 1.25rem; +} + +.moderatorBadge__icon { + display: flex; + justify-content: center; + align-items: center; + height: 0.5625rem; + width: 0.75rem; + fill: var(--asc-color-primary-default); +} + +.moderatorBadge__text { + color: var(--asc-color-primary-default); + text-align: center; + font-size: 0.625rem; + font-style: normal; + font-weight: 400; + line-height: 1.125rem; + letter-spacing: -0.0063rem; +} diff --git a/src/v4/social/elements/ModeratorBadge/ModeratorBadge.stories.tsx b/src/v4/social/elements/ModeratorBadge/ModeratorBadge.stories.tsx new file mode 100644 index 000000000..8e96d2a12 --- /dev/null +++ b/src/v4/social/elements/ModeratorBadge/ModeratorBadge.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { ModeratorBadge } from './ModeratorBadge'; + +export default { + title: 'v4-social/elements/ModeratorBadge', +}; + +export const ModeratorBadgeStory = { + render: () => { + return ; + }, + + name: 'ModeratorBadge', +}; diff --git a/src/v4/social/elements/ModeratorBadge/ModeratorBadge.tsx b/src/v4/social/elements/ModeratorBadge/ModeratorBadge.tsx new file mode 100644 index 000000000..76334e432 --- /dev/null +++ b/src/v4/social/elements/ModeratorBadge/ModeratorBadge.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import styles from './ModeratorBadge.module.css'; +import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; +import { useGenerateStylesShadeColors } from '~/v4/core/providers/ThemeProvider'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; + +const Badge = (props: React.SVGProps) => { + return ( + + + + ); +}; + +interface ModeratorBadgeProps { + pageId?: string; + componentId?: string; +} + +export function ModeratorBadge({ pageId = '*', componentId = '*' }: ModeratorBadgeProps) { + const elementId = 'moderator_badge'; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + return ( +
+ +
{config.text}
+
+ ); +} diff --git a/src/v4/social/elements/ModeratorBadge/index.tsx b/src/v4/social/elements/ModeratorBadge/index.tsx new file mode 100644 index 000000000..a357a5bc3 --- /dev/null +++ b/src/v4/social/elements/ModeratorBadge/index.tsx @@ -0,0 +1 @@ +export { ModeratorBadge } from './ModeratorBadge'; diff --git a/src/v4/social/elements/ReactionButton/Crying.tsx b/src/v4/social/elements/ReactionButton/Crying.tsx new file mode 100644 index 000000000..72597127f --- /dev/null +++ b/src/v4/social/elements/ReactionButton/Crying.tsx @@ -0,0 +1,42 @@ +import React from 'react'; + +const Crying = ({ ...props }: React.SVGProps) => ( + + + + + + + + + + + + + + +); + +export default Crying; diff --git a/src/v4/social/elements/ReactionButton/Fire.tsx b/src/v4/social/elements/ReactionButton/Fire.tsx new file mode 100644 index 000000000..a93727cd2 --- /dev/null +++ b/src/v4/social/elements/ReactionButton/Fire.tsx @@ -0,0 +1,54 @@ +import React from 'react'; + +const Fire = ({ ...props }: React.SVGProps) => ( + + + + + + + + + + + + + + + + + + + +); + +export default Fire; diff --git a/src/v4/social/elements/ReactionButton/Happy.tsx b/src/v4/social/elements/ReactionButton/Happy.tsx new file mode 100644 index 000000000..9ff7a58cf --- /dev/null +++ b/src/v4/social/elements/ReactionButton/Happy.tsx @@ -0,0 +1,47 @@ +import React from 'react'; + +const Happy = ({ ...props }: React.SVGProps) => ( + + + + + + + + + + + + + + +); + +export default Happy; diff --git a/src/v4/social/elements/ReactionButton/Like.tsx b/src/v4/social/elements/ReactionButton/Like.tsx new file mode 100644 index 000000000..7bbb9fa9f --- /dev/null +++ b/src/v4/social/elements/ReactionButton/Like.tsx @@ -0,0 +1,33 @@ +import React from 'react'; + +const Like = ({ ...props }: React.SVGProps) => ( + + + + + + + + + + +); + +export default Like; diff --git a/src/v4/social/elements/ReactionButton/Love.tsx b/src/v4/social/elements/ReactionButton/Love.tsx new file mode 100644 index 000000000..83b621c52 --- /dev/null +++ b/src/v4/social/elements/ReactionButton/Love.tsx @@ -0,0 +1,36 @@ +import React from 'react'; + +const Love = ({ ...props }: React.SVGProps) => ( + + + + + + + + + + + + +); + +export default Love; diff --git a/src/v4/social/elements/ReactionButton/ReactionButton.module.css b/src/v4/social/elements/ReactionButton/ReactionButton.module.css new file mode 100644 index 000000000..a4516b4ba --- /dev/null +++ b/src/v4/social/elements/ReactionButton/ReactionButton.module.css @@ -0,0 +1,114 @@ +.reactButton { + display: flex; + align-items: center; + justify-content: start; + gap: 0.25rem; + position: relative; + cursor: pointer; + -webkit-touch-callout: none; + user-select: none; + touch-action: none; +} + +.reactButton * { + -webkit-touch-callout: none; + user-select: none; +} + +.reactButton__icon { + width: 1.25rem; + height: 1.25rem; +} + +.reactButton__icon[data-has-my-reaction='false'] { + fill: var(--asc-color-base-shade2); +} + +.reactButton__reactionsText { + color: var(--asc-color-base-shade2); +} + +.reactButton__reactionsText[data-has-my-reaction='true'] { + color: var(--asc-color-primary-default); + text-transform: capitalize; +} + +.reactButton__panel { + position: absolute; + + /* bottom: calc(0.25rem + 0.44rem); */ + top: 0; + left: 0; + transform: translateY(calc(-100% - 0.81rem)); + display: flex; + justify-content: center; + align-items: center; + gap: 0.25rem; + padding: 0.25rem 0.375rem; + border-radius: 1.75rem; + background-color: var(--asc-color-base-background); + box-shadow: var(--asc-box-shadow-03); +} + +.reactButton__panel__reaction { + padding: 0.25rem 0.44rem; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + position: relative; +} + +.reactButton__panel__reaction__text { + display: none; + padding: 0 0.375rem; + align-items: center; + gap: 0.0625rem; + border-radius: 1.25rem; + background: rgb(0 0 0 / 50%); + color: var(--asc-color-white); + text-align: center; + font-size: 0.625rem; + font-style: normal; + font-weight: 400; + line-height: 1.125rem; /* 180% */ + letter-spacing: -0.0063rem; + position: absolute; + top: 0; + transform: translateY(-100%); +} + +.reactButton__panel__reaction:hover > .reactButton__panel__reaction__text, +.reactButton__panel__reaction[data-touch-over='true'] > .reactButton__panel__reaction__text { + display: flex; +} + +.reactButton__panel__reaction:hover, +.reactButton__panel__reaction[data-touch-over='true'] { + animation: reaction-scale 250ms ease-out; + scale: 1.5; +} + +.reactButton__panel__reaction[data-active='true'] { + animation: reaction-click 250ms ease-out; +} + +@keyframes reaction-scale { + from { + scale: 1; + } + + to { + scale: 1.5; + } +} + +@keyframes reaction-click { + from { + scale: 1.5; + } + + to { + scale: 2; + } +} diff --git a/src/v4/social/elements/ReactionButton/ReactionButton.stories.tsx b/src/v4/social/elements/ReactionButton/ReactionButton.stories.tsx new file mode 100644 index 000000000..7a1a92e73 --- /dev/null +++ b/src/v4/social/elements/ReactionButton/ReactionButton.stories.tsx @@ -0,0 +1,19 @@ +import React from 'react'; + +import { ReactionButton } from './ReactionButton'; + +export default { + title: 'v4-social/elements/ReactionButton', +}; + +export const ReactionButtonStory = { + render: () => { + return ( +
+ {}} reactionsCount={0} /> +
+ ); + }, + + name: 'ReactionButton', +}; diff --git a/src/v4/social/elements/ReactionButton/ReactionButton.tsx b/src/v4/social/elements/ReactionButton/ReactionButton.tsx new file mode 100644 index 000000000..8416f47cf --- /dev/null +++ b/src/v4/social/elements/ReactionButton/ReactionButton.tsx @@ -0,0 +1,331 @@ +import React, { useEffect, useRef, useState } from 'react'; +import clsx from 'clsx'; +import { Typography } from '~/v4/core/components'; +import { IconComponent } from '~/v4/core/IconComponent'; +import Crying from '~/v4/social/elements/ReactionButton/Crying'; +import Fire from '~/v4/social/elements/ReactionButton/Fire'; +import Happy from '~/v4/social/elements/ReactionButton/Happy'; +import Like from '~/v4/social/elements/ReactionButton/Like'; +import Love from '~/v4/social/elements/ReactionButton/Love'; +import styles from './ReactionButton.module.css'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; + +const LikeSvg = (props: React.SVGProps) => ( + + + +); + +interface ReactionButtonProps { + pageId?: string; + componentId?: string; + myReactions: string[]; + reactionsCount?: number; + defaultIconClassName?: string; + imgIconClassName?: string; + onReactionClick: (reactionKey: string) => void; +} + +const MOUSE_DURATION = 250; + +export function ReactionButton({ + pageId = '*', + componentId = '*', + myReactions, + reactionsCount, + defaultIconClassName, + imgIconClassName, + onReactionClick, +}: ReactionButtonProps) { + const elementId = 'reaction_button'; + const { isExcluded, accessibilityId, config, defaultConfig, uiReference, themeStyles } = + useAmityElement({ + pageId, + componentId, + elementId, + }); + + const clickTimerRef = useRef(0); + const touchTimerRef = useRef(null); + const [isShowReactionPanel, setIsShowReactionPanel] = useState(false); + + const likeRef = useRef(null); + const loveRef = useRef(null); + const fireRef = useRef(null); + const happyRef = useRef(null); + const cryingRef = useRef(null); + + const myReaction = myReactions && myReactions.length > 0 ? myReactions[0] : null; + const hasMyReaction = myReaction != null; + + const [selectedReaction, setSelectedReaction] = useState(null); + const [activeReaction, setActiveReaction] = useState(null); + + useEffect(() => { + if (selectedReaction) { + setTimeout(() => { + setSelectedReaction(null); + setIsShowReactionPanel(false); + }, 250); + } + }, [selectedReaction]); + + const hideReactionPanel = (ev: MouseEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + setIsShowReactionPanel(false); + }; + + useEffect(() => { + if (isShowReactionPanel) { + window.addEventListener('click', hideReactionPanel); + } else { + window.removeEventListener('click', hideReactionPanel); + } + }, [isShowReactionPanel]); + + if (isExcluded) return null; + + const renderMyReaction = () => { + switch (myReaction) { + case 'like': + return ; + case 'love': + return ; + case 'fire': + return ; + case 'happy': + return ; + case 'crying': + return ; + default: + return null; + } + }; + + return ( +
{ + ev.preventDefault(); + ev.stopPropagation(); + + clickTimerRef.current = Date.now(); + + touchTimerRef.current = setTimeout(() => { + setIsShowReactionPanel(true); + }, MOUSE_DURATION); + }} + onTouchStart={(ev) => { + ev.preventDefault(); + ev.stopPropagation(); + + clickTimerRef.current = Date.now(); + + touchTimerRef.current = setTimeout(() => { + setIsShowReactionPanel(true); + }, MOUSE_DURATION); + }} + onMouseMove={(ev) => { + ev.preventDefault(); + ev.stopPropagation(); + + if ( + likeRef.current && + likeRef.current.offsetLeft < ev.clientX && + ev.clientX < likeRef.current.offsetLeft + likeRef.current.clientWidth + ) { + setActiveReaction('like'); + return; + } + if ( + loveRef.current && + loveRef.current.offsetLeft < ev.clientX && + ev.clientX < loveRef.current.offsetLeft + loveRef.current.clientWidth + ) { + setActiveReaction('love'); + return; + } + if ( + fireRef.current && + fireRef.current.offsetLeft < ev.clientX && + ev.clientX < fireRef.current.offsetLeft + fireRef.current.clientWidth + ) { + setActiveReaction('fire'); + return; + } + if ( + happyRef.current && + happyRef.current.offsetLeft < ev.clientX && + ev.clientX < happyRef.current.offsetLeft + happyRef.current.clientWidth + ) { + setActiveReaction('happy'); + return; + } + if ( + cryingRef.current && + cryingRef.current.offsetLeft < ev.clientX && + ev.clientX < cryingRef.current.offsetLeft + cryingRef.current.clientWidth + ) { + setActiveReaction('crying'); + return; + } + + setActiveReaction(null); + }} + onTouchMove={(ev) => { + ev.preventDefault(); + ev.stopPropagation(); + + if ( + likeRef.current && + likeRef.current.offsetLeft < ev.touches[0].clientX && + ev.touches[0].clientX < likeRef.current.offsetLeft + likeRef.current.clientWidth + ) { + setActiveReaction('like'); + return; + } + if ( + loveRef.current && + loveRef.current.offsetLeft < ev.touches[0].clientX && + ev.touches[0].clientX < loveRef.current.offsetLeft + loveRef.current.clientWidth + ) { + setActiveReaction('love'); + return; + } + if ( + fireRef.current && + fireRef.current.offsetLeft < ev.touches[0].clientX && + ev.touches[0].clientX < fireRef.current.offsetLeft + fireRef.current.clientWidth + ) { + setActiveReaction('fire'); + return; + } + if ( + happyRef.current && + happyRef.current.offsetLeft < ev.touches[0].clientX && + ev.touches[0].clientX < happyRef.current.offsetLeft + happyRef.current.clientWidth + ) { + setActiveReaction('happy'); + return; + } + if ( + cryingRef.current && + cryingRef.current.offsetLeft < ev.touches[0].clientX && + ev.touches[0].clientX < cryingRef.current.offsetLeft + cryingRef.current.clientWidth + ) { + setActiveReaction('crying'); + return; + } + + setActiveReaction(null); + }} + onTouchEnd={(ev) => { + touchTimerRef.current && clearTimeout(touchTimerRef.current); + touchTimerRef.current = null; + setIsShowReactionPanel(false); + if (activeReaction) { + setSelectedReaction(activeReaction); + onReactionClick(activeReaction); + setActiveReaction(null); + } else { + setSelectedReaction('like'); + onReactionClick('like'); + } + }} + onMouseUp={(ev) => { + touchTimerRef.current && clearTimeout(touchTimerRef.current); + touchTimerRef.current = null; + setIsShowReactionPanel(false); + if (activeReaction) { + setSelectedReaction(activeReaction); + onReactionClick(activeReaction); + setActiveReaction(null); + } else { + setSelectedReaction('like'); + onReactionClick('like'); + } + }} + > + {myReaction ? ( + renderMyReaction() + ) : ( + ( + + )} + imgIcon={() => {uiReference}} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} + /> + )} + + {typeof reactionsCount === 'number' ? reactionsCount : myReaction || config.text} + + {isShowReactionPanel ? ( +
+
+
Like
+ +
+
+
Love
+ +
+
+
Fire
+ +
+
+
Happy
+ +
+
+
Crying
+ +
+
+ ) : null} +
+ ); +} diff --git a/src/v4/social/elements/ReactionButton/index.tsx b/src/v4/social/elements/ReactionButton/index.tsx new file mode 100644 index 000000000..158a510c2 --- /dev/null +++ b/src/v4/social/elements/ReactionButton/index.tsx @@ -0,0 +1 @@ +export { ReactionButton } from './ReactionButton'; diff --git a/src/v4/social/elements/ShareButton/ShareButton.module.css b/src/v4/social/elements/ShareButton/ShareButton.module.css new file mode 100644 index 000000000..ebbc14ce7 --- /dev/null +++ b/src/v4/social/elements/ShareButton/ShareButton.module.css @@ -0,0 +1,15 @@ +.shareButton { + display: flex; + justify-content: center; + align-items: center; + gap: 0.25rem; + cursor: pointer; +} + +.shareButton__icon { + fill: var(--asc-color-base-shade2); +} + +.shareButton__text { + color: var(--asc-color-base-shade2); +} diff --git a/src/v4/social/elements/ShareButton/ShareButton.stories.tsx b/src/v4/social/elements/ShareButton/ShareButton.stories.tsx new file mode 100644 index 000000000..c273a2ed9 --- /dev/null +++ b/src/v4/social/elements/ShareButton/ShareButton.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { ShareButton } from './ShareButton'; + +export default { + title: 'v4-social/elements/ShareButton', +}; + +export const ShareButtonStory = { + render: () => { + return ; + }, + + name: 'ShareButton', +}; diff --git a/src/v4/social/elements/ShareButton/ShareButton.tsx b/src/v4/social/elements/ShareButton/ShareButton.tsx new file mode 100644 index 000000000..0bd8d15d7 --- /dev/null +++ b/src/v4/social/elements/ShareButton/ShareButton.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import clsx from 'clsx'; +import { Typography } from '~/v4/core/components'; +import { IconComponent } from '~/v4/core/IconComponent'; +import styles from './ShareButton.module.css'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; + +const ShareSvg = ({ ...props }: React.SVGProps) => ( + + + +); + +export interface ShareButtonProps { + pageId?: string; + componentId?: string; + defaultIconClassName?: string; + imgIconClassName?: string; +} + +export function ShareButton({ + pageId = '*', + componentId = '*', + defaultIconClassName, + imgIconClassName, +}: ShareButtonProps) { + const elementId = 'share_button'; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + // return ( + //
+ // ( + // + // )} + // imgIcon={() => {uiReference}} + // defaultIconName={defaultConfig.icon} + // configIconName={config.icon} + // /> + // {config.text} + //
+ // ); + return null; +} diff --git a/src/v4/social/elements/ShareButton/index.tsx b/src/v4/social/elements/ShareButton/index.tsx new file mode 100644 index 000000000..64ddefe86 --- /dev/null +++ b/src/v4/social/elements/ShareButton/index.tsx @@ -0,0 +1 @@ +export { ShareButton } from './ShareButton'; diff --git a/src/v4/social/elements/ShareButton/styles.module.css b/src/v4/social/elements/ShareButton/styles.module.css new file mode 100644 index 000000000..6ee263f80 --- /dev/null +++ b/src/v4/social/elements/ShareButton/styles.module.css @@ -0,0 +1,14 @@ +.shareButton { + display: flex; + justify-content: center; + align-items: center; + gap: 0.25rem; +} + +.shareButton__icon { + fill: var(--asc-color-secondary-shade4); +} + +.shareButton__text { + color: var(--asc-color-base-shade2); +} diff --git a/src/v4/social/elements/StoryCommentButton/StoryCommentButton.module.css b/src/v4/social/elements/StoryCommentButton/StoryCommentButton.module.css new file mode 100644 index 000000000..27a945877 --- /dev/null +++ b/src/v4/social/elements/StoryCommentButton/StoryCommentButton.module.css @@ -0,0 +1,27 @@ +.uiStoryCommentButton { + font-family: Inter, sans-serif; + font-weight: var(--asc-text-font-weight-bold); + color: var(--asc-color-white); + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--asc-spacing-xxs2); + border-radius: 1.5rem; + padding: var(--asc-spacing-s1) var(--asc-spacing-s2); + background-color: var(--asc-color-base-default); + cursor: pointer; + border: none; +} + +.uiRemoteImageButton { + width: var(--asc-spacing-m3); + height: var(--asc-spacing-m3); + cursor: pointer; + border: none; + outline: none; + padding: var(--asc-spacing-none); + margin: var(--asc-spacing-none); + display: flex; + align-items: center; + justify-content: center; +} diff --git a/src/v4/social/elements/StoryCommentButton/StoryCommentButton.tsx b/src/v4/social/elements/StoryCommentButton/StoryCommentButton.tsx new file mode 100644 index 000000000..cac07ee41 --- /dev/null +++ b/src/v4/social/elements/StoryCommentButton/StoryCommentButton.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { Icon } from '~/v4/core/components'; +import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; +import { isValidHttpUrl } from '~/utils'; +import { useTheme } from 'styled-components'; +import styles from './StoryCommentButton.module.css'; + +interface ReactButtonProps { + pageId: 'story_page'; + componentId: '*'; + onClick?: (e: React.MouseEvent) => void; + style?: React.CSSProperties; + children?: React.ReactNode; + 'data-qa-anchor'?: string; +} + +export const StoryCommentButton = ({ + pageId = 'story_page', + componentId = '*', + onClick = () => {}, + style, + children, + ...props +}: ReactButtonProps) => { + const theme = useTheme(); + const { getConfig, isExcluded } = useCustomization(); + const elementConfig = getConfig(`${pageId}/*/story_comment_button`); + const isElementExcluded = isExcluded(`${pageId}/*/story_comment_button`); + const backgroundColor = elementConfig?.background_color; + const commentIcon = elementConfig?.comment_icon; + + if (isElementExcluded) return null; + + const isRemoteImage = commentIcon && isValidHttpUrl(commentIcon); + + return isRemoteImage ? ( + + ) : ( + + ); +}; diff --git a/src/v4/social/elements/StoryCommentButton/index.ts b/src/v4/social/elements/StoryCommentButton/index.ts new file mode 100644 index 000000000..5ac4972a8 --- /dev/null +++ b/src/v4/social/elements/StoryCommentButton/index.ts @@ -0,0 +1 @@ +export { StoryCommentButton } from './StoryCommentButton'; diff --git a/src/v4/social/elements/Timestamp/Timestamp.module.css b/src/v4/social/elements/Timestamp/Timestamp.module.css new file mode 100644 index 000000000..0a4e578ec --- /dev/null +++ b/src/v4/social/elements/Timestamp/Timestamp.module.css @@ -0,0 +1,3 @@ +.timestamp { + color: var(--asc-color-base-shade2); +} diff --git a/src/v4/social/elements/Timestamp/Timestamp.stories.tsx b/src/v4/social/elements/Timestamp/Timestamp.stories.tsx new file mode 100644 index 000000000..0e78e4efc --- /dev/null +++ b/src/v4/social/elements/Timestamp/Timestamp.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { Timestamp } from './Timestamp'; + +export default { + title: 'v4-social/elements/Timestamp', +}; + +export const TimestampStory = { + render: () => { + return ; + }, + + name: 'Timestamp', +}; diff --git a/src/v4/social/elements/Timestamp/Timestamp.tsx b/src/v4/social/elements/Timestamp/Timestamp.tsx new file mode 100644 index 000000000..0a068a448 --- /dev/null +++ b/src/v4/social/elements/Timestamp/Timestamp.tsx @@ -0,0 +1,89 @@ +import React, { ReactNode } from 'react'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import styles from './Timestamp.module.css'; +import dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime'; +import updateLocale from 'dayjs/plugin/updateLocale'; +import { Typography } from '~/v4/core/components'; + +dayjs.extend(updateLocale); +dayjs.extend(relativeTime); + +dayjs.updateLocale('en', { + relativeTime: { + future: 'in %s', + past: '%s', + s: 'Just now', + m: '1m', + mm: '%dm', + h: '1h', + hh: '%dh', + d: '1d', + dd: '%dd', + M: function (number: number, withoutSuffix: string, key: string, isFuture: boolean) { + if (isFuture) return number + 'M'; + const date = dayjs().subtract(number, 'M'); + const currentDate = dayjs(); + if (date.get('year') === currentDate.get('year')) { + return date.format('D MMM'); + } + return date.format('D MMM YYYY'); + }, + MM: function (number: number, withoutSuffix: string, key: string, isFuture: boolean) { + if (isFuture) return number + 'M'; + const date = dayjs().subtract(number, 'M'); + const currentDate = dayjs(); + if (date.get('year') === currentDate.get('year')) { + return date.format('D MMM'); + } + return date.format('D MMM YYYY'); + }, + y: function (number: number, withoutSuffix: string, key: string, isFuture: boolean) { + if (isFuture) return number + 'y'; + const date = dayjs().subtract(number, 'y'); + const currentDate = dayjs(); + if (date.get('year') === currentDate.get('year')) { + return date.format('D MMM'); + } + return date.format('D MMM YYYY'); + }, + yy: function (number: number, withoutSuffix: string, key: string, isFuture: boolean) { + if (isFuture) return number + 'y'; + const date = dayjs().subtract(number, 'y'); + const currentDate = dayjs(); + if (date.get('year') === currentDate.get('year')) { + return date.format('D MMM'); + } + return date.format('D MMM YYYY'); + }, + }, +}); + +interface TimestampProps { + pageId?: string; + componentId?: string; + timestamp: Date | string; +} + +export function Timestamp({ pageId = '*', componentId = '*', timestamp }: TimestampProps) { + const elementId = 'timestamp'; + const { accessibilityId, isExcluded, themeStyles } = useAmityElement({ + pageId, + componentId, + elementId, + }); + + const relativeTimeStr = dayjs(timestamp).fromNow(); + + if (isExcluded) return null; + + return ( + + {relativeTimeStr} + + ); +} diff --git a/src/v4/social/elements/Timestamp/index.tsx b/src/v4/social/elements/Timestamp/index.tsx new file mode 100644 index 000000000..aae66e6a7 --- /dev/null +++ b/src/v4/social/elements/Timestamp/index.tsx @@ -0,0 +1 @@ +export { Timestamp } from './Timestamp'; diff --git a/src/v4/social/elements/index.ts b/src/v4/social/elements/index.ts index db4ec65b3..2935b33c5 100644 --- a/src/v4/social/elements/index.ts +++ b/src/v4/social/elements/index.ts @@ -1,8 +1,8 @@ export { AspectRatioButton } from './AspectRatioButton'; export { BackButton } from './BackButton/BackButton'; -export { CloseButton } from './CloseButton/CloseButton'; -export { CancelButton } from './CancelButton/CancelButton'; -export { CommentButton } from './CommentButton'; +export { CloseButton } from './CloseButton'; +export { CancelButton } from './CancelButton'; +export { StoryCommentButton } from './StoryCommentButton'; export { CreateStoryButton } from './CreateStoryButton/CreateStoryButton'; export { HyperLinkButton } from './HyperLinkButton/HyperLinkButton'; export { ImpressionButton } from './ImpressionButton'; diff --git a/src/v4/social/internal-components/ImageViewer/ImageViewer.module.css b/src/v4/social/internal-components/ImageViewer/ImageViewer.module.css new file mode 100644 index 000000000..1a37cfa07 --- /dev/null +++ b/src/v4/social/internal-components/ImageViewer/ImageViewer.module.css @@ -0,0 +1,101 @@ +.modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: var(--asc-color-black); + z-index: 1000; +} + +.modalContent { + background-color: var(--asc-color-base-background); + position: relative; + display: flex; + justify-content: center; + align-items: center; + height: 100%; +} + +.closeButton { + position: absolute; + top: 10px; + left: 10px; + font-size: 24px; + cursor: pointer; +} + +.fullImage { + max-width: 100vw; + max-height: 100vh; + width: 100%; + height: auto; + object-fit: contain; +} + +.nextButton { + width: 2rem; + height: 2rem; +} + +.prevButton { + width: 2rem; + height: 2rem; +} + +.overlayPanel { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; +} + +.overlayPanel__prev, +.overlayPanel__next { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + opacity: 0.5; + background-color: var(--asc-color-black); + flex-basis: 2.5rem; + cursor: pointer; +} + +.overlayPanel__prev:hover, +.overlayPanel__next:hover { + opacity: 0.8; +} + +.overlayPanel__prevButton, +.overlayPanel__nextButton { + width: 2rem; + height: 2rem; + fill: var(--asc-color-base-shade4); + border-radius: 50%; + background-color: var(--asc-color-base-shade3); + padding: 0.375rem; +} + +.overlayPanel__prevButton { + transform: rotate(180deg); +} + +.overlayPanel__middle { + flex: 1 1 auto; +} + +.imageViewer__clearButton { + fill: var(--asc-color-base-shade3); + width: 2rem; + height: 2rem; + cursor: pointer; +} + +.imageViewer__clearButton__img { + width: 2rem; + height: 2rem; + cursor: pointer; +} diff --git a/src/v4/social/internal-components/ImageViewer/ImageViewer.tsx b/src/v4/social/internal-components/ImageViewer/ImageViewer.tsx new file mode 100644 index 000000000..942b68ef0 --- /dev/null +++ b/src/v4/social/internal-components/ImageViewer/ImageViewer.tsx @@ -0,0 +1,96 @@ +import React, { useState } from 'react'; +import useImage from '~/core/hooks/useImage'; +import usePostByIds from '~/social/hooks/usePostByIds'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { ClearButton } from '../../elements/ClearButton/ClearButton'; +import styles from './ImageViewer.module.css'; + +const AngleRight = (props: React.SVGProps) => ( + + + +); + +interface ImageViewerProps { + pageId?: string; + componentId?: string; + elementId?: string; + post: Amity.Post; + initialImageIndex: number; + onClose(): void; +} + +export function ImageViewer({ + pageId = '*', + componentId = '*', + elementId = '*', + post, + initialImageIndex, + onClose, +}: ImageViewerProps) { + const { themeStyles } = useAmityElement({ pageId, componentId, elementId }); + + const [selectedImageIndex, setSelectedImageIndex] = useState(initialImageIndex); + + const posts = usePostByIds(post?.children || []); + + const imagePosts = posts.filter((post) => post.dataType === 'image'); + + const selectedPost = imagePosts[selectedImageIndex]; + + const imageUrl = useImage({ fileId: selectedPost?.data?.fileId }); + const hasNext = selectedImageIndex < imagePosts.length - 1; + const hasPrev = selectedImageIndex > 0; + + const next = () => { + if (!hasNext) { + return; + } + setSelectedImageIndex((prev) => prev + 1); + }; + + const prev = () => { + if (!hasPrev) { + return; + } + setSelectedImageIndex((prev) => prev - 1); + }; + + return ( +
+
+
e.stopPropagation()}> + {selectedPost?.data?.fileId +
+ {hasPrev && ( +
+ +
+ )} +
+ {hasNext && ( +
+ +
+ )} +
+ + + +
+
+
+ ); +} diff --git a/src/v4/social/internal-components/ImageViewer/index.tsx b/src/v4/social/internal-components/ImageViewer/index.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/src/v4/social/internal-components/Linkify/Linkify.module.css b/src/v4/social/internal-components/Linkify/Linkify.module.css new file mode 100644 index 000000000..8303889b4 --- /dev/null +++ b/src/v4/social/internal-components/Linkify/Linkify.module.css @@ -0,0 +1,8 @@ +.link { + color: var(--asc-color-primary-shade1); + text-decoration: none; +} + +.link:hover { + text-decoration: underline; +} diff --git a/src/v4/social/internal-components/Linkify/Linkify.tsx b/src/v4/social/internal-components/Linkify/Linkify.tsx new file mode 100644 index 000000000..6b0e17dc2 --- /dev/null +++ b/src/v4/social/internal-components/Linkify/Linkify.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import Linkify from 'linkify-react'; + +import styles from './Linkify.module.css'; + +type UiKitLinkifyProps = Omit, 'componentDecorator'>; + +export const UiKitLinkify = (props: UiKitLinkifyProps) => ( + ( + + {decoratedText} + + )} + {...props} + /> +); diff --git a/src/v4/social/internal-components/Linkify/index.tsx b/src/v4/social/internal-components/Linkify/index.tsx new file mode 100644 index 000000000..e6b15dbe5 --- /dev/null +++ b/src/v4/social/internal-components/Linkify/index.tsx @@ -0,0 +1 @@ +export { UiKitLinkify as Linkify } from './Linkify'; diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx index 8e0cbe87b..5820bde03 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx @@ -7,7 +7,7 @@ import millify from 'millify'; import { ReactionRepository } from '@amityco/ts-sdk'; import { LIKE_REACTION_KEY } from '~/constants'; import Spinner from '~/social/components/Spinner'; -import { CommentButton, ImpressionButton, ReactButton } from '~/v4/social/elements'; +import { StoryCommentButton, ImpressionButton, ReactButton } from '~/v4/social/elements'; const Footer: React.FC< React.PropsWithChildren<{ @@ -78,9 +78,9 @@ const Footer: React.FC< )}
- + {millify(commentsCount) || 0} - + {millify(totalLikes || 0)} diff --git a/src/v4/social/internal-components/UserAvatar/UserAvatar.module.css b/src/v4/social/internal-components/UserAvatar/UserAvatar.module.css new file mode 100644 index 000000000..31f91329c --- /dev/null +++ b/src/v4/social/internal-components/UserAvatar/UserAvatar.module.css @@ -0,0 +1,31 @@ +.userAvatar__text { + display: flex; + justify-content: center; + align-items: center; + border-radius: 50%; + width: 2rem; + height: 2rem; + color: var(--asc-color-base-shade4); + background-color: var(--asc-color-secondary-shade2); + text-transform: uppercase; +} + +.userAvatar__placeholder__path { + fill: var(--asc-color-white); +} + +.userAvatar__placeholder__rect { + fill: var(--asc-color-primary-shade3); +} + +.userAvatar__placeholder { + width: 2rem; + height: 2rem; +} + +.userAvatar__img { + border-radius: 50%; + object-fit: cover; + width: 2rem; + height: 2rem; +} diff --git a/src/v4/social/internal-components/UserAvatar/UserAvatar.stories.tsx b/src/v4/social/internal-components/UserAvatar/UserAvatar.stories.tsx new file mode 100644 index 000000000..ccefd2cee --- /dev/null +++ b/src/v4/social/internal-components/UserAvatar/UserAvatar.stories.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { UserAvatar } from './UserAvatar'; + +export default { + title: 'v4-social/internal-components/UserAvatar', +}; + +export const UserAvatarStory = { + render: () => { + return ; + }, +}; diff --git a/src/v4/social/internal-components/UserAvatar/UserAvatar.tsx b/src/v4/social/internal-components/UserAvatar/UserAvatar.tsx new file mode 100644 index 000000000..dab9a2934 --- /dev/null +++ b/src/v4/social/internal-components/UserAvatar/UserAvatar.tsx @@ -0,0 +1,46 @@ +import clsx from 'clsx'; +import React from 'react'; +import useImage from '~/core/hooks/useImage'; +import useUser from '~/core/hooks/useUser'; +import styles from './UserAvatar.module.css'; + +const UserSvg = ({ className, ...props }: React.SVGProps) => ( + + + + + +); + +interface UserAvatarProps { + userId?: string | null; + className?: string; +} + +export function UserAvatar({ userId, className }: UserAvatarProps) { + const user = useUser(userId); + + const userImage = useImage({ fileId: user?.avatar?.fileId }); + + if (user == null || userId == null || userImage == null) return ; + + return ( + + + + ); +} diff --git a/src/v4/social/internal-components/UserAvatar/index.tsx b/src/v4/social/internal-components/UserAvatar/index.tsx new file mode 100644 index 000000000..930fb2e3a --- /dev/null +++ b/src/v4/social/internal-components/UserAvatar/index.tsx @@ -0,0 +1 @@ +export { UserAvatar } from './UserAvatar'; diff --git a/src/v4/social/internal-components/VideoViewer/VideoViewer.module.css b/src/v4/social/internal-components/VideoViewer/VideoViewer.module.css new file mode 100644 index 000000000..44fb5d7d3 --- /dev/null +++ b/src/v4/social/internal-components/VideoViewer/VideoViewer.module.css @@ -0,0 +1,101 @@ +.modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: var(--asc-color-black); + z-index: 1000; +} + +.modalContent { + background-color: var(--asc-color-base-background); + position: relative; + display: flex; + justify-content: center; + align-items: center; + height: 100%; +} + +.closeButton { + position: absolute; + top: 10px; + left: 10px; + font-size: 24px; + cursor: pointer; +} + +.fullImage { + max-width: 100vw; + max-height: 100vh; + width: 100%; + height: auto; + object-fit: contain; +} + +.nextButton { + width: 2rem; + height: 2rem; +} + +.prevButton { + width: 2rem; + height: 2rem; +} + +.overlayPanel { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; +} + +.overlayPanel__prev, +.overlayPanel__next { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + opacity: 0.5; + background-color: var(--asc-color-black); + flex-basis: 2.5rem; + cursor: pointer; +} + +.overlayPanel__prev:hover, +.overlayPanel__next:hover { + opacity: 0.8; +} + +.overlayPanel__prevButton, +.overlayPanel__nextButton { + width: 2rem; + height: 2rem; + fill: var(--asc-color-base-shade4); + border-radius: 50%; + background-color: var(--asc-color-base-shade3); + padding: 0.375rem; +} + +.overlayPanel__prevButton { + transform: rotate(180deg); +} + +.overlayPanel__middle { + flex: 1 1 auto; +} + +.videoViewer__clearButton { + fill: var(--asc-color-base-shade3); + width: 2rem; + height: 2rem; + cursor: pointer; +} + +.videoViewer__clearButton__img { + width: 2rem; + height: 2rem; + cursor: pointer; +} diff --git a/src/v4/social/internal-components/VideoViewer/VideoViewer.tsx b/src/v4/social/internal-components/VideoViewer/VideoViewer.tsx new file mode 100644 index 000000000..cdd1c06b6 --- /dev/null +++ b/src/v4/social/internal-components/VideoViewer/VideoViewer.tsx @@ -0,0 +1,88 @@ +import React from 'react'; +import useFile from '~/core/hooks/useFile'; +import { VideoFileStatus } from '~/social/constants'; +import usePostByIds from '~/social/hooks/usePostByIds'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { ClearButton } from '~/v4/social/elements/ClearButton/ClearButton'; +import styles from './VideoViewer.module.css'; + +interface VideoViewerProps { + pageId?: string; + componentId?: string; + elementId?: string; + post: Amity.Post; + onClose(): void; +} + +export function VideoViewer({ + pageId = '*', + componentId = '*', + elementId = '*', + post, + onClose, +}: VideoViewerProps) { + const { themeStyles } = useAmityElement({ pageId, componentId, elementId }); + + const posts = usePostByIds(post?.children || []); + + const videoPosts = posts.filter((post) => post.dataType === 'video'); + + const videoFileId = + videoPosts?.[0]?.data.videoFileId.high || + videoPosts?.[0]?.data.videoFileId.medium || + videoPosts?.[0]?.data.videoFileId.low || + videoPosts?.[0]?.data.videoFileId.original || + undefined; + + const file: Amity.File<'video'> | undefined = useFile>(videoFileId); + + if (file == null) return null; + + /* + * It's possible that certain video formats uploaded by the user are not + * playable by the browser. So it's best to use the transcoded video file + * which is an mp4 format to play video. + * + * Note: the below logic needs to be smarter based on users bandwidth and also + * should be switchable by the user, which would require a ui update + */ + const url = (() => { + if (file.status === VideoFileStatus.Transcoded) { + const { videoUrl } = file; + + return ( + videoUrl?.['1080p'] || + videoUrl?.['720p'] || + videoUrl?.['480p'] || + videoUrl?.['360p'] || + videoUrl?.original || + file.fileUrl + ); + } + return file.fileUrl; + })(); + + return ( +
+
+
e.stopPropagation()}> + + + + +
+
+
+ ); +} diff --git a/src/v4/social/internal-components/VideoViewer/index.tsx b/src/v4/social/internal-components/VideoViewer/index.tsx new file mode 100644 index 000000000..e69de29bb From 91c3035674da0a0237665b3b8940bc282732385d Mon Sep 17 00:00:00 2001 From: Bonn Date: Wed, 12 Jun 2024 19:20:47 +0700 Subject: [PATCH 112/300] feat: TopSearchBar (#393) --- .../TopSearchBar/TopSearchBar.module.css | 49 +++++++++++++++ .../TopSearchBar/TopSearchBar.stories.tsx | 15 +++++ .../components/TopSearchBar/TopSearchBar.tsx | 63 +++++++++++++++++++ .../social/components/TopSearchBar/index.tsx | 1 + .../CancelButton/CancelButton.module.css | 17 +++++ .../CancelButton/CancelButton.stories.tsx | 15 +++++ .../elements/CancelButton/CancelButton.tsx | 55 ++++++---------- .../CancelButton/{index.ts => index.tsx} | 0 .../social/elements/CancelButton/styles.tsx | 22 ------- .../SearchIcon/SearchIcon.stories.tsx | 15 +++++ .../social/elements/SearchIcon/SearchIcon.tsx | 51 +++++++++++++++ src/v4/social/elements/SearchIcon/index.tsx | 1 + 12 files changed, 245 insertions(+), 59 deletions(-) create mode 100644 src/v4/social/components/TopSearchBar/TopSearchBar.module.css create mode 100644 src/v4/social/components/TopSearchBar/TopSearchBar.stories.tsx create mode 100644 src/v4/social/components/TopSearchBar/TopSearchBar.tsx create mode 100644 src/v4/social/components/TopSearchBar/index.tsx create mode 100644 src/v4/social/elements/CancelButton/CancelButton.module.css create mode 100644 src/v4/social/elements/CancelButton/CancelButton.stories.tsx rename src/v4/social/elements/CancelButton/{index.ts => index.tsx} (100%) delete mode 100644 src/v4/social/elements/CancelButton/styles.tsx create mode 100644 src/v4/social/elements/SearchIcon/SearchIcon.stories.tsx create mode 100644 src/v4/social/elements/SearchIcon/SearchIcon.tsx create mode 100644 src/v4/social/elements/SearchIcon/index.tsx diff --git a/src/v4/social/components/TopSearchBar/TopSearchBar.module.css b/src/v4/social/components/TopSearchBar/TopSearchBar.module.css new file mode 100644 index 000000000..9c3bc3c35 --- /dev/null +++ b/src/v4/social/components/TopSearchBar/TopSearchBar.module.css @@ -0,0 +1,49 @@ +.topSearchBar { + display: grid; + grid-template-columns: minmax(0, 1fr) min-content; + align-items: center; + gap: 0.5rem; + background-color: var(--asc-color-base-background); +} + +.topSearchBar__inputBar { + display: grid; + grid-template-columns: min-content minmax(0, 1fr) min-content; + align-items: center; + background-color: var(--asc-color-base-shade4); + gap: 0.5rem; + padding: 0.62rem 0.75rem; + border-radius: 0.5rem; +} + +.topSearchBar__textInput { + border: none; + background-color: var(--asc-color-base-shade4); + color: var(--asc-color-base-shade2); +} + +.topSearchBar__searchIcon { + fill: var(--asc-color-base-default); + width: 1.25rem; + height: 1.25rem; + cursor: pointer; +} + +.topSearchBar__searchIcon_img { + width: 1.25rem; + height: 1.25rem; + cursor: pointer; +} + +.topSearchBar__clearButton { + fill: var(--asc-color-base-shade3); + width: 1.25rem; + height: 1.25rem; + cursor: pointer; +} + +.topSearchBar__clearButton__img { + width: 1.25rem; + height: 1.25rem; + cursor: pointer; +} diff --git a/src/v4/social/components/TopSearchBar/TopSearchBar.stories.tsx b/src/v4/social/components/TopSearchBar/TopSearchBar.stories.tsx new file mode 100644 index 000000000..6347943f2 --- /dev/null +++ b/src/v4/social/components/TopSearchBar/TopSearchBar.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { TopSearchBar } from './TopSearchBar'; + +export default { + title: 'v4-social/components/TopSearchBar', +}; + +export const TopSearchBarStory = { + render: () => { + return {}} />; + }, + + name: 'TopSearchBar', +}; diff --git a/src/v4/social/components/TopSearchBar/TopSearchBar.tsx b/src/v4/social/components/TopSearchBar/TopSearchBar.tsx new file mode 100644 index 000000000..87ac2ddb7 --- /dev/null +++ b/src/v4/social/components/TopSearchBar/TopSearchBar.tsx @@ -0,0 +1,63 @@ +import React, { useEffect, useState } from 'react'; +import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; +import { ClearButton } from '~/v4/social/elements/ClearButton'; +import { CancelButton } from '~/v4/social/elements/CancelButton'; +import { SearchIcon } from '~/v4/social/elements/SearchIcon'; +import { useGenerateStylesShadeColors } from '~/v4/core/providers/ThemeProvider'; + +import styles from './TopSearchBar.module.css'; +import { useAmityComponent } from '~/v4/core/hooks/uikit'; + +export interface TopSearchBarProps { + pageId?: string; + search: (keyword: string) => void; +} + +export function TopSearchBar({ pageId = '*', search }: TopSearchBarProps) { + const componentId = 'top_search_bar'; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityComponent({ + pageId, + componentId, + }); + + const [searchValue, setSearchValue] = useState(''); + + useEffect(() => { + search(searchValue); + }, [searchValue]); + + if (isExcluded) return null; + + return ( +
+
+ + setSearchValue(ev.target.value)} + /> + {searchValue != '' ? ( + { + setSearchValue(''); + }} + /> + ) : null} +
+ +
+ ); +} diff --git a/src/v4/social/components/TopSearchBar/index.tsx b/src/v4/social/components/TopSearchBar/index.tsx new file mode 100644 index 000000000..9f68ae7f0 --- /dev/null +++ b/src/v4/social/components/TopSearchBar/index.tsx @@ -0,0 +1 @@ +export { TopSearchBar } from './TopSearchBar'; diff --git a/src/v4/social/elements/CancelButton/CancelButton.module.css b/src/v4/social/elements/CancelButton/CancelButton.module.css new file mode 100644 index 000000000..f8e806e37 --- /dev/null +++ b/src/v4/social/elements/CancelButton/CancelButton.module.css @@ -0,0 +1,17 @@ +button { + appearance: none; + border-radius: 0; + text-align: inherit; + background: none; + box-shadow: none; + padding: 0; + cursor: pointer; + border: none; + color: inherit; + font: inherit; +} + +.clearButton { + color: var(--asc-color-primary-default); + background-color: var(--asc-color-base-background); +} diff --git a/src/v4/social/elements/CancelButton/CancelButton.stories.tsx b/src/v4/social/elements/CancelButton/CancelButton.stories.tsx new file mode 100644 index 000000000..cb02fc798 --- /dev/null +++ b/src/v4/social/elements/CancelButton/CancelButton.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { CancelButton } from './CancelButton'; + +export default { + title: 'v4-social/elements/CancelButton', +}; + +export const CancelButtonStory = { + render: () => { + return ; + }, + + name: 'CancelButton', +}; diff --git a/src/v4/social/elements/CancelButton/CancelButton.tsx b/src/v4/social/elements/CancelButton/CancelButton.tsx index 4a8d82f34..0b2f9ad33 100644 --- a/src/v4/social/elements/CancelButton/CancelButton.tsx +++ b/src/v4/social/elements/CancelButton/CancelButton.tsx @@ -1,53 +1,34 @@ +import { config } from 'process'; import React from 'react'; - -import { StyledRemoteImageButton } from './styles'; -import { useIntl } from 'react-intl'; -import { isValidHttpUrl } from '~/utils'; -import { useTheme } from 'styled-components'; +import { Typography } from '~/v4/core/components'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; -import { Button } from '~/v4/core/components/Button'; +import { useGenerateStylesShadeColors } from '~/v4/core/providers/ThemeProvider'; +import styles from './CancelButton.module.css'; interface CancelButtonProps { - pageId: '*'; - componentId: 'edit_comment_component'; + pageId?: string; + componentId?: string; onClick?: (e: React.MouseEvent) => void; - children?: React.ReactNode; - style?: React.CSSProperties; } export const CancelButton = ({ pageId = '*', - componentId = 'edit_comment_component', + componentId = '*', onClick = () => {}, - style, }: CancelButtonProps) => { - const theme = useTheme(); const elementId = 'cancel_button'; - const { formatMessage } = useIntl(); - const { getConfig, isExcluded } = useCustomization(); - const elementConfig = getConfig(`${pageId}/${componentId}/${elementId}`); - const isElementExcluded = isExcluded(`${pageId}/${componentId}/${elementId}`); - - const cancelButtonText = elementConfig?.cancel_button_text; - const backgroundColor = elementConfig?.background_color; - const cancelButton = elementConfig?.back_icon; - - if (isElementExcluded) return null; + const { accessibilityId, config, isExcluded, themeStyles } = useAmityElement({ + pageId, + componentId, + elementId, + }); - const isRemoteImage = cancelButton && isValidHttpUrl(cancelButton); + if (isExcluded) return null; - return isRemoteImage ? ( - - {formatMessage({ id: cancelButtonText })} - - ) : ( - + return ( + ); }; diff --git a/src/v4/social/elements/CancelButton/index.ts b/src/v4/social/elements/CancelButton/index.tsx similarity index 100% rename from src/v4/social/elements/CancelButton/index.ts rename to src/v4/social/elements/CancelButton/index.tsx diff --git a/src/v4/social/elements/CancelButton/styles.tsx b/src/v4/social/elements/CancelButton/styles.tsx deleted file mode 100644 index 3e09de51f..000000000 --- a/src/v4/social/elements/CancelButton/styles.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import styled from 'styled-components'; - -export const StyledRemoteImageButton = styled.img` - width: 1.5rem; - height: 1.5rem; - cursor: pointer; - border: none; - outline: none; - padding: 0; - margin: 0; - display: flex; - align-items: center; - justify-content: center; - - &:hover { - opacity: 0.8; - } - - &:active { - opacity: 0.6; - } -`; diff --git a/src/v4/social/elements/SearchIcon/SearchIcon.stories.tsx b/src/v4/social/elements/SearchIcon/SearchIcon.stories.tsx new file mode 100644 index 000000000..a76869aec --- /dev/null +++ b/src/v4/social/elements/SearchIcon/SearchIcon.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { SearchIcon } from './SearchIcon'; + +export default { + title: 'v4-social/elements/SearchIcon', +}; + +export const SearchIconStory = { + render: () => { + return ; + }, + + name: 'SearchIcon', +}; diff --git a/src/v4/social/elements/SearchIcon/SearchIcon.tsx b/src/v4/social/elements/SearchIcon/SearchIcon.tsx new file mode 100644 index 000000000..fa89fc0fa --- /dev/null +++ b/src/v4/social/elements/SearchIcon/SearchIcon.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { IconComponent } from '~/v4/core/IconComponent'; + +const SearchIconSvg = (props: React.SVGProps) => ( + + + +); + +export interface SearchIconProps { + pageId?: string; + componentId?: string; + defaultClassName?: string; + imgClassName?: string; +} + +export function SearchIcon({ + pageId = '*', + componentId = '*', + defaultClassName, + imgClassName, +}: SearchIconProps) { + const elementId = 'search_icon'; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + return ( + } + imgIcon={() => ( + {uiReference} + )} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} + /> + ); +} diff --git a/src/v4/social/elements/SearchIcon/index.tsx b/src/v4/social/elements/SearchIcon/index.tsx new file mode 100644 index 000000000..18ac949b0 --- /dev/null +++ b/src/v4/social/elements/SearchIcon/index.tsx @@ -0,0 +1 @@ +export { SearchIcon } from './SearchIcon'; From 58ff6aa4cd5c19c88df19447cb59d048391de279 Mon Sep 17 00:00:00 2001 From: Bonn Date: Wed, 12 Jun 2024 19:30:58 +0700 Subject: [PATCH 113/300] feat: SocialHomePage (#394) --- .../EmptyNewsFeed/EmptyNewsFeed.module.css | 13 ++ .../EmptyNewsFeed/EmptyNewsFeed.stories.tsx | 15 ++ .../EmptyNewsFeed/EmptyNewsFeed.tsx | 37 ++++ .../social/components/EmptyNewsFeed/index.tsx | 1 + .../GlobalFeed/GlobalFeed.module.css | 86 ++++++++ .../GlobalFeed/GlobalFeed.stories.tsx | 15 ++ .../components/GlobalFeed/GlobalFeed.tsx | 80 ++++++++ src/v4/social/components/GlobalFeed/index.tsx | 1 + .../components/Newsfeed/Newsfeed.module.css | 88 +++++++++ .../components/Newsfeed/Newsfeed.stories.tsx | 15 ++ .../social/components/Newsfeed/Newsfeed.tsx | 120 ++++++++++++ src/v4/social/components/Newsfeed/index.tsx | 1 + .../components/Newsfeed/styles.module.css | 44 +++++ .../TopNavigation/TopNavigation.module.css | 21 ++ .../TopNavigation/TopNavigation.stories.tsx | 15 ++ .../TopNavigation/TopNavigation.tsx | 33 ++++ .../social/components/TopNavigation/index.tsx | 1 + .../CreateCommunityButton.module.css | 9 + .../CreateCommunityButton.stories.tsx | 15 ++ .../CreateCommunityButton.tsx | 44 +++++ .../elements/CreateCommunityButton/index.tsx | 1 + .../Description/Description.module.css | 4 + .../Description/Description.stories.tsx | 15 ++ .../elements/Description/Description.tsx | 33 ++++ src/v4/social/elements/Description/index.tsx | 1 + .../ExploreButton/ExploreButton.module.css | 13 ++ .../ExploreButton/ExploreButton.stories.tsx | 15 ++ .../elements/ExploreButton/ExploreButton.tsx | 41 ++++ .../social/elements/ExploreButton/index.tsx | 1 + .../ExploreCommunitiesButton.module.css | 18 ++ .../ExploreCommunitiesButton.stories.tsx | 15 ++ .../ExploreCommunitiesButton.tsx | 48 +++++ .../ExploreCommunitiesButton/index.tsx | 1 + .../GlobalSearchButton.module.css | 15 ++ .../GlobalSearchButton.stories.tsx | 15 ++ .../GlobalSearchButton/GlobalSearchButton.tsx | 73 +++++++ .../elements/GlobalSearchButton/index.tsx | 1 + .../HeaderLabel/HeaderLabel.module.css | 3 + .../HeaderLabel/HeaderLabel.stories.tsx | 15 ++ .../elements/HeaderLabel/HeaderLabel.tsx | 31 +++ src/v4/social/elements/HeaderLabel/index.tsx | 1 + .../Illustration/Illustration.stories.tsx | 15 ++ .../elements/Illustration/Illustration.tsx | 184 ++++++++++++++++++ src/v4/social/elements/Illustration/index.tsx | 1 + .../MyCommunitiesButton.module.css | 13 ++ .../MyCommunitiesButton.stories.tsx | 15 ++ .../MyCommunitiesButton.tsx | 41 ++++ .../elements/MyCommunitiesButton/index.tsx | 1 + .../NewsfeedButton/NewsfeedButton.module.css | 13 ++ .../NewsfeedButton/NewsfeedButton.stories.tsx | 15 ++ .../NewsfeedButton/NewsfeedButton.tsx | 41 ++++ .../social/elements/NewsfeedButton/index.tsx | 1 + .../PostCreationButton.module.css | 15 ++ .../PostCreationButton.stories.tsx | 15 ++ .../PostCreationButton/PostCreationButton.tsx | 74 +++++++ .../elements/PostCreationButton/index.tsx | 1 + src/v4/social/elements/Title/Title.module.css | 4 + .../social/elements/Title/Title.stories.tsx | 15 ++ src/v4/social/elements/Title/Title.tsx | 29 +++ src/v4/social/elements/Title/index.tsx | 1 + .../TabButton/TabButton.module.css | 22 +++ .../TabButton/TabButton.stories.tsx | 12 ++ .../TabButton/TabButton.tsx | 50 +++++ .../internal-components/TabButton/index.tsx | 1 + .../SocialHomePage/SocialHomePage.module.css | 38 ++++ .../SocialHomePage/SocialHomePage.stories.tsx | 12 ++ .../pages/SocialHomePage/SocialHomePage.tsx | 50 +++++ src/v4/social/pages/SocialHomePage/index.tsx | 1 + 68 files changed, 1694 insertions(+) create mode 100644 src/v4/social/components/EmptyNewsFeed/EmptyNewsFeed.module.css create mode 100644 src/v4/social/components/EmptyNewsFeed/EmptyNewsFeed.stories.tsx create mode 100644 src/v4/social/components/EmptyNewsFeed/EmptyNewsFeed.tsx create mode 100644 src/v4/social/components/EmptyNewsFeed/index.tsx create mode 100644 src/v4/social/components/GlobalFeed/GlobalFeed.module.css create mode 100644 src/v4/social/components/GlobalFeed/GlobalFeed.stories.tsx create mode 100644 src/v4/social/components/GlobalFeed/GlobalFeed.tsx create mode 100644 src/v4/social/components/GlobalFeed/index.tsx create mode 100644 src/v4/social/components/Newsfeed/Newsfeed.module.css create mode 100644 src/v4/social/components/Newsfeed/Newsfeed.stories.tsx create mode 100644 src/v4/social/components/Newsfeed/Newsfeed.tsx create mode 100644 src/v4/social/components/Newsfeed/index.tsx create mode 100644 src/v4/social/components/Newsfeed/styles.module.css create mode 100644 src/v4/social/components/TopNavigation/TopNavigation.module.css create mode 100644 src/v4/social/components/TopNavigation/TopNavigation.stories.tsx create mode 100644 src/v4/social/components/TopNavigation/TopNavigation.tsx create mode 100644 src/v4/social/components/TopNavigation/index.tsx create mode 100644 src/v4/social/elements/CreateCommunityButton/CreateCommunityButton.module.css create mode 100644 src/v4/social/elements/CreateCommunityButton/CreateCommunityButton.stories.tsx create mode 100644 src/v4/social/elements/CreateCommunityButton/CreateCommunityButton.tsx create mode 100644 src/v4/social/elements/CreateCommunityButton/index.tsx create mode 100644 src/v4/social/elements/Description/Description.module.css create mode 100644 src/v4/social/elements/Description/Description.stories.tsx create mode 100644 src/v4/social/elements/Description/Description.tsx create mode 100644 src/v4/social/elements/Description/index.tsx create mode 100644 src/v4/social/elements/ExploreButton/ExploreButton.module.css create mode 100644 src/v4/social/elements/ExploreButton/ExploreButton.stories.tsx create mode 100644 src/v4/social/elements/ExploreButton/ExploreButton.tsx create mode 100644 src/v4/social/elements/ExploreButton/index.tsx create mode 100644 src/v4/social/elements/ExploreCommunitiesButton/ExploreCommunitiesButton.module.css create mode 100644 src/v4/social/elements/ExploreCommunitiesButton/ExploreCommunitiesButton.stories.tsx create mode 100644 src/v4/social/elements/ExploreCommunitiesButton/ExploreCommunitiesButton.tsx create mode 100644 src/v4/social/elements/ExploreCommunitiesButton/index.tsx create mode 100644 src/v4/social/elements/GlobalSearchButton/GlobalSearchButton.module.css create mode 100644 src/v4/social/elements/GlobalSearchButton/GlobalSearchButton.stories.tsx create mode 100644 src/v4/social/elements/GlobalSearchButton/GlobalSearchButton.tsx create mode 100644 src/v4/social/elements/GlobalSearchButton/index.tsx create mode 100644 src/v4/social/elements/HeaderLabel/HeaderLabel.module.css create mode 100644 src/v4/social/elements/HeaderLabel/HeaderLabel.stories.tsx create mode 100644 src/v4/social/elements/HeaderLabel/HeaderLabel.tsx create mode 100644 src/v4/social/elements/HeaderLabel/index.tsx create mode 100644 src/v4/social/elements/Illustration/Illustration.stories.tsx create mode 100644 src/v4/social/elements/Illustration/Illustration.tsx create mode 100644 src/v4/social/elements/Illustration/index.tsx create mode 100644 src/v4/social/elements/MyCommunitiesButton/MyCommunitiesButton.module.css create mode 100644 src/v4/social/elements/MyCommunitiesButton/MyCommunitiesButton.stories.tsx create mode 100644 src/v4/social/elements/MyCommunitiesButton/MyCommunitiesButton.tsx create mode 100644 src/v4/social/elements/MyCommunitiesButton/index.tsx create mode 100644 src/v4/social/elements/NewsfeedButton/NewsfeedButton.module.css create mode 100644 src/v4/social/elements/NewsfeedButton/NewsfeedButton.stories.tsx create mode 100644 src/v4/social/elements/NewsfeedButton/NewsfeedButton.tsx create mode 100644 src/v4/social/elements/NewsfeedButton/index.tsx create mode 100644 src/v4/social/elements/PostCreationButton/PostCreationButton.module.css create mode 100644 src/v4/social/elements/PostCreationButton/PostCreationButton.stories.tsx create mode 100644 src/v4/social/elements/PostCreationButton/PostCreationButton.tsx create mode 100644 src/v4/social/elements/PostCreationButton/index.tsx create mode 100644 src/v4/social/elements/Title/Title.module.css create mode 100644 src/v4/social/elements/Title/Title.stories.tsx create mode 100644 src/v4/social/elements/Title/Title.tsx create mode 100644 src/v4/social/elements/Title/index.tsx create mode 100644 src/v4/social/internal-components/TabButton/TabButton.module.css create mode 100644 src/v4/social/internal-components/TabButton/TabButton.stories.tsx create mode 100644 src/v4/social/internal-components/TabButton/TabButton.tsx create mode 100644 src/v4/social/internal-components/TabButton/index.tsx create mode 100644 src/v4/social/pages/SocialHomePage/SocialHomePage.module.css create mode 100644 src/v4/social/pages/SocialHomePage/SocialHomePage.stories.tsx create mode 100644 src/v4/social/pages/SocialHomePage/SocialHomePage.tsx create mode 100644 src/v4/social/pages/SocialHomePage/index.tsx diff --git a/src/v4/social/components/EmptyNewsFeed/EmptyNewsFeed.module.css b/src/v4/social/components/EmptyNewsFeed/EmptyNewsFeed.module.css new file mode 100644 index 000000000..497bbd9d0 --- /dev/null +++ b/src/v4/social/components/EmptyNewsFeed/EmptyNewsFeed.module.css @@ -0,0 +1,13 @@ +.emptyNewsfeed { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + background-color: var(--asc-color-base-background); +} + +.emptyNewsfeed__text { + padding-bottom: 1.0625rem; +} diff --git a/src/v4/social/components/EmptyNewsFeed/EmptyNewsFeed.stories.tsx b/src/v4/social/components/EmptyNewsFeed/EmptyNewsFeed.stories.tsx new file mode 100644 index 000000000..ad275b47b --- /dev/null +++ b/src/v4/social/components/EmptyNewsFeed/EmptyNewsFeed.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { EmptyNewsfeed } from './EmptyNewsFeed'; + +export default { + title: 'v4-social/components/EmptyNewsfeed', +}; + +export const EmptyNewsfeedStory = { + render: () => { + return ; + }, + + name: 'EmptyNewsfeed', +}; diff --git a/src/v4/social/components/EmptyNewsFeed/EmptyNewsFeed.tsx b/src/v4/social/components/EmptyNewsFeed/EmptyNewsFeed.tsx new file mode 100644 index 000000000..35407f4cf --- /dev/null +++ b/src/v4/social/components/EmptyNewsFeed/EmptyNewsFeed.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; +import { Illustration } from '~/v4/social/elements/Illustration'; +import { Description } from '~/v4/social/elements/Description'; +import { Title } from '~/v4/social/elements/Title'; +import { ExploreCommunitiesButton } from '~/v4/social/elements/ExploreCommunitiesButton'; +import { CreateCommunityButton } from '~/v4/social/elements/CreateCommunityButton'; + +import styles from './EmptyNewsFeed.module.css'; +import { useAmityComponent } from '~/v4/core/hooks/uikit'; + +interface EmptyNewsfeedProps { + pageId?: string; +} + +export function EmptyNewsfeed({ pageId = '*' }: EmptyNewsfeedProps) { + const componentId = 'empty_newsfeed'; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityComponent({ + pageId, + componentId, + }); + + if (isExcluded) return null; + + return ( +
+ +
+ + <Description pageId={pageId} componentId={componentId} /> + </div> + <ExploreCommunitiesButton pageId={pageId} componentId={componentId} /> + <CreateCommunityButton pageId={pageId} componentId={componentId} onClick={() => {}} /> + </div> + ); +} diff --git a/src/v4/social/components/EmptyNewsFeed/index.tsx b/src/v4/social/components/EmptyNewsFeed/index.tsx new file mode 100644 index 000000000..eb7f825e9 --- /dev/null +++ b/src/v4/social/components/EmptyNewsFeed/index.tsx @@ -0,0 +1 @@ +export { EmptyNewsfeed } from './EmptyNewsFeed'; diff --git a/src/v4/social/components/GlobalFeed/GlobalFeed.module.css b/src/v4/social/components/GlobalFeed/GlobalFeed.module.css new file mode 100644 index 000000000..46031cf6a --- /dev/null +++ b/src/v4/social/components/GlobalFeed/GlobalFeed.module.css @@ -0,0 +1,86 @@ +.global_feed { + display: flex; + flex-direction: column; +} + +.global_feed__postContainer { + padding: 0.25rem 1rem 0.75rem 0.75rem; +} + +.global_feed__postSkeletonContainer { + padding-top: 0.75rem; + padding-left: 1rem; +} + +.global_feed__pullToRefresh { + height: var(--asc-pull-to-refresh-height); + display: flex; + justify-content: center; + align-items: center; + overflow: hidden; +} + +.global_feed__post__reactionBar__leftPane__icon { + width: 1.25rem; + height: 1.25rem; +} + +.global_feed__post__reactionBar__leftPane__iconImg { + width: 1.25rem; + height: 1.25rem; +} + +.global_feed__pullToRefresh__spinner { + width: 1.25rem; + height: 1.25rem; + animation-name: spin; + animation-duration: 1000ms; + animation-iteration-count: infinite; + animation-timing-function: linear; +} + +.global_feed__divider { + background-color: var(--asc-color-base-shade4); + height: 0.5rem; + width: 100%; +} + +.global_feed__post__divider { + width: 100%; + background-color: var(--asc-color-base-shade4); + height: 0.0625rem; +} + +.global_feed__post__reactionBar { + display: flex; + justify-content: space-between; + align-items: center; + padding-left: 0.75rem; + padding-right: 0.75rem; +} + +.global_feed__post__reactionBar__leftPane { + display: flex; + align-items: center; + justify-content: center; +} + +.global_feed__post__reactionBar__rightPane { + display: flex; + align-items: center; + justify-content: center; +} + +.global_feed__intersection { + height: 1px; +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + + to { + transform: rotate(360deg); + } +} diff --git a/src/v4/social/components/GlobalFeed/GlobalFeed.stories.tsx b/src/v4/social/components/GlobalFeed/GlobalFeed.stories.tsx new file mode 100644 index 000000000..85d913fca --- /dev/null +++ b/src/v4/social/components/GlobalFeed/GlobalFeed.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { GlobalFeed } from './GlobalFeed'; + +export default { + title: 'v4-social/components/GlobalFeed', +}; + +export const GlobalFeedStory = { + render: () => { + return <GlobalFeed />; + }, + + name: 'GlobalFeed', +}; diff --git a/src/v4/social/components/GlobalFeed/GlobalFeed.tsx b/src/v4/social/components/GlobalFeed/GlobalFeed.tsx new file mode 100644 index 000000000..9a303bc33 --- /dev/null +++ b/src/v4/social/components/GlobalFeed/GlobalFeed.tsx @@ -0,0 +1,80 @@ +import React, { useRef } from 'react'; +import { PostContent, PostContentSkeleton } from '../PostContent'; +import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; +import { useGenerateStylesShadeColors } from '~/v4/core/providers/ThemeProvider'; +import { EmptyNewsfeed } from '~/v4/social/components/EmptyNewsFeed/EmptyNewsFeed'; +import useGlobalFeed from '~/v4/core/hooks/collections/useGlobalFeed'; +import useIntersectionObserver from '~/v4/core/hooks/useIntersectionObserver'; +import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; + +import styles from './GlobalFeed.module.css'; +import { useAmityComponent } from '~/v4/core/hooks/uikit'; + +interface GlobalFeedProps { + pageId?: string; + componentId?: string; + posts: Amity.Post[]; + isLoading: boolean; + onFeedReachBottom: () => void; +} + +export const GlobalFeed = ({ + pageId = '*', + componentId = '*', + posts, + isLoading, + onFeedReachBottom, +}: GlobalFeedProps) => { + const { getConfig } = useCustomization(); + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityComponent({ + pageId, + componentId, + }); + + const intersectionRef = useRef<HTMLDivElement>(null); + + const { AmityGlobalFeedComponentBehavior } = usePageBehavior(); + + useIntersectionObserver({ + ref: intersectionRef, + onIntersect: () => { + onFeedReachBottom(); + }, + }); + + if (posts.length === 0 && !isLoading) { + return <EmptyNewsfeed pageId={pageId} />; + } + + return ( + <div className={styles.global_feed} style={themeStyles}> + {posts.map((post, index) => ( + <div key={post.postId}> + {index !== 0 ? <div className={styles.global_feed__divider} /> : null} + <div className={styles.global_feed__postContainer}> + <PostContent + pageId={pageId} + post={post} + type="feed" + onClick={() => { + AmityGlobalFeedComponentBehavior?.goToPostDetailPage?.({ postId: post.postId }); + }} + /> + </div> + </div> + ))} + {isLoading + ? Array.from({ length: 2 }).map((_, index) => ( + <div key={index}> + <div className={styles.global_feed__divider} /> + <div className={styles.global_feed__postSkeletonContainer}> + <PostContentSkeleton /> + </div> + </div> + )) + : null} + <div ref={intersectionRef} className={styles.global_feed__intersection} /> + </div> + ); +}; diff --git a/src/v4/social/components/GlobalFeed/index.tsx b/src/v4/social/components/GlobalFeed/index.tsx new file mode 100644 index 000000000..5814d25bf --- /dev/null +++ b/src/v4/social/components/GlobalFeed/index.tsx @@ -0,0 +1 @@ +export { GlobalFeed } from './GlobalFeed'; diff --git a/src/v4/social/components/Newsfeed/Newsfeed.module.css b/src/v4/social/components/Newsfeed/Newsfeed.module.css new file mode 100644 index 000000000..4b0053de0 --- /dev/null +++ b/src/v4/social/components/Newsfeed/Newsfeed.module.css @@ -0,0 +1,88 @@ +.newsfeed { + display: flex; + flex-direction: column; + width: 100%; + background-color: var(--asc-color-base-background); +} + +.newsfeed__postList { + display: flex; + flex-direction: column; +} + +.newsfeed__postContainer { + padding: 0.25rem 1rem 0.75rem 0.75rem; +} + +.newsfeed__pullToRefresh { + height: var(--asc-pull-to-refresh-height); + display: flex; + justify-content: center; + align-items: center; + overflow: hidden; +} + +.newsfeed__post__reactionBar__leftPane__icon { + width: 1.25rem; + height: 1.25rem; +} + +.newsfeed__post__reactionBar__leftPane__iconImg { + width: 1.25rem; + height: 1.25rem; +} + +.newsfeed__pullToRefresh__spinner { + width: 1.25rem; + height: 1.25rem; + animation-name: spin; + animation-duration: 1000ms; + animation-iteration-count: infinite; + animation-timing-function: linear; +} + +.newsfeed__divider { + background-color: var(--asc-color-base-shade4); + height: 0.5rem; + width: 100%; +} + +.newsfeed__post__divider { + width: 100%; + background-color: var(--asc-color-base-shade4); + height: 0.0625rem; +} + +.newsfeed__post__reactionBar { + display: flex; + justify-content: space-between; + align-items: center; + padding-left: 0.75rem; + padding-right: 0.75rem; +} + +.newsfeed__post__reactionBar__leftPane { + display: flex; + align-items: center; + justify-content: center; +} + +.newsfeed__post__reactionBar__rightPane { + display: flex; + align-items: center; + justify-content: center; +} + +.newsfeed__intersection { + height: 1px; +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + + to { + transform: rotate(360deg); + } +} diff --git a/src/v4/social/components/Newsfeed/Newsfeed.stories.tsx b/src/v4/social/components/Newsfeed/Newsfeed.stories.tsx new file mode 100644 index 000000000..263be2990 --- /dev/null +++ b/src/v4/social/components/Newsfeed/Newsfeed.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { Newsfeed } from './Newsfeed'; + +export default { + title: 'v4-social/components/Newsfeed', +}; + +export const NewsfeedStory = { + render: () => { + return <Newsfeed />; + }, + + name: 'Newsfeed', +}; diff --git a/src/v4/social/components/Newsfeed/Newsfeed.tsx b/src/v4/social/components/Newsfeed/Newsfeed.tsx new file mode 100644 index 000000000..8f0ff6487 --- /dev/null +++ b/src/v4/social/components/Newsfeed/Newsfeed.tsx @@ -0,0 +1,120 @@ +import React, { useRef, useState } from 'react'; +import { StoryTab } from '~/v4/social/components/StoryTab'; +import useGlobalFeed from '~/v4/core/hooks/collections/useGlobalFeed'; +import { EmptyNewsfeed } from '~/v4/social/components/EmptyNewsFeed'; +import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; +import { useGenerateStylesShadeColors } from '~/v4/core/providers/ThemeProvider'; +import { GlobalFeed } from '~/v4/social/components/GlobalFeed'; + +import styles from './Newsfeed.module.css'; +import { useAmityComponent } from '~/v4/core/hooks/uikit'; + +const Spinner = (props: React.SVGProps<SVGSVGElement>) => { + return ( + <div className={styles.newsfeed__pullToRefresh__spinner}> + <svg + xmlns="http://www.w3.org/2000/svg" + width="21" + height="20" + viewBox="0 0 21 20" + fill="none" + > + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="M11.1122 5C11.1122 5.39449 10.7924 5.71429 10.3979 5.71429C10.0035 5.71429 9.68366 5.39449 9.68366 5V0.714286C9.68366 0.319797 10.0035 0 10.3979 0C10.7924 0 11.1122 0.319797 11.1122 0.714286V5ZM8.25509 6.28846C8.59673 6.09122 8.71378 5.65437 8.51654 5.31273L6.37368 1.60119C6.17644 1.25955 5.73959 1.1425 5.39795 1.33975C5.05631 1.53699 4.93926 1.97384 5.1365 2.31548L7.27936 6.02702C7.4766 6.36865 7.91346 6.48571 8.25509 6.28846ZM6.42496 6.88141C6.7666 7.07865 6.88366 7.51551 6.68641 7.85714C6.48917 8.19878 6.05232 8.31583 5.71068 8.11859L1.99914 5.97573C1.6575 5.77849 1.54045 5.34164 1.7377 5C1.93494 4.65836 2.37179 4.54131 2.71343 4.73855L6.42496 6.88141ZM6.11224 10C6.11224 9.60551 5.79244 9.28571 5.39795 9.28571H1.11223C0.717746 9.28571 0.397949 9.60551 0.397949 10C0.397949 10.3945 0.717746 10.7143 1.11224 10.7143H5.39795C5.79244 10.7143 6.11224 10.3945 6.11224 10ZM5.71068 11.8814C6.05232 11.6842 6.48917 11.8012 6.68641 12.1429C6.88366 12.4845 6.7666 12.9213 6.42497 13.1186L2.71343 15.2614C2.37179 15.4587 1.93494 15.3416 1.7377 15C1.54045 14.6584 1.6575 14.2215 1.99914 14.0243L5.71068 11.8814ZM8.25509 13.7115C7.91345 13.5143 7.4766 13.6313 7.27936 13.973L5.1365 17.6845C4.93926 18.0262 5.05631 18.463 5.39795 18.6603C5.73959 18.8575 6.17644 18.7404 6.37368 18.3988L8.51654 14.6873C8.71378 14.3456 8.59673 13.9088 8.25509 13.7115ZM10.3979 14.2857C10.0035 14.2857 9.68366 14.6055 9.68366 15V19.2857C9.68366 19.6802 10.0035 20 10.3979 20C10.7924 20 11.1122 19.6802 11.1122 19.2857V15C11.1122 14.6055 10.7924 14.2857 10.3979 14.2857ZM12.5408 6.28846C12.8824 6.48571 13.3193 6.36865 13.5165 6.02702L15.6594 2.31548C15.8566 1.97384 15.7396 1.53699 15.3979 1.33975C15.0563 1.1425 14.6195 1.25956 14.4222 1.60119L12.2794 5.31273C12.0821 5.65437 12.1992 6.09122 12.5408 6.28846ZM15.0852 8.11859C14.7436 8.31583 14.3067 8.19878 14.1095 7.85714C13.9122 7.51551 14.0293 7.07866 14.3709 6.88141L18.0825 4.73855C18.4241 4.54131 18.861 4.65836 19.0582 5C19.2554 5.34164 19.1384 5.77849 18.7968 5.97573L15.0852 8.11859ZM14.6837 10C14.6837 10.3945 15.0035 10.7143 15.3979 10.7143H19.6837C20.0782 10.7143 20.3979 10.3945 20.3979 10C20.3979 9.60551 20.0782 9.28571 19.6837 9.28571H15.3979C15.0035 9.28571 14.6837 9.60551 14.6837 10ZM14.3709 13.1186C14.0293 12.9213 13.9122 12.4845 14.1095 12.1429C14.3067 11.8012 14.7436 11.6842 15.0852 11.8814L18.7968 14.0243C19.1384 14.2215 19.2554 14.6584 19.0582 15C18.861 15.3416 18.4241 15.4587 18.0825 15.2614L14.3709 13.1186ZM12.5408 13.7115C12.1992 13.9088 12.0821 14.3456 12.2794 14.6873L14.4222 18.3988C14.6195 18.7404 15.0563 18.8575 15.3979 18.6603C15.7396 18.463 15.8566 18.0262 15.6594 17.6845L13.5165 13.973C13.3193 13.6313 12.8824 13.5143 12.5408 13.7115Z" + fill="url(#paint0_angular_1709_10374)" + /> + <defs> + <radialGradient + id="paint0_angular_1709_10374" + cx="0" + cy="0" + r="1" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(10.3979 10) scale(10)" + > + <stop offset="0.669733" stop-color="#595F67" /> + <stop offset="0.716307" stop-color="#262626" stop-opacity="0.01" /> + </radialGradient> + </defs> + </svg> + </div> + ); +}; + +interface NewsfeedProps { + pageId?: string; +} + +export const Newsfeed = ({ pageId = '*' }: NewsfeedProps) => { + const componentId = 'newsfeed'; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityComponent({ + pageId, + componentId, + }); + + const touchStartY = useRef(0); + const [touchDiff, setTouchDiff] = useState(0); + + const { posts, hasMore, isLoading, loadMore, refetch } = useGlobalFeed(); + + const onFeedReachBottom = () => { + if (hasMore && !isLoading) loadMore(); + }; + + if (posts.length === 0 && !isLoading) { + return <EmptyNewsfeed pageId={pageId} />; + } + + return ( + <div + className={styles.newsfeed} + style={themeStyles} + onTouchStart={(ev) => { + touchStartY.current = ev.touches[0].clientY; + }} + onTouchMove={(ev) => { + const touchY = ev.touches[0].clientY; + + if (touchStartY.current > touchY) { + return; + } + + setTouchDiff(Math.min(touchY - touchStartY.current, 100)); + if (touchDiff > 0 && window.scrollY === 0) { + ev.preventDefault(); + } + }} + onTouchEnd={() => { + touchStartY.current = 0; + if (touchDiff >= 75) { + refetch(); + } + setTouchDiff(0); + }} + > + <div + style={ + { + '--asc-pull-to-refresh-height': `${touchDiff}px`, + } as React.CSSProperties + } + className={styles.newsfeed__pullToRefresh} + > + <Spinner /> + </div> + <div className={styles.newsfeed__divider} /> + <StoryTab type="globalFeed" pageId={pageId} /> + {posts.length > 0 && <div className={styles.newsfeed__divider} />} + <GlobalFeed + isLoading={isLoading} + posts={posts} + pageId={pageId} + componentId={componentId} + onFeedReachBottom={() => onFeedReachBottom()} + /> + </div> + ); +}; diff --git a/src/v4/social/components/Newsfeed/index.tsx b/src/v4/social/components/Newsfeed/index.tsx new file mode 100644 index 000000000..7abb6097f --- /dev/null +++ b/src/v4/social/components/Newsfeed/index.tsx @@ -0,0 +1 @@ +export { Newsfeed } from './Newsfeed'; diff --git a/src/v4/social/components/Newsfeed/styles.module.css b/src/v4/social/components/Newsfeed/styles.module.css new file mode 100644 index 000000000..54a15521e --- /dev/null +++ b/src/v4/social/components/Newsfeed/styles.module.css @@ -0,0 +1,44 @@ +.newsfeed { + display: flex; + flex-direction: column; + width: 100%; +} + +.newsfeed__postList { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.newsfeed__pullToRefresh { + height: var(--asc-pull-to-refresh-height); + display: flex; + justify-content: center; + align-items: center; + overflow: hidden; +} + +.newsfeed__pullToRefresh__spinner { + width: 1.25rem; + height: 1.25rem; + animation-name: spin; + animation-duration: 1000ms; + animation-iteration-count: infinite; + animation-timing-function: linear; +} + +.newsfeed__divider { + background-color: var(--asc-color-base-shade4); + height: 0.5rem; + width: 100%; +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + + to { + transform: rotate(360deg); + } +} diff --git a/src/v4/social/components/TopNavigation/TopNavigation.module.css b/src/v4/social/components/TopNavigation/TopNavigation.module.css new file mode 100644 index 000000000..80f794e10 --- /dev/null +++ b/src/v4/social/components/TopNavigation/TopNavigation.module.css @@ -0,0 +1,21 @@ +.topNavigation { + display: flex; + justify-content: space-between; + align-items: center; + background-color: var(--asc-color-base-background); + height: 3.625rem; +} + +.topNavigationLeftPane { + display: flex; + gap: 0.625rem; + justify-content: center; + align-items: center; +} + +.topNavigationRightPane { + display: flex; + gap: 0.625rem; + justify-content: center; + align-items: center; +} diff --git a/src/v4/social/components/TopNavigation/TopNavigation.stories.tsx b/src/v4/social/components/TopNavigation/TopNavigation.stories.tsx new file mode 100644 index 000000000..3fda5e5ba --- /dev/null +++ b/src/v4/social/components/TopNavigation/TopNavigation.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { TopNavigation } from './TopNavigation'; + +export default { + title: 'v4-social/components/TopNavigation', +}; + +export const TopNavigationStory = { + render: () => { + return <TopNavigation />; + }, + + name: 'TopNavigation', +}; diff --git a/src/v4/social/components/TopNavigation/TopNavigation.tsx b/src/v4/social/components/TopNavigation/TopNavigation.tsx new file mode 100644 index 000000000..30949262c --- /dev/null +++ b/src/v4/social/components/TopNavigation/TopNavigation.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { PostCreationButton } from '~/v4/social/elements/PostCreationButton'; +import { GlobalSearchButton } from '~/v4/social/elements/GlobalSearchButton'; +import { HeaderLabel } from '~/v4/social/elements/HeaderLabel'; +import styles from './TopNavigation.module.css'; +import { useAmityComponent } from '~/v4/core/hooks/uikit'; + +export interface TopNavigationProps { + pageId?: string; +} + +export function TopNavigation({ pageId = '*' }: TopNavigationProps) { + const componentId = 'top_navigation'; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityComponent({ + pageId, + componentId, + }); + + if (isExcluded) return null; + + return ( + <div className={styles.topNavigation} style={themeStyles}> + <div className={styles.topNavigationLeftPane}> + <HeaderLabel pageId={pageId} componentId={componentId} /> + </div> + <div className={styles.topNavigationRightPane}> + <GlobalSearchButton pageId={pageId} componentId={componentId} /> + <PostCreationButton pageId={pageId} componentId={componentId} /> + </div> + </div> + ); +} diff --git a/src/v4/social/components/TopNavigation/index.tsx b/src/v4/social/components/TopNavigation/index.tsx new file mode 100644 index 000000000..2c5d02b7a --- /dev/null +++ b/src/v4/social/components/TopNavigation/index.tsx @@ -0,0 +1 @@ +export { TopNavigation } from './TopNavigation'; diff --git a/src/v4/social/elements/CreateCommunityButton/CreateCommunityButton.module.css b/src/v4/social/elements/CreateCommunityButton/CreateCommunityButton.module.css new file mode 100644 index 000000000..ef9201772 --- /dev/null +++ b/src/v4/social/elements/CreateCommunityButton/CreateCommunityButton.module.css @@ -0,0 +1,9 @@ +.createCommunityButton { + display: flex; + padding: 0.625rem 1rem; + justify-content: center; + align-items: center; + border-radius: 0.25rem; + border: 0 solid var(--asc-color-secondary-shade3); + color: var(--asc-color-primary-default); +} diff --git a/src/v4/social/elements/CreateCommunityButton/CreateCommunityButton.stories.tsx b/src/v4/social/elements/CreateCommunityButton/CreateCommunityButton.stories.tsx new file mode 100644 index 000000000..62b983f66 --- /dev/null +++ b/src/v4/social/elements/CreateCommunityButton/CreateCommunityButton.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { CreateCommunityButton } from './CreateCommunityButton'; + +export default { + title: 'v4-social/elements/CreateCommunityButton', +}; + +export const CreateCommunityButtonStory = { + render: () => { + return <CreateCommunityButton onClick={() => console.log('CreateCommunityButton clicked')} />; + }, + + name: 'CreateCommunityButton', +}; diff --git a/src/v4/social/elements/CreateCommunityButton/CreateCommunityButton.tsx b/src/v4/social/elements/CreateCommunityButton/CreateCommunityButton.tsx new file mode 100644 index 000000000..c6936410b --- /dev/null +++ b/src/v4/social/elements/CreateCommunityButton/CreateCommunityButton.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { Typography } from '~/v4/core/components'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { IconComponent } from '~/v4/core/IconComponent'; +import styles from './CreateCommunityButton.module.css'; + +export interface CreateCommunityButtonProps { + pageId?: string; + componentId?: string; + onClick: () => void; +} + +export function CreateCommunityButton({ + pageId = '*', + componentId = '*', + onClick, +}: CreateCommunityButtonProps) { + const elementId = 'create_community_button'; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + return ( + <div + className={styles.createCommunityButton} + onClick={() => onClick()} + data-qa-anchor={accessibilityId} + style={themeStyles} + > + <IconComponent + defaultIcon={() => <></>} + imgIcon={() => <img src={config.icon} alt={uiReference} />} + configIconName={config.icon} + defaultIconName={defaultConfig.icon} + /> + <Typography.Body>{config.text}</Typography.Body> + </div> + ); +} diff --git a/src/v4/social/elements/CreateCommunityButton/index.tsx b/src/v4/social/elements/CreateCommunityButton/index.tsx new file mode 100644 index 000000000..104d3e632 --- /dev/null +++ b/src/v4/social/elements/CreateCommunityButton/index.tsx @@ -0,0 +1 @@ +export { CreateCommunityButton } from './CreateCommunityButton'; diff --git a/src/v4/social/elements/Description/Description.module.css b/src/v4/social/elements/Description/Description.module.css new file mode 100644 index 000000000..6f088f894 --- /dev/null +++ b/src/v4/social/elements/Description/Description.module.css @@ -0,0 +1,4 @@ +.description { + color: var(--asc-color-base-shade3); + text-align: center; +} diff --git a/src/v4/social/elements/Description/Description.stories.tsx b/src/v4/social/elements/Description/Description.stories.tsx new file mode 100644 index 000000000..c02003914 --- /dev/null +++ b/src/v4/social/elements/Description/Description.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { Description } from './Description'; + +export default { + title: 'v4-social/elements/Description', +}; + +export const DescriptionStory = { + render: () => { + return <Description />; + }, + + name: 'Description', +}; diff --git a/src/v4/social/elements/Description/Description.tsx b/src/v4/social/elements/Description/Description.tsx new file mode 100644 index 000000000..183a23f24 --- /dev/null +++ b/src/v4/social/elements/Description/Description.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import styles from './Description.module.css'; +import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; +import { Typography } from '~/v4/core/components'; +import { useGenerateStylesShadeColors } from '~/v4/core/providers/ThemeProvider'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; + +interface DescriptionProps { + pageId?: string; + componentId?: string; +} + +export function Description({ pageId = '*', componentId = '*' }: DescriptionProps) { + const elementId = 'description'; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + return ( + <Typography.Caption + className={styles.description} + style={themeStyles} + data-qa-anchor={accessibilityId} + > + {config.text} + </Typography.Caption> + ); +} diff --git a/src/v4/social/elements/Description/index.tsx b/src/v4/social/elements/Description/index.tsx new file mode 100644 index 000000000..6899644e6 --- /dev/null +++ b/src/v4/social/elements/Description/index.tsx @@ -0,0 +1 @@ +export { Description } from './Description'; diff --git a/src/v4/social/elements/ExploreButton/ExploreButton.module.css b/src/v4/social/elements/ExploreButton/ExploreButton.module.css new file mode 100644 index 000000000..5bc061ac1 --- /dev/null +++ b/src/v4/social/elements/ExploreButton/ExploreButton.module.css @@ -0,0 +1,13 @@ +.exploreButton { + background-color: var(--asc-color-base-default); + display: flex; + justify-content: center; + align-items: center; + border-radius: 50%; + width: var(--asc-spacing-l1); + height: var(--asc-spacing-l1); +} + +.exploreButton__icon { + fill: var(--asc-color-secondary-shade4); +} diff --git a/src/v4/social/elements/ExploreButton/ExploreButton.stories.tsx b/src/v4/social/elements/ExploreButton/ExploreButton.stories.tsx new file mode 100644 index 000000000..a16e5aa00 --- /dev/null +++ b/src/v4/social/elements/ExploreButton/ExploreButton.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { ExploreButton } from './ExploreButton'; + +export default { + title: 'v4-social/elements/ExploreButton', +}; + +export const ExploreButtonStory = { + render: () => { + return <ExploreButton />; + }, + + name: 'ExploreButton', +}; diff --git a/src/v4/social/elements/ExploreButton/ExploreButton.tsx b/src/v4/social/elements/ExploreButton/ExploreButton.tsx new file mode 100644 index 000000000..33b98e3c0 --- /dev/null +++ b/src/v4/social/elements/ExploreButton/ExploreButton.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; +import { TabButton } from '~/v4/social/internal-components/TabButton'; + +export interface ExploreButtonProps { + pageId?: string; + componentId?: string; + isActive?: boolean; + onClick?: () => void; +} + +export function ExploreButton({ + pageId = '*', + componentId = '*', + isActive, + onClick, +}: ExploreButtonProps) { + const elementId = 'explore_button'; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + return ( + <TabButton + pageId={pageId} + componentId={componentId} + elementId={elementId} + isActive={isActive} + onClick={() => onClick?.()} + data-qa-anchor={accessibilityId} + > + {config.text} + </TabButton> + ); +} diff --git a/src/v4/social/elements/ExploreButton/index.tsx b/src/v4/social/elements/ExploreButton/index.tsx new file mode 100644 index 000000000..0e994fd39 --- /dev/null +++ b/src/v4/social/elements/ExploreButton/index.tsx @@ -0,0 +1 @@ +export { ExploreButton } from './ExploreButton'; diff --git a/src/v4/social/elements/ExploreCommunitiesButton/ExploreCommunitiesButton.module.css b/src/v4/social/elements/ExploreCommunitiesButton/ExploreCommunitiesButton.module.css new file mode 100644 index 000000000..7941b7403 --- /dev/null +++ b/src/v4/social/elements/ExploreCommunitiesButton/ExploreCommunitiesButton.module.css @@ -0,0 +1,18 @@ +.exploreCommunitiesButton { + display: flex; + padding: 0.625rem 1rem 0.625rem 0.75rem; + justify-content: center; + align-items: center; + gap: 0.5rem; + border-radius: 0.25rem; + background: var(--asc-color-primary-default); + cursor: pointer; +} + +.exploreCommunitiesButton__icon { + fill: var(--asc-color-white); +} + +.exploreCommunitiesButton__text { + color: var(--asc-color-white); +} diff --git a/src/v4/social/elements/ExploreCommunitiesButton/ExploreCommunitiesButton.stories.tsx b/src/v4/social/elements/ExploreCommunitiesButton/ExploreCommunitiesButton.stories.tsx new file mode 100644 index 000000000..2aa274dc3 --- /dev/null +++ b/src/v4/social/elements/ExploreCommunitiesButton/ExploreCommunitiesButton.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { ExploreCommunitiesButton } from './ExploreCommunitiesButton'; + +export default { + title: 'v4-social/elements/ExploreCommunitiesButton', +}; + +export const ExploreCommunitiesButtonStory = { + render: () => { + return <ExploreCommunitiesButton />; + }, + + name: 'ExploreCommunitiesButton', +}; diff --git a/src/v4/social/elements/ExploreCommunitiesButton/ExploreCommunitiesButton.tsx b/src/v4/social/elements/ExploreCommunitiesButton/ExploreCommunitiesButton.tsx new file mode 100644 index 000000000..3aefa5cd0 --- /dev/null +++ b/src/v4/social/elements/ExploreCommunitiesButton/ExploreCommunitiesButton.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; +import { Typography } from '~/v4/core/components'; +import styles from './ExploreCommunitiesButton.module.css'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; + +const Globe = (props: React.SVGProps<SVGSVGElement>) => ( + <svg + width="16" + height="16" + viewBox="0 0 16 16" + fill="none" + xmlns="http://www.w3.org/2000/svg" + {...props} + > + <path d="M7.85254 0.25C12.1338 0.25 15.6025 3.71875 15.6025 8C15.6025 12.2812 12.1338 15.75 7.85254 15.75C3.57129 15.75 0.102539 12.2812 0.102539 8C0.102539 3.71875 3.57129 0.25 7.85254 0.25ZM13.4463 5.25C12.79 3.9375 11.6963 2.90625 10.3525 2.3125C10.79 3.125 11.1338 4.125 11.3525 5.25H13.4463ZM7.85254 1.75C7.25879 1.75 6.32129 3.0625 5.85254 5.25H9.82129C9.35254 3.0625 8.41504 1.75 7.85254 1.75ZM1.60254 8C1.60254 8.4375 1.63379 8.875 1.72754 9.25H4.13379C4.10254 8.84375 4.10254 8.4375 4.10254 8C4.10254 7.59375 4.10254 7.1875 4.13379 6.75H1.72754C1.63379 7.15625 1.60254 7.59375 1.60254 8ZM2.22754 10.75C2.88379 12.0625 3.97754 13.125 5.32129 13.7188C4.88379 12.9062 4.54004 11.9062 4.32129 10.75H2.22754ZM4.32129 5.25C4.54004 4.125 4.88379 3.125 5.32129 2.3125C3.97754 2.90625 2.88379 3.9375 2.22754 5.25H4.32129ZM7.85254 14.25C8.41504 14.25 9.35254 12.9688 9.82129 10.75H5.85254C6.32129 12.9688 7.25879 14.25 7.85254 14.25ZM10.04 9.25C10.0713 8.875 10.1025 8.4375 10.1025 8C10.1025 7.5625 10.0713 7.15625 10.04 6.75H5.63379C5.60254 7.15625 5.60254 7.5625 5.60254 8C5.60254 8.4375 5.60254 8.875 5.63379 9.25H10.04ZM10.3525 13.7188C11.6963 13.125 12.79 12.0625 13.4463 10.75H11.3525C11.1338 11.9062 10.79 12.9062 10.3525 13.7188ZM11.54 9.25H13.9775C14.04 8.875 14.1025 8.4375 14.1025 8C14.1025 7.59375 14.04 7.15625 13.9775 6.75H11.54C11.5713 7.1875 11.6025 7.59375 11.6025 8C11.6025 8.4375 11.5713 8.84375 11.54 9.25Z" /> + </svg> +); + +interface DescriptionProps { + pageId?: string; + componentId?: string; +} + +export function ExploreCommunitiesButton({ pageId = '*', componentId = '*' }: DescriptionProps) { + const elementId = 'explore_communities_button'; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + return ( + <div + className={styles.exploreCommunitiesButton} + data-qa-anchor={accessibilityId} + style={themeStyles} + > + <Globe className={styles.exploreCommunitiesButton__icon} /> + <Typography.BodyBold className={styles.exploreCommunitiesButton__text}> + {config.text} + </Typography.BodyBold> + </div> + ); +} diff --git a/src/v4/social/elements/ExploreCommunitiesButton/index.tsx b/src/v4/social/elements/ExploreCommunitiesButton/index.tsx new file mode 100644 index 000000000..37c8df07f --- /dev/null +++ b/src/v4/social/elements/ExploreCommunitiesButton/index.tsx @@ -0,0 +1 @@ +export { ExploreCommunitiesButton } from './ExploreCommunitiesButton'; diff --git a/src/v4/social/elements/GlobalSearchButton/GlobalSearchButton.module.css b/src/v4/social/elements/GlobalSearchButton/GlobalSearchButton.module.css new file mode 100644 index 000000000..9f2ba0aa0 --- /dev/null +++ b/src/v4/social/elements/GlobalSearchButton/GlobalSearchButton.module.css @@ -0,0 +1,15 @@ +.globalSearchButton { + background-color: var(--asc-color-secondary-shade4); + display: flex; + justify-content: center; + align-items: center; + border-radius: 50%; + width: 2rem; + height: 2rem; +} + +.globalSearchButton__icon { + fill: var(--asc-color-secondary-default); + width: 1rem; + height: 1rem; +} diff --git a/src/v4/social/elements/GlobalSearchButton/GlobalSearchButton.stories.tsx b/src/v4/social/elements/GlobalSearchButton/GlobalSearchButton.stories.tsx new file mode 100644 index 000000000..29322fc1f --- /dev/null +++ b/src/v4/social/elements/GlobalSearchButton/GlobalSearchButton.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { GlobalSearchButton } from './GlobalSearchButton'; + +export default { + title: 'v4-social/elements/GlobalSearchButton', +}; + +export const GlobalSearchButtonStory = { + render: () => { + return <GlobalSearchButton />; + }, + + name: 'GlobalSearchButton', +}; diff --git a/src/v4/social/elements/GlobalSearchButton/GlobalSearchButton.tsx b/src/v4/social/elements/GlobalSearchButton/GlobalSearchButton.tsx new file mode 100644 index 000000000..1f65d87a0 --- /dev/null +++ b/src/v4/social/elements/GlobalSearchButton/GlobalSearchButton.tsx @@ -0,0 +1,73 @@ +import clsx from 'clsx'; +import React from 'react'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { IconComponent } from '~/v4/core/IconComponent'; +import { getDefaultConfig, useCustomization } from '~/v4/core/providers/CustomizationProvider'; +import { useGenerateStylesShadeColors } from '~/v4/core/providers/ThemeProvider'; +import styles from './GlobalSearchButton.module.css'; + +const GlobalSearchSvg = (props: React.SVGProps<SVGSVGElement>) => ( + <svg + width="17" + height="17" + viewBox="0 0 17 17" + fill="none" + xmlns="http://www.w3.org/2000/svg" + {...props} + > + <path d="M15.7814 14.3104C16.0627 14.6229 16.0627 15.0917 15.7502 15.3729L14.8752 16.2479C14.5939 16.5604 14.1252 16.5604 13.8127 16.2479L10.7189 13.1542C10.5627 12.9979 10.5002 12.8104 10.5002 12.6229V12.0917C9.37516 12.9667 8.00016 13.4667 6.50016 13.4667C2.90641 13.4667 0.000162125 10.5604 0.000162125 6.96667C0.000162125 3.40417 2.90641 0.466675 6.50016 0.466675C10.0627 0.466675 13.0002 3.40417 13.0002 6.96667C13.0002 8.49792 12.4689 9.87292 11.6252 10.9667H12.1252C12.3127 10.9667 12.5002 11.0604 12.6564 11.1854L15.7814 14.3104ZM6.50016 10.9667C8.68766 10.9667 10.5002 9.18542 10.5002 6.96667C10.5002 4.77917 8.68766 2.96667 6.50016 2.96667C4.28141 2.96667 2.50016 4.77917 2.50016 6.96667C2.50016 9.18542 4.28141 10.9667 6.50016 10.9667Z" /> + </svg> +); + +export interface GlobalSearchButtonProps { + pageId?: string; + componentId?: string; + defaultClassName?: string; + imgClassName?: string; + onClick?: (e: React.MouseEvent) => void; +} + +export function GlobalSearchButton({ + pageId = '*', + componentId = '*', + defaultClassName, + imgClassName, + onClick, +}: GlobalSearchButtonProps) { + const elementId = 'global_search_button'; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + return ( + <IconComponent + defaultIcon={() => ( + <div + style={themeStyles} + className={styles.globalSearchButton} + onClick={onClick} + data-qa-anchor={accessibilityId} + > + <GlobalSearchSvg className={clsx(styles.globalSearchButton__icon, defaultClassName)} /> + </div> + )} + imgIcon={() => ( + <img + style={themeStyles} + src={config.icon} + alt={uiReference} + className={clsx(styles.globalSearchButton__icon, imgClassName)} + onClick={onClick} + data-qa-anchor={accessibilityId} + /> + )} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} + /> + ); +} diff --git a/src/v4/social/elements/GlobalSearchButton/index.tsx b/src/v4/social/elements/GlobalSearchButton/index.tsx new file mode 100644 index 000000000..15bf518c1 --- /dev/null +++ b/src/v4/social/elements/GlobalSearchButton/index.tsx @@ -0,0 +1 @@ +export { GlobalSearchButton } from './GlobalSearchButton'; diff --git a/src/v4/social/elements/HeaderLabel/HeaderLabel.module.css b/src/v4/social/elements/HeaderLabel/HeaderLabel.module.css new file mode 100644 index 000000000..b96a45f0f --- /dev/null +++ b/src/v4/social/elements/HeaderLabel/HeaderLabel.module.css @@ -0,0 +1,3 @@ +.headerLabel { + color: var(--asc-color-base-default); +} diff --git a/src/v4/social/elements/HeaderLabel/HeaderLabel.stories.tsx b/src/v4/social/elements/HeaderLabel/HeaderLabel.stories.tsx new file mode 100644 index 000000000..33b9280b2 --- /dev/null +++ b/src/v4/social/elements/HeaderLabel/HeaderLabel.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { HeaderLabel } from './HeaderLabel'; + +export default { + title: 'v4-social/elements/HeaderLabel', +}; + +export const HeaderLabelStory = { + render: () => { + return <HeaderLabel />; + }, + + name: 'HeaderLabel', +}; diff --git a/src/v4/social/elements/HeaderLabel/HeaderLabel.tsx b/src/v4/social/elements/HeaderLabel/HeaderLabel.tsx new file mode 100644 index 000000000..6139400e6 --- /dev/null +++ b/src/v4/social/elements/HeaderLabel/HeaderLabel.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import styles from './HeaderLabel.module.css'; +import { Typography } from '~/v4/core/components'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; + +export interface HeaderLabelProps { + pageId?: string; + componentId?: string; +} + +export function HeaderLabel({ pageId = '*', componentId = '*' }: HeaderLabelProps) { + const elementId = 'header_label'; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + return ( + <Typography.Heading + className={styles.headerLabel} + style={themeStyles} + data-qa-anchor={accessibilityId} + > + {config.text} + </Typography.Heading> + ); +} diff --git a/src/v4/social/elements/HeaderLabel/index.tsx b/src/v4/social/elements/HeaderLabel/index.tsx new file mode 100644 index 000000000..3793e798b --- /dev/null +++ b/src/v4/social/elements/HeaderLabel/index.tsx @@ -0,0 +1 @@ +export { HeaderLabel } from './HeaderLabel'; diff --git a/src/v4/social/elements/Illustration/Illustration.stories.tsx b/src/v4/social/elements/Illustration/Illustration.stories.tsx new file mode 100644 index 000000000..d9e6892f5 --- /dev/null +++ b/src/v4/social/elements/Illustration/Illustration.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { Illustration } from './Illustration'; + +export default { + title: 'v4-social/elements/Illustration', +}; + +export const IllustrationStory = { + render: () => { + return <Illustration />; + }, + + name: 'Illustration', +}; diff --git a/src/v4/social/elements/Illustration/Illustration.tsx b/src/v4/social/elements/Illustration/Illustration.tsx new file mode 100644 index 000000000..b01482c30 --- /dev/null +++ b/src/v4/social/elements/Illustration/Illustration.tsx @@ -0,0 +1,184 @@ +import React from 'react'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { IconComponent } from '~/v4/core/IconComponent'; + +const DarkIllustrationSvg = () => ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="161" + height="161" + viewBox="0 0 161 161" + fill="none" + > + <path + d="M130.1 24.5H34.0996C29.6813 24.5 26.0996 28.0817 26.0996 32.5V128.5C26.0996 132.918 29.6813 136.5 34.0996 136.5H130.1C134.518 136.5 138.1 132.918 138.1 128.5V32.5C138.1 28.0817 134.518 24.5 130.1 24.5Z" + fill="#292B32" + /> + <path + d="M43.7002 68.2H44.2002H151.7C152.628 68.2 153.519 68.5688 154.175 69.2251C154.831 69.8815 155.2 70.7718 155.2 71.7V91.7C155.2 92.6283 154.831 93.5185 154.175 94.1749C153.519 94.8313 152.628 95.2 151.7 95.2H43.7002C42.7719 95.2 41.8817 94.8313 41.2253 94.1749C40.5689 93.5185 40.2002 92.6283 40.2002 91.7V71.7C40.2002 70.7718 40.5689 69.8815 41.2253 69.2251C41.8817 68.5688 42.7719 68.2 43.7002 68.2Z" + fill="#292B32" + stroke="#40434E" + /> + <path + opacity="0.3" + d="M90.9002 74.1H70.1002C68.7747 74.1 67.7002 75.1745 67.7002 76.5C67.7002 77.8255 68.7747 78.9 70.1002 78.9H90.9002C92.2257 78.9 93.3002 77.8255 93.3002 76.5C93.3002 75.1745 92.2257 74.1 90.9002 74.1Z" + fill="#40434E" + /> + <path + opacity="0.15" + d="M105.3 84.5H70.1002C68.7747 84.5 67.7002 85.5745 67.7002 86.9C67.7002 88.2255 68.7747 89.3 70.1002 89.3H105.3C106.626 89.3 107.7 88.2255 107.7 86.9C107.7 85.5745 106.626 84.5 105.3 84.5Z" + fill="#40434E" + /> + <path + d="M53.6996 89.3C57.897 89.3 61.2996 85.8974 61.2996 81.7C61.2996 77.5027 57.897 74.1 53.6996 74.1C49.5022 74.1 46.0996 77.5027 46.0996 81.7C46.0996 85.8974 49.5022 89.3 53.6996 89.3Z" + fill="#40434E" + /> + <path + d="M9.2998 102.6H9.7998H117.3C118.228 102.6 119.118 102.969 119.775 103.625C120.431 104.282 120.8 105.172 120.8 106.1V126.1C120.8 127.028 120.431 127.919 119.775 128.575C119.118 129.231 118.228 129.6 117.3 129.6H9.2998C8.37155 129.6 7.48131 129.231 6.82493 128.575C6.16855 127.919 5.7998 127.028 5.7998 126.1V106.1C5.7998 105.172 6.16855 104.282 6.82493 103.625C7.48131 102.969 8.37155 102.6 9.2998 102.6Z" + fill="#292B32" + stroke="#40434E" + /> + <path + opacity="0.3" + d="M56.4998 108.5H35.6998C34.3743 108.5 33.2998 109.575 33.2998 110.9C33.2998 112.225 34.3743 113.3 35.6998 113.3H56.4998C57.8253 113.3 58.8998 112.225 58.8998 110.9C58.8998 109.575 57.8253 108.5 56.4998 108.5Z" + fill="#40434E" + /> + <path + opacity="0.15" + d="M70.8998 118.9H35.6998C34.3743 118.9 33.2998 119.975 33.2998 121.3C33.2998 122.626 34.3743 123.7 35.6998 123.7H70.8998C72.2253 123.7 73.2998 122.626 73.2998 121.3C73.2998 119.975 72.2253 118.9 70.8998 118.9Z" + fill="#40434E" + /> + <path + d="M19.3002 123.7C23.4976 123.7 26.9002 120.297 26.9002 116.1C26.9002 111.903 23.4976 108.5 19.3002 108.5C15.1028 108.5 11.7002 111.903 11.7002 116.1C11.7002 120.297 15.1028 123.7 19.3002 123.7Z" + fill="#40434E" + /> + <path + d="M9.2998 33.8H117.3C119.233 33.8 120.8 35.367 120.8 37.3V57.3C120.8 59.233 119.233 60.8 117.3 60.8H9.2998C7.36681 60.8 5.7998 59.233 5.7998 57.3V37.3C5.7998 35.367 7.36681 33.8 9.2998 33.8Z" + fill="#292B32" + stroke="#40434E" + /> + <path + opacity="0.3" + d="M54.8992 39.7H34.0992C32.7737 39.7 31.6992 40.7745 31.6992 42.1C31.6992 43.4255 32.7737 44.5 34.0992 44.5H54.8992C56.2247 44.5 57.2992 43.4255 57.2992 42.1C57.2992 40.7745 56.2247 39.7 54.8992 39.7Z" + fill="#40434E" + /> + <path + opacity="0.15" + d="M69.2992 50.1H34.0992C32.7737 50.1 31.6992 51.1745 31.6992 52.5C31.6992 53.8255 32.7737 54.9 34.0992 54.9H69.2992C70.6247 54.9 71.6992 53.8255 71.6992 52.5C71.6992 51.1745 70.6247 50.1 69.2992 50.1Z" + fill="#40434E" + /> + <path + d="M19.3002 54.9C23.4976 54.9 26.9002 51.4974 26.9002 47.3C26.9002 43.1026 23.4976 39.7 19.3002 39.7C15.1028 39.7 11.7002 43.1026 11.7002 47.3C11.7002 51.4974 15.1028 54.9 19.3002 54.9Z" + fill="#40434E" + /> + </svg> +); + +const IllustrationSvg = () => ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="161" + height="160" + viewBox="0 0 161 160" + fill="none" + > + <path + d="M130.452 24H34.4521C30.0339 24 26.4521 27.5817 26.4521 32V128C26.4521 132.418 30.0339 136 34.4521 136H130.452C134.87 136 138.452 132.418 138.452 128V32C138.452 27.5817 134.87 24 130.452 24Z" + fill="#EBECEF" + /> + <path + d="M44.0522 67.7H44.5522H152.052C152.981 67.7 153.871 68.0688 154.527 68.7251C155.184 69.3815 155.552 70.2718 155.552 71.2V91.2C155.552 92.1283 155.184 93.0185 154.527 93.6749C153.871 94.3313 152.981 94.7 152.052 94.7H44.0522C43.124 94.7 42.2337 94.3313 41.5774 93.6749C40.921 93.0185 40.5522 92.1283 40.5522 91.2V71.2C40.5522 70.2718 40.921 69.3815 41.5774 68.7251C42.2337 68.0688 43.124 67.7 44.0522 67.7Z" + fill="white" + stroke="#EBECEF" + /> + <path + opacity="0.3" + d="M91.2523 73.6H70.4522C69.1268 73.6 68.0522 74.6745 68.0522 76C68.0522 77.3255 69.1268 78.4 70.4522 78.4H91.2523C92.5777 78.4 93.6523 77.3255 93.6523 76C93.6523 74.6745 92.5777 73.6 91.2523 73.6Z" + fill="#A5A9B5" + /> + <path + opacity="0.15" + d="M105.652 84H70.4522C69.1268 84 68.0522 85.0745 68.0522 86.4C68.0522 87.7255 69.1268 88.8 70.4522 88.8H105.652C106.978 88.8 108.052 87.7255 108.052 86.4C108.052 85.0745 106.978 84 105.652 84Z" + fill="#A5A9B5" + /> + <path + d="M54.0521 88.8C58.2495 88.8 61.6521 85.3974 61.6521 81.2C61.6521 77.0027 58.2495 73.6 54.0521 73.6C49.8548 73.6 46.4521 77.0027 46.4521 81.2C46.4521 85.3974 49.8548 88.8 54.0521 88.8Z" + fill="#A5A9B5" + /> + <path + d="M9.65234 102.1H10.1523H117.652C118.581 102.1 119.471 102.469 120.127 103.125C120.784 103.782 121.152 104.672 121.152 105.6V125.6C121.152 126.528 120.784 127.419 120.127 128.075C119.471 128.731 118.581 129.1 117.652 129.1H9.65234C8.72409 129.1 7.83385 128.731 7.17747 128.075C6.52109 127.419 6.15234 126.528 6.15234 125.6V105.6C6.15234 104.672 6.52109 103.782 7.17747 103.125C7.83385 102.469 8.72409 102.1 9.65234 102.1Z" + fill="white" + stroke="#EBECEF" + /> + <path + opacity="0.3" + d="M56.8523 108H36.0523C34.7269 108 33.6523 109.075 33.6523 110.4C33.6523 111.725 34.7269 112.8 36.0523 112.8H56.8523C58.1778 112.8 59.2523 111.725 59.2523 110.4C59.2523 109.075 58.1778 108 56.8523 108Z" + fill="#A5A9B5" + /> + <path + opacity="0.15" + d="M71.2523 118.4H36.0523C34.7269 118.4 33.6523 119.475 33.6523 120.8C33.6523 122.126 34.7269 123.2 36.0523 123.2H71.2523C72.5778 123.2 73.6523 122.126 73.6523 120.8C73.6523 119.475 72.5778 118.4 71.2523 118.4Z" + fill="#A5A9B5" + /> + <path + d="M19.6522 123.2C23.8496 123.2 27.2522 119.797 27.2522 115.6C27.2522 111.403 23.8496 108 19.6522 108C15.4549 108 12.0522 111.403 12.0522 115.6C12.0522 119.797 15.4549 123.2 19.6522 123.2Z" + fill="#A5A9B5" + /> + <path + d="M9.65234 33.3H117.652C119.585 33.3 121.152 34.867 121.152 36.8V56.8C121.152 58.733 119.585 60.3 117.652 60.3H9.65234C7.71935 60.3 6.15234 58.733 6.15234 56.8V36.8C6.15234 34.867 7.71935 33.3 9.65234 33.3Z" + fill="white" + stroke="#EBECEF" + /> + <path + opacity="0.3" + d="M55.2522 39.2H34.4522C33.1268 39.2 32.0522 40.2745 32.0522 41.6C32.0522 42.9255 33.1268 44 34.4522 44H55.2522C56.5777 44 57.6522 42.9255 57.6522 41.6C57.6522 40.2745 56.5777 39.2 55.2522 39.2Z" + fill="#A5A9B5" + /> + <path + opacity="0.15" + d="M69.6522 49.6H34.4522C33.1268 49.6 32.0522 50.6745 32.0522 52C32.0522 53.3255 33.1268 54.4 34.4522 54.4H69.6522C70.9777 54.4 72.0522 53.3255 72.0522 52C72.0522 50.6745 70.9777 49.6 69.6522 49.6Z" + fill="#A5A9B5" + /> + <path + d="M19.6522 54.4C23.8496 54.4 27.2522 50.9974 27.2522 46.8C27.2522 42.6026 23.8496 39.2 19.6522 39.2C15.4549 39.2 12.0522 42.6026 12.0522 46.8C12.0522 50.9974 15.4549 54.4 19.6522 54.4Z" + fill="#A5A9B5" + /> + </svg> +); + +interface IllustrationProps { + pageId?: string; + componentId?: string; +} + +export const Illustration = ({ pageId = '*', componentId = '*' }: IllustrationProps) => { + const elementId = 'illustration'; + const { + currentTheme, + accessibilityId, + config, + defaultConfig, + isExcluded, + uiReference, + themeStyles, + } = useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + return ( + <IconComponent + defaultIconName={defaultConfig.icon} + configIconName={config.icon} + imgIcon={() => <img src={config.icon} alt={uiReference} data-qa-anchor={accessibilityId} />} + defaultIcon={() => ( + <div data-qa-anchor={accessibilityId}> + {currentTheme === 'light' ? <IllustrationSvg /> : <DarkIllustrationSvg />} + </div> + )} + /> + ); +}; diff --git a/src/v4/social/elements/Illustration/index.tsx b/src/v4/social/elements/Illustration/index.tsx new file mode 100644 index 000000000..c334ab5f9 --- /dev/null +++ b/src/v4/social/elements/Illustration/index.tsx @@ -0,0 +1 @@ +export { Illustration } from './Illustration'; diff --git a/src/v4/social/elements/MyCommunitiesButton/MyCommunitiesButton.module.css b/src/v4/social/elements/MyCommunitiesButton/MyCommunitiesButton.module.css new file mode 100644 index 000000000..5dff8c329 --- /dev/null +++ b/src/v4/social/elements/MyCommunitiesButton/MyCommunitiesButton.module.css @@ -0,0 +1,13 @@ +.myCommunitiesButton { + background-color: var(--asc-color-base-default); + display: flex; + justify-content: center; + align-items: center; + border-radius: 50%; + width: var(--asc-spacing-l1); + height: var(--asc-spacing-l1); +} + +.myCommunitiesButton__icon { + fill: var(--asc-color-secondary-shade4); +} diff --git a/src/v4/social/elements/MyCommunitiesButton/MyCommunitiesButton.stories.tsx b/src/v4/social/elements/MyCommunitiesButton/MyCommunitiesButton.stories.tsx new file mode 100644 index 000000000..0643fcff2 --- /dev/null +++ b/src/v4/social/elements/MyCommunitiesButton/MyCommunitiesButton.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { MyCommunitiesButton } from './MyCommunitiesButton'; + +export default { + title: 'v4-social/elements/MyCommunitiesButton', +}; + +export const MyCommunitiesButtonStory = { + render: () => { + return <MyCommunitiesButton />; + }, + + name: 'MyCommunitiesButton', +}; diff --git a/src/v4/social/elements/MyCommunitiesButton/MyCommunitiesButton.tsx b/src/v4/social/elements/MyCommunitiesButton/MyCommunitiesButton.tsx new file mode 100644 index 000000000..80fd99320 --- /dev/null +++ b/src/v4/social/elements/MyCommunitiesButton/MyCommunitiesButton.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; +import { TabButton } from '~/v4/social/internal-components/TabButton'; + +export interface MyCommunitiesButtonProps { + pageId?: string; + componentId?: string; + isActive?: boolean; + onClick?: () => void; +} + +export function MyCommunitiesButton({ + pageId = '*', + componentId = '*', + isActive, + onClick, +}: MyCommunitiesButtonProps) { + const elementId = 'my_communities_button'; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + return ( + <TabButton + data-qa-anchor={accessibilityId} + pageId={pageId} + componentId={componentId} + elementId={elementId} + isActive={isActive} + onClick={() => onClick?.()} + > + {config.text} + </TabButton> + ); +} diff --git a/src/v4/social/elements/MyCommunitiesButton/index.tsx b/src/v4/social/elements/MyCommunitiesButton/index.tsx new file mode 100644 index 000000000..81e1d2e92 --- /dev/null +++ b/src/v4/social/elements/MyCommunitiesButton/index.tsx @@ -0,0 +1 @@ +export { MyCommunitiesButton } from './MyCommunitiesButton'; diff --git a/src/v4/social/elements/NewsfeedButton/NewsfeedButton.module.css b/src/v4/social/elements/NewsfeedButton/NewsfeedButton.module.css new file mode 100644 index 000000000..55e1f12a5 --- /dev/null +++ b/src/v4/social/elements/NewsfeedButton/NewsfeedButton.module.css @@ -0,0 +1,13 @@ +.newsfeedButton { + background-color: var(--asc-color-base-default); + display: flex; + justify-content: center; + align-items: center; + border-radius: 50%; + width: var(--asc-spacing-l1); + height: var(--asc-spacing-l1); +} + +.newsfeedButton__icon { + fill: var(--asc-color-secondary-shade4); +} diff --git a/src/v4/social/elements/NewsfeedButton/NewsfeedButton.stories.tsx b/src/v4/social/elements/NewsfeedButton/NewsfeedButton.stories.tsx new file mode 100644 index 000000000..d6c27160f --- /dev/null +++ b/src/v4/social/elements/NewsfeedButton/NewsfeedButton.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { NewsfeedButton } from './NewsfeedButton'; + +export default { + title: 'v4-social/elements/NewsfeedButton', +}; + +export const NewsfeedButtonStory = { + render: () => { + return <NewsfeedButton />; + }, + + name: 'NewsfeedButton', +}; diff --git a/src/v4/social/elements/NewsfeedButton/NewsfeedButton.tsx b/src/v4/social/elements/NewsfeedButton/NewsfeedButton.tsx new file mode 100644 index 000000000..9a017559f --- /dev/null +++ b/src/v4/social/elements/NewsfeedButton/NewsfeedButton.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; +import { TabButton } from '~/v4/social/internal-components/TabButton'; + +export interface NewsfeedButtonProps { + pageId?: string; + componentId?: string; + isActive?: boolean; + onClick?: () => void; +} + +export function NewsfeedButton({ + pageId = '*', + componentId = '*', + isActive, + onClick, +}: NewsfeedButtonProps) { + const elementId = 'newsfeed_button'; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + return ( + <TabButton + data-qa-anchor={accessibilityId} + pageId={pageId} + componentId={componentId} + elementId={elementId} + isActive={isActive} + onClick={() => onClick?.()} + > + {config.text} + </TabButton> + ); +} diff --git a/src/v4/social/elements/NewsfeedButton/index.tsx b/src/v4/social/elements/NewsfeedButton/index.tsx new file mode 100644 index 000000000..f975a51bd --- /dev/null +++ b/src/v4/social/elements/NewsfeedButton/index.tsx @@ -0,0 +1 @@ +export { NewsfeedButton } from './NewsfeedButton'; diff --git a/src/v4/social/elements/PostCreationButton/PostCreationButton.module.css b/src/v4/social/elements/PostCreationButton/PostCreationButton.module.css new file mode 100644 index 000000000..fcf6abee5 --- /dev/null +++ b/src/v4/social/elements/PostCreationButton/PostCreationButton.module.css @@ -0,0 +1,15 @@ +.postCreationButton { + background-color: var(--asc-color-secondary-shade4); + display: flex; + justify-content: center; + align-items: center; + border-radius: 50%; + width: 2rem; + height: 2rem; +} + +.postCreationButton__icon { + fill: var(--asc-color-secondary-default); + width: 1rem; + height: 1rem; +} diff --git a/src/v4/social/elements/PostCreationButton/PostCreationButton.stories.tsx b/src/v4/social/elements/PostCreationButton/PostCreationButton.stories.tsx new file mode 100644 index 000000000..63a4b1aa9 --- /dev/null +++ b/src/v4/social/elements/PostCreationButton/PostCreationButton.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { PostCreationButton } from './PostCreationButton'; + +export default { + title: 'v4-social/elements/PostCreationButton', +}; + +export const PostCreationButtonStory = { + render: () => { + return <PostCreationButton />; + }, + + name: 'PostCreationButton', +}; diff --git a/src/v4/social/elements/PostCreationButton/PostCreationButton.tsx b/src/v4/social/elements/PostCreationButton/PostCreationButton.tsx new file mode 100644 index 000000000..86030ad00 --- /dev/null +++ b/src/v4/social/elements/PostCreationButton/PostCreationButton.tsx @@ -0,0 +1,74 @@ +import clsx from 'clsx'; +import React from 'react'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { IconComponent } from '~/v4/core/IconComponent'; +import { getDefaultConfig, useCustomization } from '~/v4/core/providers/CustomizationProvider'; +import { useGenerateStylesShadeColors } from '~/v4/core/providers/ThemeProvider'; +import styles from './PostCreationButton.module.css'; + +const PostCreationButtonSvg = (props: React.SVGProps<SVGSVGElement>) => ( + <svg + width="14" + height="15" + viewBox="0 0 14 15" + fill="none" + xmlns="http://www.w3.org/2000/svg" + {...props} + > + <path d="M13.0002 5.96667C13.5314 5.96667 14.0002 6.43542 14.0002 6.96667V7.96667C14.0002 8.52917 13.5314 8.96667 13.0002 8.96667H8.50016V13.4667C8.50016 14.0292 8.03141 14.4667 7.50016 14.4667H6.50016C5.93766 14.4667 5.50016 14.0292 5.50016 13.4667V8.96667H1.00016C0.437662 8.96667 0.000162125 8.52917 0.000162125 7.96667V6.96667C0.000162125 6.43542 0.437662 5.96667 1.00016 5.96667H5.50016V1.46667C5.50016 0.935425 5.93766 0.466675 6.50016 0.466675H7.50016C8.03141 0.466675 8.50016 0.935425 8.50016 1.46667V5.96667H13.0002Z" /> + </svg> +); + +export interface PostCreationButtonProps { + pageId?: string; + componentId?: string; + defaultClassName?: string; + imgClassName?: string; + onClick?: (e: React.MouseEvent) => void; +} + +export function PostCreationButton({ + pageId = '*', + componentId = '*', + defaultClassName, + imgClassName, + onClick, +}: PostCreationButtonProps) { + const elementId = 'post_creation_button'; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + return ( + <IconComponent + defaultIcon={() => ( + <div + style={themeStyles} + className={styles.postCreationButton} + onClick={onClick} + data-qa-anchor={accessibilityId} + > + <PostCreationButtonSvg + className={clsx(styles.postCreationButton__icon, defaultClassName)} + /> + </div> + )} + imgIcon={() => ( + <img + src={config.icon} + alt={uiReference} + className={clsx(styles.postCreationButton, imgClassName)} + onClick={onClick} + data-qa-anchor={accessibilityId} + /> + )} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} + /> + ); +} diff --git a/src/v4/social/elements/PostCreationButton/index.tsx b/src/v4/social/elements/PostCreationButton/index.tsx new file mode 100644 index 000000000..fdac36a62 --- /dev/null +++ b/src/v4/social/elements/PostCreationButton/index.tsx @@ -0,0 +1 @@ +export { PostCreationButton } from './PostCreationButton'; diff --git a/src/v4/social/elements/Title/Title.module.css b/src/v4/social/elements/Title/Title.module.css new file mode 100644 index 000000000..57e4598ff --- /dev/null +++ b/src/v4/social/elements/Title/Title.module.css @@ -0,0 +1,4 @@ +.title { + color: var(--asc-color-base-shade3); + text-align: center; +} diff --git a/src/v4/social/elements/Title/Title.stories.tsx b/src/v4/social/elements/Title/Title.stories.tsx new file mode 100644 index 000000000..9535dbb52 --- /dev/null +++ b/src/v4/social/elements/Title/Title.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { Title } from './Title'; + +export default { + title: 'v4-social/elements/Title', +}; + +export const TitleStory = { + render: () => { + return <Title />; + }, + + name: 'Title', +}; diff --git a/src/v4/social/elements/Title/Title.tsx b/src/v4/social/elements/Title/Title.tsx new file mode 100644 index 000000000..925a39a84 --- /dev/null +++ b/src/v4/social/elements/Title/Title.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import styles from './Title.module.css'; +import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; +import { Typography } from '~/v4/core/components'; +import { useGenerateStylesShadeColors } from '~/v4/core/providers/ThemeProvider'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; + +interface TitleProps { + pageId?: string; + componentId?: string; +} + +export function Title({ pageId = '*', componentId = '*' }: TitleProps) { + const elementId = 'title'; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + return ( + <Typography.Title className={styles.title} style={themeStyles} data-qa-anchor={accessibilityId}> + {config.text} + </Typography.Title> + ); +} diff --git a/src/v4/social/elements/Title/index.tsx b/src/v4/social/elements/Title/index.tsx new file mode 100644 index 000000000..177576b64 --- /dev/null +++ b/src/v4/social/elements/Title/index.tsx @@ -0,0 +1 @@ +export { Title } from './Title'; diff --git a/src/v4/social/internal-components/TabButton/TabButton.module.css b/src/v4/social/internal-components/TabButton/TabButton.module.css new file mode 100644 index 000000000..e058e951c --- /dev/null +++ b/src/v4/social/internal-components/TabButton/TabButton.module.css @@ -0,0 +1,22 @@ +.tabButton { + display: flex; + align-items: center; + padding: var(--asc-spacing-s1) var(--asc-spacing-s2); + background: var(--asc-color-base-background, #fff); + border: 1px solid var(--asc-color-base-shade4); + border-radius: 1.5rem; + cursor: pointer; + color: var(--asc-color-base-shade1); + white-space: nowrap; +} + +.tabButton[data-active='true'] { + font-weight: 600; + color: var(--asc-color-white); + background-color: var(--asc-color-primary-default); + border: 1px solid transparent; +} + +.tabButton__text { + font-size: 1.0625rem; +} diff --git a/src/v4/social/internal-components/TabButton/TabButton.stories.tsx b/src/v4/social/internal-components/TabButton/TabButton.stories.tsx new file mode 100644 index 000000000..0deadf01b --- /dev/null +++ b/src/v4/social/internal-components/TabButton/TabButton.stories.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { TabButton } from './TabButton'; + +export default { + title: 'v4-social/internal-components/TabButton', +}; + +export const TabButtonStory = { + render: () => { + return <TabButton>Tab Name</TabButton>; + }, +}; diff --git a/src/v4/social/internal-components/TabButton/TabButton.tsx b/src/v4/social/internal-components/TabButton/TabButton.tsx new file mode 100644 index 000000000..120d7ae0d --- /dev/null +++ b/src/v4/social/internal-components/TabButton/TabButton.tsx @@ -0,0 +1,50 @@ +import React, { ReactNode } from 'react'; + +import { Typography } from '~/v4/core/components'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; + +import styles from './TabButton.module.css'; + +export interface TabButtonProps extends React.HTMLAttributes<HTMLButtonElement> { + pageId?: string; + componentId?: string; + elementId?: string; + children: ReactNode; + isActive?: boolean; +} + +export function TabButton({ + pageId = '*', + componentId = '*', + elementId = '*', + children, + isActive = false, + ...rest +}: TabButtonProps) { + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityElement({ + pageId, + componentId, + elementId, + }); + + return ( + <button + style={themeStyles} + className={styles.tabButton} + data-active={isActive} + data-qa-anchor={accessibilityId} + {...rest} + > + {isActive ? ( + <Typography.Title data-active={isActive} className={styles.tabButton__text}> + {children} + </Typography.Title> + ) : ( + <Typography.Caption data-active={isActive} className={styles.tabButton__text}> + {children} + </Typography.Caption> + )} + </button> + ); +} diff --git a/src/v4/social/internal-components/TabButton/index.tsx b/src/v4/social/internal-components/TabButton/index.tsx new file mode 100644 index 000000000..0a8a06ff0 --- /dev/null +++ b/src/v4/social/internal-components/TabButton/index.tsx @@ -0,0 +1 @@ +export { TabButton } from './TabButton'; diff --git a/src/v4/social/pages/SocialHomePage/SocialHomePage.module.css b/src/v4/social/pages/SocialHomePage/SocialHomePage.module.css new file mode 100644 index 000000000..d4f63f76d --- /dev/null +++ b/src/v4/social/pages/SocialHomePage/SocialHomePage.module.css @@ -0,0 +1,38 @@ +.socialHomePage { + display: flex; + flex-direction: column; + height: 3.625rem; + width: 100%; + background-color: var(--asc-color-base-background); +} + +.socialHomePage__topBar { + width: 100%; + padding-left: 1rem; + padding-right: 1rem; + background-color: var(--asc-color-base-background); +} + +.socialHomePage__tabs { + display: inline-flex; + flex-wrap: nowrap; + gap: 0.5rem; + overflow-y: scroll; + width: 100%; + padding: 0.75rem 0; + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ +} + +.socialHomePage__tabs::-webkit-scrollbar { + display: none; +} + +.socialHomePage__contents { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 0.5rem; + flex: 1; +} diff --git a/src/v4/social/pages/SocialHomePage/SocialHomePage.stories.tsx b/src/v4/social/pages/SocialHomePage/SocialHomePage.stories.tsx new file mode 100644 index 000000000..3cf2ef890 --- /dev/null +++ b/src/v4/social/pages/SocialHomePage/SocialHomePage.stories.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { SocialHomePage } from './SocialHomePage'; + +export default { + title: 'v4-social/pages/SocialHomePage', +}; + +export const SocialHomePageStories = { + render: () => { + return <SocialHomePage />; + }, +}; diff --git a/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx b/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx new file mode 100644 index 000000000..220787c36 --- /dev/null +++ b/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx @@ -0,0 +1,50 @@ +import React, { useState } from 'react'; +import styles from './SocialHomePage.module.css'; + +import { TopNavigation } from '~/v4/social/components/TopNavigation'; +import { NewsfeedButton } from '~/v4/social/elements/NewsfeedButton'; +import { ExploreButton } from '~/v4/social/elements/ExploreButton'; +import { MyCommunitiesButton } from '~/v4/social/elements/MyCommunitiesButton'; +import { Newsfeed } from '~/v4/social/components/Newsfeed'; +import { useAmityPage } from '~/v4/core/hooks/uikit'; + +enum EnumTabNames { + Newsfeed = 'Newsfeed', + Explore = 'Explore', + MyCommunities = 'My communities', +} + +export function SocialHomePage() { + const pageId = 'social_home_page'; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityPage({ + pageId, + }); + + const [activeTab, setActiveTab] = useState(EnumTabNames.Newsfeed); + + return ( + <div className={styles.socialHomePage} style={themeStyles}> + <div className={styles.socialHomePage__topBar}> + <TopNavigation pageId={pageId} /> + <div className={styles.socialHomePage__tabs}> + <NewsfeedButton + pageId={pageId} + isActive={activeTab === EnumTabNames.Newsfeed} + onClick={() => setActiveTab(EnumTabNames.Newsfeed)} + /> + <ExploreButton pageId={pageId} isActive={activeTab === EnumTabNames.Explore} /> + <MyCommunitiesButton + pageId={pageId} + isActive={activeTab === EnumTabNames.MyCommunities} + onClick={() => setActiveTab(EnumTabNames.MyCommunities)} + /> + </div> + </div> + <div className={styles.socialHomePage__contents}> + {activeTab === EnumTabNames.Newsfeed && <Newsfeed pageId={pageId} />} + {activeTab === EnumTabNames.Explore && <div>Explore</div>} + </div> + </div> + ); +} diff --git a/src/v4/social/pages/SocialHomePage/index.tsx b/src/v4/social/pages/SocialHomePage/index.tsx new file mode 100644 index 000000000..71c95f184 --- /dev/null +++ b/src/v4/social/pages/SocialHomePage/index.tsx @@ -0,0 +1 @@ +export { SocialHomePage } from './SocialHomePage'; From c81ad9efcd597a02aae0bd91e83128b889f29987 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Wed, 12 Jun 2024 19:39:09 +0700 Subject: [PATCH 114/300] feat: SocialGlobalSearchPage (#395) --- .../collections/useCommunitiesCollection.ts | 20 ++++ .../UserSearchResult.module.css | 42 ++++++++ .../UserSearchResult.stories.tsx | 20 ++++ .../UserSearchResult/UserSearchResult.tsx | 50 ++++++++++ .../components/UserSearchResult/index.tsx | 1 + .../social/hooks/useUserQueryByDisplayName.ts | 56 +++++++++++ .../TabsBar/TabsBar.module.css | 28 ++++++ .../TabsBar/TabsBar.stories.tsx | 22 +++++ .../internal-components/TabsBar/TabsBar.tsx | 52 ++++++++++ .../internal-components/TabsBar/index.tsx | 1 + .../SocialGlobalSearchPage.module.css | 24 +++++ .../SocialGlobalSearchPage.stories.tsx | 12 +++ .../SocialGlobalSearchPage.tsx | 98 +++++++++++++++++++ .../pages/SocialGlobalSearchPage/index.tsx | 1 + 14 files changed, 427 insertions(+) create mode 100644 src/v4/core/hooks/collections/useCommunitiesCollection.ts create mode 100644 src/v4/social/components/UserSearchResult/UserSearchResult.module.css create mode 100644 src/v4/social/components/UserSearchResult/UserSearchResult.stories.tsx create mode 100644 src/v4/social/components/UserSearchResult/UserSearchResult.tsx create mode 100644 src/v4/social/components/UserSearchResult/index.tsx create mode 100644 src/v4/social/hooks/useUserQueryByDisplayName.ts create mode 100644 src/v4/social/internal-components/TabsBar/TabsBar.module.css create mode 100644 src/v4/social/internal-components/TabsBar/TabsBar.stories.tsx create mode 100644 src/v4/social/internal-components/TabsBar/TabsBar.tsx create mode 100644 src/v4/social/internal-components/TabsBar/index.tsx create mode 100644 src/v4/social/pages/SocialGlobalSearchPage/SocialGlobalSearchPage.module.css create mode 100644 src/v4/social/pages/SocialGlobalSearchPage/SocialGlobalSearchPage.stories.tsx create mode 100644 src/v4/social/pages/SocialGlobalSearchPage/SocialGlobalSearchPage.tsx create mode 100644 src/v4/social/pages/SocialGlobalSearchPage/index.tsx diff --git a/src/v4/core/hooks/collections/useCommunitiesCollection.ts b/src/v4/core/hooks/collections/useCommunitiesCollection.ts new file mode 100644 index 000000000..a8329461f --- /dev/null +++ b/src/v4/core/hooks/collections/useCommunitiesCollection.ts @@ -0,0 +1,20 @@ +import { CommunityRepository } from '@amityco/ts-sdk'; +import useLiveCollection from '~/core/hooks/useLiveCollection'; + +// breaking changes + +export default function useCommunitiesCollection( + queryParams?: Parameters<typeof CommunityRepository.getCommunities>[0], + shouldCall?: () => boolean, +) { + const { items, ...rest } = useLiveCollection({ + fetcher: CommunityRepository.getCommunities, + params: queryParams as Parameters<typeof CommunityRepository.getCommunities>[0], + shouldCall: () => !!queryParams && (shouldCall ? shouldCall?.() : true), + }); + + return { + communities: items, + ...rest, + }; +} diff --git a/src/v4/social/components/UserSearchResult/UserSearchResult.module.css b/src/v4/social/components/UserSearchResult/UserSearchResult.module.css new file mode 100644 index 000000000..bec91a812 --- /dev/null +++ b/src/v4/social/components/UserSearchResult/UserSearchResult.module.css @@ -0,0 +1,42 @@ +.userSearchResult { + display: flex; + flex-direction: column; + justify-content: center; + align-items: start; + width: 100%; + background-color: var(--asc-color-base-background); +} + +.userSearchResult__userItem { + display: grid; + grid-template-columns: 3.75rem minmax(0, 1fr); + padding-top: 0.5rem; + padding-bottom: 0.5rem; + width: 100%; +} + +.userSearchResult__userItem__leftPane { + display: flex; + justify-content: center; + align-items: center; +} + +.userSearchResult__userItem__rightPane { + display: flex; + justify-content: center; + align-items: center; + width: 100%; +} + +.userSearchResult__userItem__avatar { + width: 2.5rem; + height: 2.5rem; +} + +.userItem__userName { + display: flex; + justify-content: start; + align-items: last baseline; + gap: 0.25rem; + width: 100%; +} diff --git a/src/v4/social/components/UserSearchResult/UserSearchResult.stories.tsx b/src/v4/social/components/UserSearchResult/UserSearchResult.stories.tsx new file mode 100644 index 000000000..5bea1f1f9 --- /dev/null +++ b/src/v4/social/components/UserSearchResult/UserSearchResult.stories.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import useOneUser from '~/mock/useOneUser'; + +import { UserSearchResult } from './UserSearchResult'; + +export default { + title: 'v4-social/components/UserSearchResult', +}; + +export const UserSearchResultStory = { + render: () => { + const user = useOneUser(); + + if (user == null) return null; + + return <UserSearchResult userCollection={[user]} onLoadMore={() => {}} />; + }, + + name: 'UserSearchResult', +}; diff --git a/src/v4/social/components/UserSearchResult/UserSearchResult.tsx b/src/v4/social/components/UserSearchResult/UserSearchResult.tsx new file mode 100644 index 000000000..5c1e4e6c7 --- /dev/null +++ b/src/v4/social/components/UserSearchResult/UserSearchResult.tsx @@ -0,0 +1,50 @@ +import React, { useRef } from 'react'; +import styles from './UserSearchResult.module.css'; +import useIntersectionObserver from '~/v4/core/hooks/useIntersectionObserver'; +import { useAmityComponent } from '~/v4/core/hooks/uikit'; +import { UserAvatar } from '../../internal-components/UserAvatar/UserAvatar'; +import { Typography } from '~/v4/core/components/index'; + +interface UserSearchResultProps { + pageId?: string; + userCollection: Amity.User[]; + onLoadMore: () => void; +} + +export const UserSearchResult = ({ + pageId = '*', + userCollection = [], + onLoadMore, +}: UserSearchResultProps) => { + const componentId = 'user_search_result'; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityComponent({ + pageId, + componentId, + }); + + const intersectionRef = useRef<HTMLDivElement>(null); + + useIntersectionObserver({ onIntersect: () => onLoadMore(), ref: intersectionRef }); + + return ( + <div className={styles.userSearchResult} style={themeStyles}> + {userCollection.map((user) => ( + <div key={user.userId} className={styles.userSearchResult__userItem}> + <div className={styles.userSearchResult__userItem__leftPane}> + <UserAvatar + userId={user.userId} + className={styles.userSearchResult__userItem__avatar} + /> + </div> + <div className={styles.userSearchResult__userItem__rightPane}> + <div className={styles.userItem__userName}> + <Typography.BodyBold>{user.displayName}</Typography.BodyBold> + </div> + </div> + </div> + ))} + <div ref={intersectionRef} /> + </div> + ); +}; diff --git a/src/v4/social/components/UserSearchResult/index.tsx b/src/v4/social/components/UserSearchResult/index.tsx new file mode 100644 index 000000000..67c5b0f81 --- /dev/null +++ b/src/v4/social/components/UserSearchResult/index.tsx @@ -0,0 +1 @@ +export { UserSearchResult } from './UserSearchResult'; diff --git a/src/v4/social/hooks/useUserQueryByDisplayName.ts b/src/v4/social/hooks/useUserQueryByDisplayName.ts new file mode 100644 index 000000000..ab8916988 --- /dev/null +++ b/src/v4/social/hooks/useUserQueryByDisplayName.ts @@ -0,0 +1,56 @@ +import { UserRepository } from '@amityco/ts-sdk'; +import { useEffect, useRef, useState } from 'react'; + +const MINIMUM_STRING_LENGTH_TO_TRIGGER_QUERY = 1; + +export const useUserQueryByDisplayName = ( + displayName: string, + minLength: number = MINIMUM_STRING_LENGTH_TO_TRIGGER_QUERY, +) => { + const [items, setItems] = useState<Amity.User[]>([]); + const [isLoading, setIsLoading] = useState(false); + const [hasMore, setHasMore] = useState(false); + const [loadMoreHasBeenCalled, setLoadMoreHasBeenCalled] = useState(false); + const loadMoreRef = useRef<(() => void) | null>(null); + const unSubRef = useRef<(() => void) | null>(null); + + const loadMore = () => { + if (loadMoreRef.current) { + loadMoreRef.current(); + setLoadMoreHasBeenCalled(true); + } + }; + + useEffect(() => { + if (displayName.length < minLength) return; + + if (unSubRef.current) { + unSubRef.current(); + unSubRef.current = null; + } + + const unSubFn = UserRepository.searchUserByDisplayName( + { displayName, limit: 10 }, + (response) => { + setHasMore(response.hasNextPage || false); + setIsLoading(response.loading); + loadMoreRef.current = response.onNextPage || null; + setItems(response.data); + }, + ); + unSubRef.current = unSubFn; + + return () => { + unSubRef.current?.(); + unSubRef.current = null; + }; + }, [displayName, minLength]); + + return { + users: items, + isLoading, + hasMore, + loadMore, + loadMoreHasBeenCalled, + }; +}; diff --git a/src/v4/social/internal-components/TabsBar/TabsBar.module.css b/src/v4/social/internal-components/TabsBar/TabsBar.module.css new file mode 100644 index 000000000..a377b2e4d --- /dev/null +++ b/src/v4/social/internal-components/TabsBar/TabsBar.module.css @@ -0,0 +1,28 @@ +button, +fieldset, +input { + all: unset; +} + +.tabsRoot { + display: flex; + flex-direction: column; + background-color: var(--asc-color-base-background); +} + +.tabsList { + display: flex; + flex-shrink: 0; + border-bottom: 1px solid var(--asc-color-base-shade4); + gap: 1.25rem; +} + +.tabsTrigger { + height: 3rem; + color: var(--asc-color-base-shade3); +} + +.tabsTrigger[data-state='active'] { + color: var(--asc-color-primary-default); + border-bottom: 0.125rem solid var(--asc-color-primary-default); +} diff --git a/src/v4/social/internal-components/TabsBar/TabsBar.stories.tsx b/src/v4/social/internal-components/TabsBar/TabsBar.stories.tsx new file mode 100644 index 000000000..03784411d --- /dev/null +++ b/src/v4/social/internal-components/TabsBar/TabsBar.stories.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { TabsBar } from './TabsBar'; + +export default { + title: 'v4-social/internal-components/TabsBar', +}; + +export const TabsBarStory = { + render: () => { + const tabs = [ + { label: 'tab1', value: 'tab1', content: () => 'Tab 1' }, + { label: 'tab2', value: 'tab2', content: () => 'Tab 2' }, + { label: 'tab3', value: 'tab3', content: () => 'Tab 3' }, + ]; + + const [activeTab, setActiveTab] = React.useState('tab1'); + + return ( + <TabsBar activeTab={activeTab} tabs={tabs} onTabChange={(newTab) => setActiveTab(newTab)} /> + ); + }, +}; diff --git a/src/v4/social/internal-components/TabsBar/TabsBar.tsx b/src/v4/social/internal-components/TabsBar/TabsBar.tsx new file mode 100644 index 000000000..8556a44d3 --- /dev/null +++ b/src/v4/social/internal-components/TabsBar/TabsBar.tsx @@ -0,0 +1,52 @@ +import React, { ReactNode } from 'react'; +import * as Tabs from '@radix-ui/react-tabs'; +import { Typography } from '~/v4/core/components'; +import styles from './TabsBar.module.css'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; + +interface TabsBarProps { + pageId?: string; + componentId?: string; + elementId?: string; + tabs: { value: string; label: string; content: () => ReactNode }[]; + activeTab: string; + onTabChange: (tabName: string) => void; +} + +export const TabsBar = ({ + tabs, + activeTab, + onTabChange, + pageId = '*', + componentId = '*', + elementId = '*', +}: TabsBarProps) => { + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityElement({ + pageId, + componentId, + elementId, + }); + + return ( + <Tabs.Root + style={themeStyles} + className={styles.tabsRoot} + value={activeTab} + onValueChange={(newValue) => onTabChange(newValue)} + > + <Tabs.List className={styles.tabsList}> + {tabs.map((tab) => ( + <Tabs.Trigger value={tab.value} className={styles.tabsTrigger}> + <Typography.Title>{tab.label}</Typography.Title> + </Tabs.Trigger> + ))} + </Tabs.List> + {tabs.map((tab) => ( + <Tabs.Content value={tab.value}>{tab.content()}</Tabs.Content> + ))} + </Tabs.Root> + ); +}; + +export default TabsBar; diff --git a/src/v4/social/internal-components/TabsBar/index.tsx b/src/v4/social/internal-components/TabsBar/index.tsx new file mode 100644 index 000000000..a7157de09 --- /dev/null +++ b/src/v4/social/internal-components/TabsBar/index.tsx @@ -0,0 +1 @@ +export { TabsBar } from './TabsBar'; diff --git a/src/v4/social/pages/SocialGlobalSearchPage/SocialGlobalSearchPage.module.css b/src/v4/social/pages/SocialGlobalSearchPage/SocialGlobalSearchPage.module.css new file mode 100644 index 000000000..5a811d338 --- /dev/null +++ b/src/v4/social/pages/SocialGlobalSearchPage/SocialGlobalSearchPage.module.css @@ -0,0 +1,24 @@ +.socialGlobalSearchPage { + display: flex; + flex-direction: column; + gap: 0.5rem; + width: 100%; + padding: 1rem; + background-color: var(--asc-color-base-background); +} + +.socialGlobalSearchPage__tabs { + display: flex; + gap: 0.5rem; + width: 100%; + overflow-y: scroll; +} + +.socialGlobalSearchPage__postContents { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 0.5rem; + height: 100%; +} diff --git a/src/v4/social/pages/SocialGlobalSearchPage/SocialGlobalSearchPage.stories.tsx b/src/v4/social/pages/SocialGlobalSearchPage/SocialGlobalSearchPage.stories.tsx new file mode 100644 index 000000000..1e2cd4c74 --- /dev/null +++ b/src/v4/social/pages/SocialGlobalSearchPage/SocialGlobalSearchPage.stories.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { SocialGlobalSearchPage } from './SocialGlobalSearchPage'; + +export default { + title: 'v4-social/pages/SocialGlobalSearchPage', +}; + +export const SocialGlobalSearchPageStories = { + render: () => { + return <SocialGlobalSearchPage />; + }, +}; diff --git a/src/v4/social/pages/SocialGlobalSearchPage/SocialGlobalSearchPage.tsx b/src/v4/social/pages/SocialGlobalSearchPage/SocialGlobalSearchPage.tsx new file mode 100644 index 000000000..8fc86add3 --- /dev/null +++ b/src/v4/social/pages/SocialGlobalSearchPage/SocialGlobalSearchPage.tsx @@ -0,0 +1,98 @@ +import React, { useCallback, useState } from 'react'; +import styles from './SocialGlobalSearchPage.module.css'; + +import { TopSearchBar } from '~/v4/social/components/TopSearchBar'; +import { CommunitySearchResult } from '~/v4/social/components/CommunitySearchResult'; +import { TabsBar } from '~/v4/social/internal-components/TabsBar'; +import useCommunitiesCollection from '~/v4/core/hooks/collections/useCommunitiesCollection'; +import { useAmityPage } from '~/v4/core/hooks/uikit'; +import { UserSearchResult } from '../../components/UserSearchResult'; +import { useUserQueryByDisplayName } from '~/v4/core/hooks/collections/useUsersCollection'; + +enum AmityGlobalSearchType { + Community = 'community', + User = 'user', +} + +const useGlobalSearchViewModel = () => { + const [searchKeyword, setSearchKeyword] = useState<string>(''); + + const [searchType, setSearchType] = useState<AmityGlobalSearchType>( + AmityGlobalSearchType.Community, + ); + const communityCollection = useCommunitiesCollection( + { displayName: searchKeyword }, + () => searchType === AmityGlobalSearchType.Community, + ); + const userCollection = useUserQueryByDisplayName(searchKeyword); + + const search = useCallback((keyword: string) => { + setSearchKeyword(keyword); + }, []); + + return { + userCollection, + communityCollection, + searchType, + search, + setSearchType, + }; +}; + +export function SocialGlobalSearchPage() { + const pageId = 'social_global_search_page'; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityPage({ + pageId, + }); + + const [activeTab, setActiveTab] = useState('communities'); + + const { userCollection, communityCollection, searchType, search, setSearchType } = + useGlobalSearchViewModel(); + + const tabs = [ + { + value: 'communities', + label: 'Communities', + content: () => ( + <CommunitySearchResult + pageId={pageId} + communityCollection={communityCollection.communities} + onLoadMore={() => { + if (communityCollection.hasMore && communityCollection.isLoading === false) { + communityCollection.loadMore(); + } + }} + /> + ), + }, + { + value: 'users', + label: 'Users', + content: () => ( + <UserSearchResult + pageId={pageId} + userCollection={userCollection.users} + onLoadMore={() => { + if (userCollection.hasMore && userCollection.isLoading === false) { + userCollection.loadMore(); + } + }} + /> + ), + }, + ]; + + return ( + <div className={styles.socialGlobalSearchPage} style={themeStyles}> + <TopSearchBar pageId={pageId} search={search} /> + <TabsBar + pageId={pageId} + tabs={tabs} + activeTab={activeTab} + onTabChange={(newTab) => setActiveTab(newTab)} + /> + </div> + ); +} diff --git a/src/v4/social/pages/SocialGlobalSearchPage/index.tsx b/src/v4/social/pages/SocialGlobalSearchPage/index.tsx new file mode 100644 index 000000000..828cd7b6e --- /dev/null +++ b/src/v4/social/pages/SocialGlobalSearchPage/index.tsx @@ -0,0 +1 @@ +export { SocialGlobalSearchPage } from './SocialGlobalSearchPage'; From 300dea5b1c39efe8c535ea27132d0c18417140f3 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Wed, 12 Jun 2024 19:45:56 +0700 Subject: [PATCH 115/300] feat: MyCommunities (#396) --- .../MyCommunities/MyCommunities.module.css | 6 +++ .../MyCommunities/MyCommunities.stories.tsx | 15 ++++++++ .../MyCommunities/MyCommunities.tsx | 37 +++++++++++++++++++ .../social/components/MyCommunities/index.tsx | 1 + .../pages/SocialHomePage/SocialHomePage.tsx | 2 + 5 files changed, 61 insertions(+) create mode 100644 src/v4/social/components/MyCommunities/MyCommunities.module.css create mode 100644 src/v4/social/components/MyCommunities/MyCommunities.stories.tsx create mode 100644 src/v4/social/components/MyCommunities/MyCommunities.tsx create mode 100644 src/v4/social/components/MyCommunities/index.tsx diff --git a/src/v4/social/components/MyCommunities/MyCommunities.module.css b/src/v4/social/components/MyCommunities/MyCommunities.module.css new file mode 100644 index 000000000..d055c9016 --- /dev/null +++ b/src/v4/social/components/MyCommunities/MyCommunities.module.css @@ -0,0 +1,6 @@ +.myCommunitiesList { + height: 100%; + width: 100%; + padding: 0 1rem; + background-color: var(--asc-color-base-background); +} diff --git a/src/v4/social/components/MyCommunities/MyCommunities.stories.tsx b/src/v4/social/components/MyCommunities/MyCommunities.stories.tsx new file mode 100644 index 000000000..8f12f18e4 --- /dev/null +++ b/src/v4/social/components/MyCommunities/MyCommunities.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { MyCommunities } from './MyCommunities'; + +export default { + title: 'v4-social/components/MyCommunities', +}; + +export const MyCommunitiesStory = { + render: () => { + return <MyCommunities />; + }, + + name: 'MyCommunities', +}; diff --git a/src/v4/social/components/MyCommunities/MyCommunities.tsx b/src/v4/social/components/MyCommunities/MyCommunities.tsx new file mode 100644 index 000000000..638a7c993 --- /dev/null +++ b/src/v4/social/components/MyCommunities/MyCommunities.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { CommunitySearchResult } from '~/v4/social/components/CommunitySearchResult/'; +import useCommunitiesCollection from '~/v4/core/hooks/collections/useCommunitiesCollection'; +import { useAmityComponent } from '~/v4/core/hooks/uikit'; + +import styles from './MyCommunities.module.css'; + +interface MyCommunitiesProps { + pageId?: string; +} + +export const MyCommunities = ({ pageId = '*' }: MyCommunitiesProps) => { + const componentId = 'my_communities'; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityComponent({ + pageId, + componentId, + }); + + const { communities, isLoading, error, hasMore, loadMore } = useCommunitiesCollection({ + membership: 'member', + }); + + return ( + <div style={themeStyles} className={styles.myCommunitiesList}> + <CommunitySearchResult + pageId={pageId} + communityCollection={communities} + onLoadMore={() => { + if (hasMore && isLoading === false) { + loadMore(); + } + }} + /> + </div> + ); +}; diff --git a/src/v4/social/components/MyCommunities/index.tsx b/src/v4/social/components/MyCommunities/index.tsx new file mode 100644 index 000000000..86a22fbc5 --- /dev/null +++ b/src/v4/social/components/MyCommunities/index.tsx @@ -0,0 +1 @@ +export { MyCommunities } from './MyCommunities'; diff --git a/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx b/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx index 220787c36..a19c74e28 100644 --- a/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx +++ b/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx @@ -2,6 +2,7 @@ import React, { useState } from 'react'; import styles from './SocialHomePage.module.css'; import { TopNavigation } from '~/v4/social/components/TopNavigation'; +import { MyCommunities } from '~/v4/social/components/MyCommunities'; import { NewsfeedButton } from '~/v4/social/elements/NewsfeedButton'; import { ExploreButton } from '~/v4/social/elements/ExploreButton'; import { MyCommunitiesButton } from '~/v4/social/elements/MyCommunitiesButton'; @@ -44,6 +45,7 @@ export function SocialHomePage() { <div className={styles.socialHomePage__contents}> {activeTab === EnumTabNames.Newsfeed && <Newsfeed pageId={pageId} />} {activeTab === EnumTabNames.Explore && <div>Explore</div>} + {activeTab === EnumTabNames.MyCommunities && <MyCommunities pageId={pageId} />} </div> </div> ); From a0b5fbaafd0afee959991d3de437427c51e31522 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Wed, 12 Jun 2024 19:52:33 +0700 Subject: [PATCH 116/300] feat: PostDetailPage (#397) --- .../components/ReactionList/ReactionList.tsx | 19 ++-- .../elements/BackButton/BackButton.module.css | 23 ++-- .../BackButton/BackButton.stories.tsx | 15 +++ .../social/elements/BackButton/BackButton.tsx | 74 +++++++------ src/v4/social/elements/BackButton/styles.tsx | 18 ---- .../elements/CloseButton/CloseButton.tsx | 6 +- .../Comment/CommentText.module.css | 5 + .../Comment/CommentText.tsx | 12 ++- .../CommentComposeBar/CommentComposeBar.tsx | 10 +- .../PostDetailPage/PostDetailPage.module.css | 101 +++++++++++++++++ .../PostDetailPage/PostDetailPage.stories.tsx | 17 +++ .../pages/PostDetailPage/PostDetailPage.tsx | 102 ++++++++++++++++++ src/v4/social/pages/PostDetailPage/index.tsx | 1 + 13 files changed, 330 insertions(+), 73 deletions(-) create mode 100644 src/v4/social/elements/BackButton/BackButton.stories.tsx delete mode 100644 src/v4/social/elements/BackButton/styles.tsx create mode 100644 src/v4/social/pages/PostDetailPage/PostDetailPage.module.css create mode 100644 src/v4/social/pages/PostDetailPage/PostDetailPage.stories.tsx create mode 100644 src/v4/social/pages/PostDetailPage/PostDetailPage.tsx create mode 100644 src/v4/social/pages/PostDetailPage/index.tsx diff --git a/src/v4/social/components/ReactionList/ReactionList.tsx b/src/v4/social/components/ReactionList/ReactionList.tsx index 66b4afbbd..83e2d86e5 100644 --- a/src/v4/social/components/ReactionList/ReactionList.tsx +++ b/src/v4/social/components/ReactionList/ReactionList.tsx @@ -4,14 +4,15 @@ import { useReactionsCollection } from '~/v4/social/hooks/collections/useReactio import { Typography } from '~/v4/core/components'; import { useCustomReaction } from '~/v4/core/providers/CustomReactionProvider'; import { abbreviateCount } from '~/v4/utils/abbreviateCount'; -import { ReactionIcon } from '~/v4/social/components/ReactionList/ReactionIcon'; -import { ReactionListPanel } from '~/v4/social/components/ReactionList/ReactionListPanel'; -import { ReactionListError } from '~/v4/social/components/ReactionList/ReactionListError'; -import { ReactionListEmptyState } from '~/v4/social/components/ReactionList/ReactionListEmptyState'; -import { ReactionListLoadingState } from '~/v4/social/components/ReactionList/ReactionListLoadingState'; +import { ReactionIcon } from './ReactionIcon'; +import { ReactionListPanel } from './ReactionListPanel'; +import { ReactionListError } from './ReactionListError'; +import { ReactionListEmptyState } from './ReactionListEmptyState'; +import { ReactionListLoadingState } from './ReactionListLoadingState'; import useReaction from '~/v4/chat/hooks/useReaction'; import useReactionByReference from '~/v4/chat/hooks/useReactionByReference'; import FallbackReaction from '~/v4/icons/FallbackReaction'; +import { useAmityComponent } from '~/v4/core/hooks/uikit/index'; interface ReactionListProps { pageId: string; @@ -80,6 +81,10 @@ const RenderCondition = ({ export const ReactionList = ({ pageId = '*', referenceId, referenceType }: ReactionListProps) => { const componentId = 'reaction_list'; + const { accessibilityId } = useAmityComponent({ + pageId, + componentId, + }); const { reactions, error, isLoading, hasMore, loadMore } = useReactionsCollection({ referenceId, referenceType, @@ -126,9 +131,9 @@ export const ReactionList = ({ pageId = '*', referenceId, referenceType }: React ); return ( - <div className={styles.reactionListContainer} data-qa-anchor="reaction_list_header"> + <div className={styles.reactionListContainer} data-qa-anchor={`${accessibilityId}_header`}> <div className={styles.tabListContainer}> - <div className={styles.tabList} data-qa-anchor="reaction_list_tab"> + <div className={styles.tabList} data-qa-anchor={`${accessibilityId}_tab`}> <div data-active={activeTab === 'All'} className={styles.tabItem} diff --git a/src/v4/social/elements/BackButton/BackButton.module.css b/src/v4/social/elements/BackButton/BackButton.module.css index 0097239f3..c27a31ba8 100644 --- a/src/v4/social/elements/BackButton/BackButton.module.css +++ b/src/v4/social/elements/BackButton/BackButton.module.css @@ -1,15 +1,16 @@ -.uiBackButton { - width: 2rem; - height: 2rem; - position: absolute; - top: 0; - left: 0; +button { + appearance: none; + border-radius: 0; + text-align: inherit; + background: none; + box-shadow: none; + padding: 0; cursor: pointer; - border-radius: 50%; + border: none; + color: inherit; + font: inherit; } -.uiBackButtonImage { - width: 1.5rem; - height: 1.5rem; - cursor: pointer; +.backButton { + fill: var(--asc-color-base-default); } diff --git a/src/v4/social/elements/BackButton/BackButton.stories.tsx b/src/v4/social/elements/BackButton/BackButton.stories.tsx new file mode 100644 index 000000000..c47768193 --- /dev/null +++ b/src/v4/social/elements/BackButton/BackButton.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { BackButton } from './BackButton'; + +export default { + title: 'v4-social/elements/BackButton', +}; + +export const BackButtonStory = { + render: () => { + return <BackButton />; + }, + + name: 'BackButton', +}; diff --git a/src/v4/social/elements/BackButton/BackButton.tsx b/src/v4/social/elements/BackButton/BackButton.tsx index 5eaa57805..267f0ef47 100644 --- a/src/v4/social/elements/BackButton/BackButton.tsx +++ b/src/v4/social/elements/BackButton/BackButton.tsx @@ -1,47 +1,61 @@ import React from 'react'; +import { getDefaultConfig, useCustomization } from '~/v4/core/providers/CustomizationProvider'; +import { IconComponent } from '~/v4/core/IconComponent'; +import styles from './BackButton.module.css'; +import { useGenerateStylesShadeColors } from '~/v4/core/providers/ThemeProvider'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; -import { UIBackButtonImage } from './styles'; -import { isValidHttpUrl } from '~/utils'; -import { ActionButton } from '../ActionButton'; -import { ArrowLeftCircle, ArrowLeftCircle2 } from '~/icons'; -import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; +const BackButtonSvg = (props: React.SVGProps<SVGSVGElement>) => ( + <svg + width="10" + height="17" + viewBox="0 0 10 17" + fill="none" + xmlns="http://www.w3.org/2000/svg" + {...props} + > + <path d="M8.62109 15.9141C8.44531 16.0898 8.19922 16.0898 8.02344 15.9141L0.640625 8.56641C0.5 8.39062 0.5 8.14453 0.640625 7.96875L8.02344 0.621094C8.19922 0.445312 8.44531 0.445312 8.62109 0.621094L9.32422 1.28906C9.46484 1.46484 9.46484 1.74609 9.32422 1.88672L2.96094 8.25L9.32422 14.6484C9.46484 14.7891 9.46484 15.0703 9.32422 15.2461L8.62109 15.9141Z" /> + </svg> +); interface BackButtonProps { - pageId?: 'create_story_page'; - componentId?: '*'; + pageId?: string; + componentId?: string; + defaultClassName?: string; + imgClassName?: string; onClick?: (e: React.MouseEvent) => void; - 'data-qa-anchor'?: string; } export const BackButton = ({ - pageId = 'create_story_page', + pageId = '*', componentId = '*', + defaultClassName, + imgClassName, onClick = () => {}, }: BackButtonProps) => { const elementId = 'back_button'; - const { getConfig, isExcluded } = useCustomization(); - const elementConfig = getConfig(`${pageId}/${componentId}/${elementId}`); - const backIcon = elementConfig?.back_icon; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityElement({ + pageId, + componentId, + elementId, + }); - const isElementExcluded = isExcluded(`${pageId}/${componentId}/${elementId}`); + if (isExcluded) return null; - if (isElementExcluded) return null; - - const isRemoteImage = backIcon && isValidHttpUrl(backIcon); - - return isRemoteImage ? ( - <UIBackButtonImage data-qa-anchor="back_button" src={backIcon} onClick={onClick} /> - ) : ( - <ActionButton - data-qa-anchor="back_button" - icon={ - backIcon === 'back' ? ( - <ArrowLeftCircle width={20} height={20} /> - ) : ( - <ArrowLeftCircle2 width={20} height={20} /> - ) - } + return ( + <button + data-qa-anchor={accessibilityId} + className={styles.backButton} onClick={onClick} - /> + style={themeStyles} + > + <IconComponent + defaultIcon={() => <BackButtonSvg className={defaultClassName} />} + imgIcon={() => <img src={config.icon} alt={uiReference} className={imgClassName} />} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} + /> + </button> ); }; diff --git a/src/v4/social/elements/BackButton/styles.tsx b/src/v4/social/elements/BackButton/styles.tsx deleted file mode 100644 index 8754fcc78..000000000 --- a/src/v4/social/elements/BackButton/styles.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import styled from 'styled-components'; -import { Icon } from '~/v4/core/components'; - -export const UIBackButton = styled(Icon)` - width: 2rem; - height: 2rem; - cursor: pointer; - border-radius: 50%; - padding: 0.375rem 0rem; - background: ${({ theme }) => theme.v4.colors.actionButton.default}; - fill: ${({ theme }) => theme.v4.colors.baseInverse.default}; -`; - -export const UIBackButtonImage = styled.img` - width: 1.5rem; - height: 1.5rem; - cursor: pointer; -`; diff --git a/src/v4/social/elements/CloseButton/CloseButton.tsx b/src/v4/social/elements/CloseButton/CloseButton.tsx index bb2ab6ba4..b9346e1a4 100644 --- a/src/v4/social/elements/CloseButton/CloseButton.tsx +++ b/src/v4/social/elements/CloseButton/CloseButton.tsx @@ -5,15 +5,15 @@ import { isValidHttpUrl } from '~/utils'; import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; interface CloseButtonProps { - pageId: 'story_page'; - componentId: '*'; + pageId: string; + componentId: string; onClick?: (e: React.MouseEvent) => void; style?: React.CSSProperties; 'data-qa-anchor'?: string; } export const CloseButton = ({ - pageId = 'story_page', + pageId = '*', componentId = '*', onClick = () => {}, style, diff --git a/src/v4/social/internal-components/Comment/CommentText.module.css b/src/v4/social/internal-components/Comment/CommentText.module.css index c3a5e077d..433b00dba 100644 --- a/src/v4/social/internal-components/Comment/CommentText.module.css +++ b/src/v4/social/internal-components/Comment/CommentText.module.css @@ -20,3 +20,8 @@ .readMoreButton:hover { text-decoration: underline; } + +.mentionHighlightTag { + cursor: pointer; + color: var(--asc-color-primary-default); +} diff --git a/src/v4/social/internal-components/Comment/CommentText.tsx b/src/v4/social/internal-components/Comment/CommentText.tsx index 693df78e6..569571385 100644 --- a/src/v4/social/internal-components/Comment/CommentText.tsx +++ b/src/v4/social/internal-components/Comment/CommentText.tsx @@ -1,13 +1,21 @@ -import React, { useMemo, useState } from 'react'; +import React, { ReactNode, useMemo, useState } from 'react'; import { FormattedMessage } from 'react-intl'; import Truncate from 'react-truncate-markup'; import clsx from 'clsx'; import { findChunks, Mentioned } from '~/v4/helpers/utils'; -import MentionHighlightTag from '~/core/components/MentionHighlightTag'; import { processChunks } from '~/core/components/ChunkHighlighter'; import Linkify from '~/core/components/Linkify'; import styles from './CommentText.module.css'; +interface MentionHighlightTagProps { + children: ReactNode; + mentionee: Mentioned; +} + +const MentionHighlightTag = ({ children }: MentionHighlightTagProps) => { + return <span className={styles.mentionHighlightTag}>{children}</span>; +}; + const COMMENT_MAX_LINES = 8; interface CommentTextProps { diff --git a/src/v4/social/internal-components/CommentComposeBar/CommentComposeBar.tsx b/src/v4/social/internal-components/CommentComposeBar/CommentComposeBar.tsx index 73aa98d1e..efbb07195 100644 --- a/src/v4/social/internal-components/CommentComposeBar/CommentComposeBar.tsx +++ b/src/v4/social/internal-components/CommentComposeBar/CommentComposeBar.tsx @@ -21,6 +21,7 @@ const COMMENT_LENGTH_LIMIT = 50000; interface CommentComposeBarProps { targetId: string; + targetType: string; className?: string; userToReply?: Amity.User['displayName'] | null; onSubmit: (text: string, mentionees: Mentionees, metadata: Metadata) => void; @@ -28,14 +29,19 @@ interface CommentComposeBarProps { isReplying?: boolean; } -export const CommentComposeBar = ({ userToReply, onSubmit, targetId }: CommentComposeBarProps) => { +export const CommentComposeBar = ({ + userToReply, + onSubmit, + targetId, + targetType, +}: CommentComposeBarProps) => { const { currentUserId } = useSDK(); const user = useUser(currentUserId); const avatarFileUrl = useImage({ fileId: user?.avatarFileId, imageSize: 'small' }); const { text, markup, mentions, mentionees, metadata, onChange, clearAll, queryMentionees } = useMention({ targetId: targetId, - targetType: 'community', + targetType, }); const { formatMessage } = useIntl(); const { info } = useConfirmContext(); diff --git a/src/v4/social/pages/PostDetailPage/PostDetailPage.module.css b/src/v4/social/pages/PostDetailPage/PostDetailPage.module.css new file mode 100644 index 000000000..6fb24e0c2 --- /dev/null +++ b/src/v4/social/pages/PostDetailPage/PostDetailPage.module.css @@ -0,0 +1,101 @@ +.postDetailPage { + display: flex; + flex-direction: column; + position: relative; + height: 100%; + background-color: var(--asc-color-base-background); +} + +.postDetailPage__container { + overflow-y: scroll; + scrollbar-width: none; + -ms-overflow-style: none; +} + +.postDetailPage__container::-webkit-scrollbar { + display: none; +} + +.postDetailPage__topBar { + display: grid; + grid-template-columns: min-content 1fr min-content; + padding-inline: 0.75rem 1rem; + height: 3.625rem; + align-items: center; + gap: 0.5rem; + position: absolute; + left: 0; + right: 0; + top: 0; + background-color: var(--asc-color-base-background); +} + +.postDetailPage__topBar__title { + text-align: center; +} + +.postDetailPage__postContent { + padding: 0.5rem 1rem; +} + +.postDetailPage__backIcon { + fill: var(--asc-color-base-default); + cursor: pointer; +} + +.postDetailPage__comments__divider { + height: 0.0625rem; + width: 100%; + background-color: var(--asc-color-base-shade4); + animation: skeleton-pulse 1.5s ease-in-out infinite; +} + +.postDetailPage__comments__divider[data-is-loading='true'] { + height: 0.5rem; + width: 100%; + background-color: var(--asc-color-base-shade4); +} + +.postDetailPage__reactions_and_comments { + display: flex; + justify-content: space-between; + padding: 0.5rem 1rem; +} + +.postDetailPage__reactionsBar { + display: flex; + justify-content: center; + align-items: center; + gap: 0.25rem; +} + +.postDetailPage__commentsCount { + color: var(--asc-color-base-shade2); +} + +.postDetailPage__comments { + display: flex; + flex-direction: column; + gap: 1rem; + padding: 0.75rem 1rem; +} + +.postDetailPage__commentComposeBar { + display: flex; + gap: 0.5rem; + padding: 0.5rem 1rem; +} + +@keyframes skeleton-pulse { + 0% { + opacity: 0.6; + } + + 50% { + opacity: 1; + } + + 100% { + opacity: 0.6; + } +} diff --git a/src/v4/social/pages/PostDetailPage/PostDetailPage.stories.tsx b/src/v4/social/pages/PostDetailPage/PostDetailPage.stories.tsx new file mode 100644 index 000000000..a567b3954 --- /dev/null +++ b/src/v4/social/pages/PostDetailPage/PostDetailPage.stories.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import useOnePostWithCommentAndReactions from '~/mock/useOnePostWithCommentsAndReactions'; +import { PostDetailPage } from './PostDetailPage'; + +export default { + title: 'v4-social/pages/PostDetailPage', +}; + +export const PostDetailPageStories = { + render: () => { + const [post, isLoading] = useOnePostWithCommentAndReactions(); + + if (isLoading && !post) return null; + + return <PostDetailPage id={post?.postId} />; + }, +}; diff --git a/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx b/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx new file mode 100644 index 000000000..a772c6832 --- /dev/null +++ b/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx @@ -0,0 +1,102 @@ +import React, { useState } from 'react'; +import { useMutation } from '@tanstack/react-query'; +import { CommentRepository } from '@amityco/ts-sdk'; + +import { Typography } from '~/v4/core/components'; +import { PostContent, PostContentSkeleton } from '~/v4/social/components/PostContent'; +import { MenuButton } from '~/v4/social/elements/MenuButton'; +import usePost from '~/v4/core/hooks/objects/usePost'; + +import { useNavigation } from '~/v4/core/providers/NavigationProvider'; +import { BackButton } from '~/v4/social/elements/BackButton'; +import { useAmityPage } from '~/v4/core/hooks/uikit'; +import CommentList from '~/v4/social/internal-components/CommentList/CommentList'; +import CommentComposeBar from '~/v4/social/internal-components/CommentComposeBar/CommentComposeBar'; +import { Mentionees, Metadata } from '~/v4/helpers/utils'; +import styles from './PostDetailPage.module.css'; + +interface PostDetailPageProps { + id: string; +} + +export function PostDetailPage({ id }: PostDetailPageProps) { + const pageId = 'post_detail_page'; + const { post, isLoading: isPostLoading, error } = usePost(id); + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityPage({ + pageId, + }); + const { onBack } = useNavigation(); + const [replyComment, setReplyComment] = useState<Amity.Comment | null>(null); + + const { mutateAsync } = useMutation({ + mutationFn: async ({ + text, + mentionees, + metadata, + }: { + text: string; + mentionees: Mentionees; + metadata: Metadata; + }) => { + const referenceId = replyComment ? replyComment.referenceId : post?.postId; + const referenceType = replyComment ? replyComment.referenceType : 'post'; + const parentId = replyComment ? replyComment.commentId : undefined; + + await CommentRepository.createComment({ + referenceId, + referenceType, + data: { + text: text, + }, + parentId, + metadata, + mentionees: mentionees as Amity.UserMention[], + }); + }, + }); + + return ( + <div className={styles.postDetailPage} style={themeStyles}> + <div className={styles.postDetailPage__container}> + <div className={styles.postDetailPage__postContent}> + {isPostLoading ? ( + <PostContentSkeleton pageId={pageId} /> + ) : post ? ( + <PostContent pageId={pageId} post={post} type="detail" /> + ) : null} + </div> + <div className={styles.postDetailPage__comments__divider} data-is-loading={isPostLoading} /> + <div className={styles.postDetailPage__comments}> + <CommentList + referenceId={post?.postId} + referenceType={'post'} + onClickReply={(comment) => setReplyComment(comment)} + /> + </div> + </div> + <div className={styles.postDetailPage__topBar}> + <BackButton + pageId={pageId} + defaultClassName={styles.postDetailPage__backIcon} + onClick={() => onBack()} + /> + <Typography.Title className={styles.postDetailPage__topBar__title}>Post</Typography.Title> + <MenuButton pageId={pageId} /> + </div> + <div className={styles.postDetailPage__commentComposeBar}> + <CommentComposeBar + targetId={post?.postId} + targetType={'post'} + userToReply={replyComment?.creator?.displayName || undefined} + isReplying={!!replyComment} + onSubmit={async (text, mentionees, metadata) => { + await mutateAsync({ text, mentionees, metadata }); + setReplyComment(null); + }} + onCancelReply={() => setReplyComment(null)} + /> + </div> + </div> + ); +} diff --git a/src/v4/social/pages/PostDetailPage/index.tsx b/src/v4/social/pages/PostDetailPage/index.tsx new file mode 100644 index 000000000..ab8b9f0d8 --- /dev/null +++ b/src/v4/social/pages/PostDetailPage/index.tsx @@ -0,0 +1 @@ +export { PostDetailPage } from './PostDetailPage'; From 4f732ad389c550b06d06e04b9947c231478701b1 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Wed, 12 Jun 2024 20:03:09 +0700 Subject: [PATCH 117/300] feat: ASC-22335 - update routes (#398) * feat: update route * chore: remove unused code --- src/v4/social/pages/Application/index.tsx | 77 +++-------------------- src/v4/styles/global.css | 1 + src/v4/styles/typography.module.css | 8 ++- 3 files changed, 14 insertions(+), 72 deletions(-) diff --git a/src/v4/social/pages/Application/index.tsx b/src/v4/social/pages/Application/index.tsx index 6c4c2d057..63f869ef6 100644 --- a/src/v4/social/pages/Application/index.tsx +++ b/src/v4/social/pages/Application/index.tsx @@ -1,82 +1,21 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import styles from './Application.module.css'; -import { PageTypes } from '~/social/constants'; -import MainLayout from '~/social/layouts/Main'; -import CommunitySideMenu from '~/social/components/CommunitySideMenu'; -import ExplorePage from '~/social/pages/Explore'; -import NewsFeedPage from '~/social/pages/NewsFeed'; -import UserFeedPage from '~/social/pages/UserFeed'; -import CategoryCommunitiesPage from '~/social/pages/CategoryCommunities'; -import CommunityEditPage from '~/social/pages/CommunityEdit'; -import ProfileSettings from '~/social/components/ProfileSettings'; -import { useNavigation } from '~/social/providers/NavigationProvider'; -import useSDK from '~/core/hooks/useSDK'; -import { AmityViewStoryPage } from '~/v4/social/pages/StoryPage'; +import { SocialHomePage } from '~/v4/social/pages/SocialHomePage'; +import { PostDetailPage } from '~/v4/social/pages/PostDetailPage'; +import { PageTypes, useNavigation } from '~/v4/core/providers/NavigationProvider'; import { StoryProvider } from '~/v4/social/providers/StoryProvider'; -import CommunityFeed from '~/social/pages/CommunityFeed'; -const Community = () => { +const Application = () => { const { page } = useNavigation(); - const { client } = useSDK(); - const [socialSettings, setSocialSettings] = useState<Amity.SocialSettings | null>(null); - const [open, setOpen] = useState(false); - - const toggleOpen = () => { - setOpen(!open); - }; - - useEffect(() => { - if (client === null) return; - async function run() { - const settings = await client?.getSocialSettings(); - if (settings) { - setSocialSettings(settings); - } - } - run(); - }, [client]); return ( <StoryProvider> <div className={styles.applicationContainer}> - <MainLayout - aside={ - <div className={styles.styledCommunitySideMenu}> - <CommunitySideMenu activeCommunity={page.communityId} /> - </div> - } - > - {page.type === PageTypes.Explore && <ExplorePage />} - {page.type === PageTypes.NewsFeed && ( - <NewsFeedPage toggleOpen={toggleOpen} isOpen={open} /> - )} - {page.type === PageTypes.CommunityFeed && ( - <CommunityFeed - communityId={page.communityId} - isNewCommunity={page.isNewCommunity} - isOpen={open} - toggleOpen={toggleOpen} - /> - )} - {page.type === PageTypes.ViewStory && page.storyType && ( - <div className={styles.wrapper}> - <AmityViewStoryPage type={page.storyType} /> - </div> - )} - {page.type === PageTypes.CommunityEdit && ( - <CommunityEditPage communityId={page.communityId} tab={page.tab} /> - )} - {page.type === PageTypes.Category && ( - <CategoryCommunitiesPage categoryId={page.categoryId} /> - )} - {page.type === PageTypes.UserFeed && ( - <UserFeedPage userId={page.userId} socialSettings={socialSettings} /> - )} - {page.type === PageTypes.UserEdit && <ProfileSettings userId={page.userId} />} - </MainLayout> + {page.type === PageTypes.SocialHomePage && <SocialHomePage />} + {page.type === PageTypes.PostDetailPage && <PostDetailPage id={page.context.postId} />} </div> </StoryProvider> ); }; -export default Community; +export default Application; diff --git a/src/v4/styles/global.css b/src/v4/styles/global.css index f0812996d..a09bb11a1 100644 --- a/src/v4/styles/global.css +++ b/src/v4/styles/global.css @@ -1,4 +1,5 @@ @import url('../styles/typography.module.css'); +@import url('../../../node_modules/modern-normalize/modern-normalize.css'); /* The @supports rule is used to check if the browser supports the 'font-variation-settings' CSS property. diff --git a/src/v4/styles/typography.module.css b/src/v4/styles/typography.module.css index 359dc7c62..6d9d09266 100644 --- a/src/v4/styles/typography.module.css +++ b/src/v4/styles/typography.module.css @@ -36,9 +36,11 @@ } .typography-caption { - font-weight: var(--asc-text-font-weight-normal); - font-size: var(--asc-text-font-size-xs); - line-height: var(--asc-line-height-xs); + font-size: 0.8125rem; + font-style: normal; + font-weight: 400; + line-height: 1.125rem; /* 138.462% */ + letter-spacing: -0.0063rem; } .typography-caption-bold { From 94d383199268856bd48ac36726305c5f628aa91a Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Thu, 13 Jun 2024 11:51:25 +0700 Subject: [PATCH 118/300] chore: Drawer (#402) --- package.json | 1 + pnpm-lock.yaml | 125 ++++++++++ .../core/components/Drawer/Drawer.module.css | 41 ++++ src/v4/core/components/Drawer/Drawer.tsx | 25 ++ src/v4/core/components/Drawer/index.ts | 1 + src/v4/core/providers/AmityUIKitProvider.tsx | 53 +++-- src/v4/core/providers/DrawerProvider.tsx | 47 ++++ .../PostContent/PostContent.module.css | 51 +--- .../components/PostContent/PostContent.tsx | 222 ++++-------------- .../PostMenu/PostMenu.module.css | 53 +++++ .../internal-components/PostMenu/PostMenu.tsx | 184 +++++++++++++++ .../internal-components/PostMenu/index.tsx | 1 + .../PostDetailPage/PostDetailPage.module.css | 4 + .../pages/PostDetailPage/PostDetailPage.tsx | 17 +- 14 files changed, 576 insertions(+), 249 deletions(-) create mode 100644 src/v4/core/components/Drawer/Drawer.module.css create mode 100644 src/v4/core/components/Drawer/Drawer.tsx create mode 100644 src/v4/core/components/Drawer/index.ts create mode 100644 src/v4/core/providers/DrawerProvider.tsx create mode 100644 src/v4/social/internal-components/PostMenu/PostMenu.module.css create mode 100644 src/v4/social/internal-components/PostMenu/PostMenu.tsx create mode 100644 src/v4/social/internal-components/PostMenu/index.tsx diff --git a/package.json b/package.json index 1800ff93c..6c8c79ee6 100644 --- a/package.json +++ b/package.json @@ -131,6 +131,7 @@ "react-use": "^17.4.2", "stylis": "^4.3.1", "uuid": "^8.3.2", + "vaul": "^0.9.1", "zod": "^3.22.4" }, "lint-staged": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9d64edc2c..422496fbc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -110,6 +110,9 @@ importers: uuid: specifier: ^8.3.2 version: 8.3.2 + vaul: + specifier: ^0.9.1 + version: 0.9.1(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) zod: specifier: ^3.22.4 version: 3.23.8 @@ -1568,6 +1571,19 @@ packages: '@types/react': optional: true + '@radix-ui/react-dialog@1.0.5': + resolution: {integrity: sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-direction@1.0.1': resolution: {integrity: sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==} peerDependencies: @@ -1590,6 +1606,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-dismissable-layer@1.0.5': + resolution: {integrity: sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-focus-guards@1.0.1': resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==} peerDependencies: @@ -1612,6 +1641,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-focus-scope@1.0.4': + resolution: {integrity: sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-id@1.0.1': resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==} peerDependencies: @@ -1647,6 +1689,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-portal@1.0.4': + resolution: {integrity: sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-presence@1.0.1': resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==} peerDependencies: @@ -6696,6 +6751,12 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + vaul@0.9.1: + resolution: {integrity: sha512-fAhd7i4RNMinx+WEm6pF3nOl78DFkAazcN04ElLPFF9BMCNGbY/kou8UMhIcicm0rJCNePJP0Yyza60gGOD0Jw==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + vite-tsconfig-paths@4.3.2: resolution: {integrity: sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==} peerDependencies: @@ -8387,6 +8448,28 @@ snapshots: optionalDependencies: '@types/react': 17.0.80 + '@radix-ui/react-dialog@1.0.5(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-context': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.0.5(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-portal': 1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.0.1(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.0.2(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@17.0.80)(react@18.3.1) + aria-hidden: 1.2.4 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.5.5(@types/react@17.0.80)(react@18.3.1) + optionalDependencies: + '@types/react': 17.0.80 + '@radix-ui/react-direction@1.0.1(@types/react@17.0.80)(react@18.3.1)': dependencies: '@babel/runtime': 7.24.7 @@ -8407,6 +8490,19 @@ snapshots: optionalDependencies: '@types/react': 17.0.80 + '@radix-ui/react-dismissable-layer@1.0.5(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-use-escape-keydown': 1.0.3(@types/react@17.0.80)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 17.0.80 + '@radix-ui/react-focus-guards@1.0.1(@types/react@17.0.80)(react@18.3.1)': dependencies: '@babel/runtime': 7.24.7 @@ -8425,6 +8521,17 @@ snapshots: optionalDependencies: '@types/react': 17.0.80 + '@radix-ui/react-focus-scope@1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.80)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 17.0.80 + '@radix-ui/react-id@1.0.1(@types/react@17.0.80)(react@18.3.1)': dependencies: '@babel/runtime': 7.24.7 @@ -8460,6 +8567,15 @@ snapshots: optionalDependencies: '@types/react': 17.0.80 + '@radix-ui/react-portal@1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 17.0.80 + '@radix-ui/react-presence@1.0.1(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.24.7 @@ -14575,6 +14691,15 @@ snapshots: vary@1.1.2: {} + vaul@0.9.1(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@radix-ui/react-dialog': 1.0.5(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + vite-tsconfig-paths@4.3.2(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.2)(less@4.2.0)(sass@1.77.4)(stylus@0.62.0)(terser@5.31.1)): dependencies: debug: 4.3.5 diff --git a/src/v4/core/components/Drawer/Drawer.module.css b/src/v4/core/components/Drawer/Drawer.module.css new file mode 100644 index 000000000..ad4a3fd73 --- /dev/null +++ b/src/v4/core/components/Drawer/Drawer.module.css @@ -0,0 +1,41 @@ +.drawer__content { + padding: 1rem; + background-color: var(--asc-color-base-background); + max-height: 50%; + position: fixed; + bottom: 0; + left: 0; + right: 0; + display: flex; + flex-direction: column; + border-top-left-radius: 0.625rem; + border-top-right-radius: 0.625rem; + margin-top: 6rem; +} + +.drawer__content:focus { + outline: none; +} + +.drawer__innerContent { + background-color: var(--asc-color-base-background); + border-top-left-radius: 0.625rem; + border-top-right-radius: 0.625rem; + flex: 1; +} + +.drawer__placeholder { + margin: auto; + width: 3rem; + height: 0.375rem; + flex-shrink: 0; + border-radius: 9999px; + background-color: var(--asc-color-secondary-shade2); + margin-bottom: 2rem; +} + +.drawer__overlay { + background-color: color(from var(--asc-color-base-background) srgb r g b / 50%); + inset: 0; + position: fixed; +} diff --git a/src/v4/core/components/Drawer/Drawer.tsx b/src/v4/core/components/Drawer/Drawer.tsx new file mode 100644 index 000000000..1afd98ef3 --- /dev/null +++ b/src/v4/core/components/Drawer/Drawer.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import styles from './Drawer.module.css'; +import { useDrawer, useDrawerData } from '../../providers/DrawerProvider'; +import { Drawer } from 'vaul'; + +export const DrawerContainer = () => { + const drawerData = useDrawerData(); + const { removeDrawerData } = useDrawer(); + + const isOpen = drawerData != null; + + return ( + <Drawer.Root open={isOpen} onOpenChange={(open) => open === false && removeDrawerData()}> + <Drawer.Portal> + <Drawer.Overlay className={styles.drawer__overlay} /> + <Drawer.Content className={styles.drawer__content}> + <div className={styles.drawer__innerContent}> + <div className={styles.drawer__placeholder} /> + {drawerData?.content} + </div> + </Drawer.Content> + </Drawer.Portal> + </Drawer.Root> + ); +}; diff --git a/src/v4/core/components/Drawer/index.ts b/src/v4/core/components/Drawer/index.ts new file mode 100644 index 000000000..ae7b1ba67 --- /dev/null +++ b/src/v4/core/components/Drawer/index.ts @@ -0,0 +1 @@ +export { DrawerContainer } from './Drawer'; diff --git a/src/v4/core/providers/AmityUIKitProvider.tsx b/src/v4/core/providers/AmityUIKitProvider.tsx index ef46f8bde..4dc85f11a 100644 --- a/src/v4/core/providers/AmityUIKitProvider.tsx +++ b/src/v4/core/providers/AmityUIKitProvider.tsx @@ -16,6 +16,7 @@ import ConfigProvider from '~/social/providers/ConfigProvider'; import { ConfirmComponent } from '~/v4/core/components/ConfirmModal'; import { ConfirmComponent as LegacyConfirmComponent } from '~/core/components/Confirm'; import { NotificationsContainer } from '~/v4/core/components/Notification'; +import { DrawerContainer } from '~/v4/core/components/Drawer'; import { NotificationsContainer as LegacyNotificationsContainer } from '~/core/components/Notification'; import Localization from '~/core/providers/UiKitProvider/Localization'; @@ -31,6 +32,7 @@ import AmityUIKitManager from '../AmityUIKitManager'; import { ConfirmProvider } from '~/v4/core/providers/ConfirmProvider'; import { ConfirmProvider as LegacyConfirmProvider } from '~/core/providers/ConfirmProvider'; import { NotificationProvider } from '~/v4/core/providers/NotificationProvider'; +import { DrawerProvider } from '~/v4/core/providers/DrawerProvider'; import { NotificationProvider as LegacyNotificationProvider } from '~/core/providers/NotificationProvider'; import { CustomReactionProvider } from './CustomReactionProvider'; @@ -145,30 +147,33 @@ const AmityUIKitProvider: React.FC<AmityUIKitProviderProps> = ({ <SDKConnectorProviderV3> <SDKConnectorProvider> <NotificationProvider> - <LegacyNotificationProvider> - <ConfirmProvider> - <LegacyConfirmProvider> - <ConfigProvider - config={{ - socialCommunityCreationButtonVisible: - socialCommunityCreationButtonVisible || true, - }} - > - <PostRendererProvider config={postRendererConfig}> - <NavigationProvider> - <PageBehaviorProvider pageBehavior={pageBehavior}> - {children} - </PageBehaviorProvider> - </NavigationProvider> - </PostRendererProvider> - </ConfigProvider> - <NotificationsContainer /> - <LegacyNotificationsContainer /> - <ConfirmComponent /> - <LegacyConfirmComponent /> - </LegacyConfirmProvider> - </ConfirmProvider> - </LegacyNotificationProvider> + <DrawerProvider> + <LegacyNotificationProvider> + <ConfirmProvider> + <LegacyConfirmProvider> + <ConfigProvider + config={{ + socialCommunityCreationButtonVisible: + socialCommunityCreationButtonVisible || true, + }} + > + <PostRendererProvider config={postRendererConfig}> + <NavigationProvider> + <PageBehaviorProvider pageBehavior={pageBehavior}> + {children} + </PageBehaviorProvider> + </NavigationProvider> + </PostRendererProvider> + </ConfigProvider> + <NotificationsContainer /> + <LegacyNotificationsContainer /> + <ConfirmComponent /> + <DrawerContainer /> + <LegacyConfirmComponent /> + </LegacyConfirmProvider> + </ConfirmProvider> + </LegacyNotificationProvider> + </DrawerProvider> </NotificationProvider> </SDKConnectorProvider> </SDKConnectorProviderV3> diff --git a/src/v4/core/providers/DrawerProvider.tsx b/src/v4/core/providers/DrawerProvider.tsx new file mode 100644 index 000000000..21481ec50 --- /dev/null +++ b/src/v4/core/providers/DrawerProvider.tsx @@ -0,0 +1,47 @@ +import React, { createContext, ReactNode, useContext, useState } from 'react'; + +interface DrawerData { + content: ReactNode; +} + +interface DrawerContextProps { + drawerData?: DrawerData | null; + setDrawerData: (data: DrawerData) => void; + removeDrawerData: () => void; +} + +export const DrawerContext = createContext<DrawerContextProps>({ + drawerData: null, + setDrawerData: () => {}, + removeDrawerData: () => {}, +}); + +export const DrawerProvider: React.FC = ({ children }) => { + const [drawerData, setDrawerData] = useState<DrawerData | null>(null); + + return ( + <DrawerContext.Provider + value={{ + drawerData, + setDrawerData: (data: DrawerData) => { + setDrawerData(data); + }, + removeDrawerData: () => { + setDrawerData(null); + }, + }} + > + {children} + </DrawerContext.Provider> + ); +}; + +export const useDrawerData = () => { + const { drawerData } = useContext(DrawerContext); + return drawerData; +}; + +export const useDrawer = () => { + const { setDrawerData, removeDrawerData } = useContext(DrawerContext); + return { setDrawerData, removeDrawerData }; +}; diff --git a/src/v4/social/components/PostContent/PostContent.module.css b/src/v4/social/components/PostContent/PostContent.module.css index 48067441a..fb4c285db 100644 --- a/src/v4/social/components/PostContent/PostContent.module.css +++ b/src/v4/social/components/PostContent/PostContent.module.css @@ -38,6 +38,13 @@ cursor: pointer; } +.postContent__bar__information__subtitle__timestamp:hover { + text-decoration: underline; + text-underline-offset: 0.125rem; + text-decoration-thickness: 0.0625rem; + text-decoration-color: var(--asc-color-base-shade2); +} + .postContent__bar__actionButton { justify-self: flex-end; cursor: pointer; @@ -167,47 +174,3 @@ .postContent__reactionsBar__reactions__count { color: var(--asc-color-base-shade2); } - -.postContent__postMenu__item { - display: flex; - align-items: center; - gap: 0.75rem; - padding: 0.75rem 1.25rem; - cursor: pointer; - width: 100%; -} - -.postContent__postMenu__item:hover { - background-color: var(--asc-color-base-shade4); - border-radius: var(--asc-border-radius-md); -} - -.postContent__postMenu__editPost__text { - color: var(--asc-color-secondary-default); -} - -.postContent__postMenu__editPost__icon { - fill: var(--asc-color-secondary-default); - width: 1.5rem; - height: 1.25rem; -} - -.postContent__postMenu__reportPost__text { - color: var(--asc-color-secondary-default); -} - -.postContent__postMenu__reportPost__icon { - fill: var(--asc-color-secondary-default); - width: 1.5rem; - height: 1.25rem; -} - -.postContent__postMenu__deletePost__text { - color: var(--asc-color-alert); -} - -.postContent__postMenu__deletePost__icon { - fill: var(--asc-color-alert); - width: 1.5rem; - height: 1.25rem; -} diff --git a/src/v4/social/components/PostContent/PostContent.tsx b/src/v4/social/components/PostContent/PostContent.tsx index 7a1695fe4..5ca1e8da1 100644 --- a/src/v4/social/components/PostContent/PostContent.tsx +++ b/src/v4/social/components/PostContent/PostContent.tsx @@ -14,7 +14,7 @@ import { CommentButton } from '~/v4/social/elements/CommentButton'; import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; import { useGenerateStylesShadeColors } from '~/v4/core/providers/ThemeProvider'; import { useMutation } from '@tanstack/react-query'; -import { PostRepository, ReactionRepository } from '@amityco/ts-sdk'; +import { ReactionRepository } from '@amityco/ts-sdk'; import styles from './PostContent.module.css'; @@ -32,167 +32,8 @@ import { VideoViewer } from '~/v4/social/internal-components/VideoViewer/VideoVi import usePost from '~/v4/core/hooks/objects/usePost'; import { ReactionList } from '~/v4/social/components/ReactionList/'; import { usePostPermissions } from '~/v4/core/hooks/usePostPermissions'; -import { usePostFlaggedByMe } from '~/v4/core/hooks/usePostFlaggedByMe'; -import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; -import { useNotifications } from '~/v4/core/providers/NotificationProvider'; - -const PenSvg = (props: React.SVGProps<SVGSVGElement>) => ( - <svg - width="20" - height="19" - viewBox="0 0 20 19" - fill="none" - xmlns="http://www.w3.org/2000/svg" - {...props} - > - <path d="M18.3804 2.25391C19.2593 3.13281 19.2593 4.53906 18.3804 5.41797L15.7437 8.05469L14.5483 9.25L5.97021 17.8281L1.9624 18.25C1.92725 18.25 1.89209 18.25 1.85693 18.25C1.36475 18.25 0.978027 17.8281 1.04834 17.3359L1.47021 13.3281L10.0483 4.75L11.2437 3.55469L13.8804 0.917969C14.3022 0.496094 14.8999 0.25 15.4624 0.25C16.0249 0.25 16.6226 0.496094 17.0444 0.917969L18.3804 2.25391ZM5.19678 16.2109L13.353 8.08984L14.4429 7L12.2983 4.85547L11.2085 5.94531L3.0874 14.1016L2.84131 16.457L5.19678 16.2109ZM17.1851 4.22266C17.396 4.01172 17.396 3.66016 17.1851 3.44922L15.8491 2.11328C15.7085 1.97266 15.5327 1.9375 15.4624 1.9375C15.3921 1.9375 15.2163 1.97266 15.0757 2.11328L13.4937 3.66016L15.6382 5.80469L17.1851 4.22266Z" /> - </svg> -); - -const TrashSvg = (props: React.SVGProps<SVGSVGElement>) => ( - <svg - width="16" - height="19" - viewBox="0 0 16 19" - fill="none" - xmlns="http://www.w3.org/2000/svg" - {...props} - > - <path d="M15.3608 3.0625C15.6421 3.0625 15.9233 3.34375 15.9233 3.625V4.1875C15.9233 4.50391 15.6421 4.75 15.3608 4.75H14.7983L14.0249 16.668C13.9897 17.5469 13.2163 18.25 12.3374 18.25H3.72412C2.84521 18.25 2.07178 17.5469 2.03662 16.668L1.29834 4.75H0.73584C0.419434 4.75 0.17334 4.50391 0.17334 4.1875V3.625C0.17334 3.34375 0.419434 3.0625 0.73584 3.0625H3.61865L4.81396 1.09375C5.09521 0.636719 5.72803 0.25 6.25537 0.25H9.80615C10.3335 0.25 10.9663 0.636719 11.2476 1.09375L12.4429 3.0625H15.3608ZM6.25537 1.9375L5.5874 3.0625H10.4741L9.80615 1.9375H6.25537ZM12.3374 16.5625L13.0757 4.75H2.98584L3.72412 16.5625H12.3374Z" /> - </svg> -); - -const FlagSvg = (props: React.SVGProps<SVGSVGElement>) => ( - <svg - width="19" - height="19" - viewBox="0 0 19 19" - fill="none" - xmlns="http://www.w3.org/2000/svg" - {...props} - > - <path d="M12.3125 3.0625C13.543 3.0625 14.8789 2.64062 16.1445 2.07812C17.2344 1.62109 18.5 2.42969 18.5 3.625V12.0625C18.5 12.6602 18.1836 13.1875 17.7266 13.5039C16.7773 14.1016 15.2656 14.875 13.1914 14.875C10.8008 14.875 9.21875 13.75 7.49609 13.75C5.52734 13.75 4.33203 14.1719 3.03125 14.7695V17.6875C3.03125 18.0039 2.75 18.25 2.46875 18.25H1.90625C1.58984 18.25 1.34375 18.0039 1.34375 17.6875V3.41406C0.816406 3.13281 0.5 2.57031 0.5 1.9375C0.5 0.988281 1.30859 0.214844 2.29297 0.285156C3.10156 0.320312 3.76953 0.953125 3.83984 1.76172C3.83984 1.79688 3.875 1.90234 3.875 1.9375C3.875 2.11328 3.80469 2.35938 3.76953 2.5C4.54297 2.18359 5.49219 1.9375 6.61719 1.9375C9.00781 1.9375 10.5898 3.0625 12.3125 3.0625ZM16.8125 12.0625V3.625C15.6875 4.15234 13.8242 4.75 12.3125 4.75C10.2031 4.75 8.72656 3.625 6.61719 3.625C5.14062 3.625 3.76953 4.22266 3.03125 4.75V12.9062C4.12109 12.4141 5.98438 12.0625 7.49609 12.0625C9.60547 12.0625 11.082 13.1875 13.1914 13.1875C14.668 13.1875 16.0391 12.625 16.8125 12.0625Z" /> - </svg> -); - -interface PostMenuProps { - post: Amity.Post; - community: Amity.Community | null; - onCloseMenu: () => void; -} - -const PostMenu = ({ post, community, onCloseMenu }: PostMenuProps) => { - const { success, error } = useNotifications(); - const { isCommunityModerator, isOwner } = usePostPermissions({ post, community }); - - const { showEditPostButton, showDeletePostButton, showReportPostButton } = useMemo(() => { - if (isCommunityModerator) { - if (isOwner) { - return { - showEditPostButton: true, - showDeletePostButton: true, - showReportPostButton: false, - }; - } else { - return { - showEditPostButton: false, - showDeletePostButton: true, - showReportPostButton: true, - }; - } - } else { - if (isOwner) { - return { - showEditPostButton: true, - showDeletePostButton: true, - showReportPostButton: false, - }; - } else { - return { - showEditPostButton: false, - showDeletePostButton: false, - showReportPostButton: true, - }; - } - } - }, [isCommunityModerator, isOwner]); - - const { isFlaggedByMe, isLoading, mutateReportPost, mutateUnReportPost } = usePostFlaggedByMe({ - post, - isFlaggable: showReportPostButton, - onReportSuccess: () => { - success({ content: 'Post reported' }); - }, - onReportError: () => { - error({ content: 'Failed to report post' }); - }, - onUnreportSuccess: () => { - success({ content: 'Post unreported' }); - }, - onUnreportError: () => { - error({ content: 'Failed to unreport post' }); - }, - }); - - const { confirm } = useConfirmContext(); - - const { mutateAsync: mutateDeletePost } = useMutation({ - mutationFn: async () => { - onCloseMenu(); - return PostRepository.hardDeletePost(post.postId); - }, - onSuccess: () => { - success({ content: 'Post deleted' }); - }, - onError: () => { - error({ content: 'Failed to delete post' }); - }, - }); - - const onDeleteClick = () => { - onCloseMenu(); - confirm({ - title: 'Delete post', - content: 'This post will be permanently deleted.', - cancelText: 'Cancel', - okText: 'Delete', - onOk: () => mutateDeletePost(), - }); - }; - - return ( - <div> - {/* <div className={styles.postContent__postMenu__item} onClick={onEdit}> - <PenSvg className={styles.postContent__postMenu__editPost__icon} /> - <span>Edit post</span> - </div> */} - {showReportPostButton ? ( - <button - className={styles.postContent__postMenu__item} - disabled={isLoading} - onClick={() => { - if (isFlaggedByMe) { - mutateUnReportPost(); - } else { - mutateReportPost(); - } - }} - > - <FlagSvg className={styles.postContent__postMenu__reportPost__icon} /> - <span className={styles.postContent__postMenu__reportPost__text}> - {isFlaggedByMe ? 'Unreport post' : 'Report post'} - </span> - </button> - ) : null} - {showDeletePostButton ? ( - <button className={styles.postContent__postMenu__item} onClick={() => onDeleteClick()}> - <TrashSvg className={styles.postContent__postMenu__deletePost__icon} /> - <span className={styles.postContent__postMenu__deletePost__text}>Delete post</span> - </button> - ) : null} - </div> - ); -}; +import { PostMenu } from '~/v4/social/internal-components/PostMenu/PostMenu'; +import { useDrawer } from '~/v4/core/providers/DrawerProvider'; interface PostTitleProps { post: Amity.Post; @@ -286,7 +127,7 @@ const ChildrenPostContent = ({ }: { post: Amity.Post[]; onImageClick: (imageIndex: number) => void; - onVideoClick: () => void; + onVideoClick: (videoIndex: number) => void; }) => { return ( <> @@ -316,14 +157,14 @@ export const PostContent = ({ }); const { post: postData } = usePost(initialPost.postId); + const { setDrawerData, removeDrawerData } = useDrawer(); const post = postData || initialPost; const [isImageViewerOpen, setIsImageViewerOpen] = useState(false); const [isVideoViewerOpen, setIsVideoViewerOpen] = useState(false); const [clickedImageIndex, setClickedImageIndex] = useState<number | null>(null); - const [showReactionList, setShowReactionList] = useState(false); - const [showMenu, setShowMenu] = useState(false); + const [clickedVideoIndex, setClickedVideoIndex] = useState<number | null>(null); useEffect(() => { if (post) { @@ -374,6 +215,16 @@ export const PostContent = ({ setClickedImageIndex(null); }; + const openVideoViewer = (imageIndex: number) => { + setIsVideoViewerOpen(true); + setClickedVideoIndex(imageIndex); + }; + + const closeVideoViewer = () => { + setIsVideoViewerOpen(false); + setClickedVideoIndex(null); + }; + const hasLike = post?.reactions.like > 0; const hasLove = post?.reactions.love > 0; const hasFire = post?.reactions.fire > 0; @@ -415,7 +266,18 @@ export const PostContent = ({ <MenuButton pageId={pageId} componentId={componentId} - onClick={() => setShowMenu(true)} + onClick={() => + setDrawerData({ + content: ( + <PostMenu + post={post} + onCloseMenu={() => removeDrawerData()} + pageId={pageId} + componentId={componentId} + /> + ), + }) + } /> ) : null} </div> @@ -427,7 +289,7 @@ export const PostContent = ({ <ChildrenPostContent post={post} onImageClick={openImageViewer} - onVideoClick={() => setIsVideoViewerOpen(true)} + onVideoClick={openVideoViewer} /> ) : null} </div> @@ -435,7 +297,17 @@ export const PostContent = ({ <div className={styles.postContent__reactions_and_comments}> <div className={styles.postContent__reactionsBar} - onClick={() => setShowReactionList(true)} + onClick={() => + setDrawerData({ + content: ( + <ReactionList + pageId={pageId} + referenceId={post.postId} + referenceType={'post'} + /> + ), + }) + } > {hasReaction ? ( <div className={styles.postContent__reactionsBar__reactions}> @@ -497,19 +369,9 @@ export const PostContent = ({ {isImageViewerOpen && typeof clickedImageIndex === 'number' ? ( <ImageViewer post={post} onClose={closeImageViewer} initialImageIndex={clickedImageIndex} /> ) : null} - {isVideoViewerOpen ? ( - <VideoViewer post={post} onClose={() => setIsVideoViewerOpen(false)} /> + {isVideoViewerOpen && typeof clickedVideoIndex === 'number' ? ( + <VideoViewer post={post} onClose={closeVideoViewer} initialVideoIndex={clickedVideoIndex} /> ) : null} - <BottomSheet - detent="content-height" - isOpen={showReactionList} - onClose={() => setShowReactionList(false)} - > - <ReactionList pageId={pageId} referenceId={post.postId} referenceType={'post'} /> - </BottomSheet> - <BottomSheet detent="content-height" isOpen={showMenu} onClose={() => setShowMenu(false)}> - <PostMenu post={post} community={targetCommunity} onCloseMenu={() => setShowMenu(false)} /> - </BottomSheet> </> ); }; diff --git a/src/v4/social/internal-components/PostMenu/PostMenu.module.css b/src/v4/social/internal-components/PostMenu/PostMenu.module.css new file mode 100644 index 000000000..98973f662 --- /dev/null +++ b/src/v4/social/internal-components/PostMenu/PostMenu.module.css @@ -0,0 +1,53 @@ +.postMenu { + height: 100%; + width: 100%; +} + +.postMenu__item { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem 1.25rem; + cursor: pointer; + width: 100%; + box-sizing: border-box; +} + +.postMenu__item:focus { + outline: none; +} + +.postMenu__item:hover { + background-color: var(--asc-color-base-shade4); + border-radius: var(--asc-border-radius-md); +} + +.postMenu__editPost__text { + color: var(--asc-color-secondary-default); +} + +.postMenu__editPost__icon { + fill: var(--asc-color-secondary-default); + width: 1.5rem; + height: 1.25rem; +} + +.postMenu__reportPost__text { + color: var(--asc-color-secondary-default); +} + +.postMenu__reportPost__icon { + fill: var(--asc-color-secondary-default); + width: 1.5rem; + height: 1.25rem; +} + +.postMenu__deletePost__text { + color: var(--asc-color-alert); +} + +.postMenu__deletePost__icon { + fill: var(--asc-color-alert); + width: 1.5rem; + height: 1.25rem; +} diff --git a/src/v4/social/internal-components/PostMenu/PostMenu.tsx b/src/v4/social/internal-components/PostMenu/PostMenu.tsx new file mode 100644 index 000000000..5b30f292b --- /dev/null +++ b/src/v4/social/internal-components/PostMenu/PostMenu.tsx @@ -0,0 +1,184 @@ +import React, { useMemo } from 'react'; +import { PostRepository } from '@amityco/ts-sdk'; +import { useMutation } from '@tanstack/react-query'; +import { usePostPermissions } from '~/v4/core/hooks/usePostPermissions'; +import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; +import { useNotifications } from '~/v4/core/providers/NotificationProvider'; + +import styles from './PostMenu.module.css'; +import { usePostFlaggedByMe } from '~/v4/core/hooks/usePostFlaggedByMe'; +import useCommunity from '~/v4/core/hooks/collections/useCommunity'; + +const PenSvg = (props: React.SVGProps<SVGSVGElement>) => ( + <svg + width="20" + height="19" + viewBox="0 0 20 19" + fill="none" + xmlns="http://www.w3.org/2000/svg" + {...props} + > + <path d="M18.3804 2.25391C19.2593 3.13281 19.2593 4.53906 18.3804 5.41797L15.7437 8.05469L14.5483 9.25L5.97021 17.8281L1.9624 18.25C1.92725 18.25 1.89209 18.25 1.85693 18.25C1.36475 18.25 0.978027 17.8281 1.04834 17.3359L1.47021 13.3281L10.0483 4.75L11.2437 3.55469L13.8804 0.917969C14.3022 0.496094 14.8999 0.25 15.4624 0.25C16.0249 0.25 16.6226 0.496094 17.0444 0.917969L18.3804 2.25391ZM5.19678 16.2109L13.353 8.08984L14.4429 7L12.2983 4.85547L11.2085 5.94531L3.0874 14.1016L2.84131 16.457L5.19678 16.2109ZM17.1851 4.22266C17.396 4.01172 17.396 3.66016 17.1851 3.44922L15.8491 2.11328C15.7085 1.97266 15.5327 1.9375 15.4624 1.9375C15.3921 1.9375 15.2163 1.97266 15.0757 2.11328L13.4937 3.66016L15.6382 5.80469L17.1851 4.22266Z" /> + </svg> +); + +const TrashSvg = (props: React.SVGProps<SVGSVGElement>) => ( + <svg + width="16" + height="19" + viewBox="0 0 16 19" + fill="none" + xmlns="http://www.w3.org/2000/svg" + {...props} + > + <path d="M15.3608 3.0625C15.6421 3.0625 15.9233 3.34375 15.9233 3.625V4.1875C15.9233 4.50391 15.6421 4.75 15.3608 4.75H14.7983L14.0249 16.668C13.9897 17.5469 13.2163 18.25 12.3374 18.25H3.72412C2.84521 18.25 2.07178 17.5469 2.03662 16.668L1.29834 4.75H0.73584C0.419434 4.75 0.17334 4.50391 0.17334 4.1875V3.625C0.17334 3.34375 0.419434 3.0625 0.73584 3.0625H3.61865L4.81396 1.09375C5.09521 0.636719 5.72803 0.25 6.25537 0.25H9.80615C10.3335 0.25 10.9663 0.636719 11.2476 1.09375L12.4429 3.0625H15.3608ZM6.25537 1.9375L5.5874 3.0625H10.4741L9.80615 1.9375H6.25537ZM12.3374 16.5625L13.0757 4.75H2.98584L3.72412 16.5625H12.3374Z" /> + </svg> +); + +const FlagSvg = (props: React.SVGProps<SVGSVGElement>) => ( + <svg + width="19" + height="19" + viewBox="0 0 19 19" + fill="none" + xmlns="http://www.w3.org/2000/svg" + {...props} + > + <path d="M12.3125 3.0625C13.543 3.0625 14.8789 2.64062 16.1445 2.07812C17.2344 1.62109 18.5 2.42969 18.5 3.625V12.0625C18.5 12.6602 18.1836 13.1875 17.7266 13.5039C16.7773 14.1016 15.2656 14.875 13.1914 14.875C10.8008 14.875 9.21875 13.75 7.49609 13.75C5.52734 13.75 4.33203 14.1719 3.03125 14.7695V17.6875C3.03125 18.0039 2.75 18.25 2.46875 18.25H1.90625C1.58984 18.25 1.34375 18.0039 1.34375 17.6875V3.41406C0.816406 3.13281 0.5 2.57031 0.5 1.9375C0.5 0.988281 1.30859 0.214844 2.29297 0.285156C3.10156 0.320312 3.76953 0.953125 3.83984 1.76172C3.83984 1.79688 3.875 1.90234 3.875 1.9375C3.875 2.11328 3.80469 2.35938 3.76953 2.5C4.54297 2.18359 5.49219 1.9375 6.61719 1.9375C9.00781 1.9375 10.5898 3.0625 12.3125 3.0625ZM16.8125 12.0625V3.625C15.6875 4.15234 13.8242 4.75 12.3125 4.75C10.2031 4.75 8.72656 3.625 6.61719 3.625C5.14062 3.625 3.76953 4.22266 3.03125 4.75V12.9062C4.12109 12.4141 5.98438 12.0625 7.49609 12.0625C9.60547 12.0625 11.082 13.1875 13.1914 13.1875C14.668 13.1875 16.0391 12.625 16.8125 12.0625Z" /> + </svg> +); + +interface PostMenuProps { + post: Amity.Post; + pageId?: string; + componentId?: string; + elementId?: string; + onCloseMenu: () => void; +} + +export const PostMenu = ({ + post, + pageId = '*', + componentId = '*', + elementId = '*', + onCloseMenu, +}: PostMenuProps) => { + const { success, error } = useNotifications(); + + const { community } = useCommunity({ + communityId: post?.targetId, + shouldCall: () => post?.targetType === 'community', + }); + + const { isCommunityModerator, isOwner } = usePostPermissions({ post, community }); + + const { showEditPostButton, showDeletePostButton, showReportPostButton } = useMemo(() => { + if (isCommunityModerator) { + if (isOwner) { + return { + showEditPostButton: true, + showDeletePostButton: true, + showReportPostButton: false, + }; + } else { + return { + showEditPostButton: false, + showDeletePostButton: true, + showReportPostButton: true, + }; + } + } else { + if (isOwner) { + return { + showEditPostButton: true, + showDeletePostButton: true, + showReportPostButton: false, + }; + } else { + return { + showEditPostButton: false, + showDeletePostButton: false, + showReportPostButton: true, + }; + } + } + }, [isCommunityModerator, isOwner]); + + const { isFlaggedByMe, isLoading, mutateReportPost, mutateUnReportPost } = usePostFlaggedByMe({ + post, + isFlaggable: showReportPostButton, + onReportSuccess: () => { + success({ content: 'Post reported' }); + }, + onReportError: () => { + error({ content: 'Failed to report post' }); + }, + onUnreportSuccess: () => { + success({ content: 'Post unreported' }); + }, + onUnreportError: () => { + error({ content: 'Failed to unreport post' }); + }, + }); + + const { confirm } = useConfirmContext(); + + const { mutateAsync: mutateDeletePost } = useMutation({ + mutationFn: async () => { + onCloseMenu(); + return PostRepository.hardDeletePost(post.postId); + }, + onSuccess: () => { + success({ content: 'Post deleted' }); + }, + onError: () => { + error({ content: 'Failed to delete post' }); + }, + }); + + const onDeleteClick = () => { + onCloseMenu(); + confirm({ + title: 'Delete post', + content: 'This post will be permanently deleted.', + cancelText: 'Cancel', + okText: 'Delete', + onOk: () => mutateDeletePost(), + pageId, + componentId, + }); + }; + + return ( + <div className={styles.postMenu}> + {/* <button className={styles.postMenu__item} onClick={onEdit}> + <PenSvg className={styles.postMenu__editPost__icon} /> + <span>Edit post</span> + </button> */} + {showReportPostButton ? ( + <button + className={styles.postMenu__item} + disabled={isLoading} + onClick={() => { + if (isFlaggedByMe) { + mutateUnReportPost(); + } else { + mutateReportPost(); + } + }} + > + <FlagSvg className={styles.postMenu__reportPost__icon} /> + <span className={styles.postMenu__reportPost__text}> + {isFlaggedByMe ? 'Unreport post' : 'Report post'} + </span> + </button> + ) : null} + {showDeletePostButton ? ( + <button className={styles.postMenu__item} onClick={() => onDeleteClick()}> + <TrashSvg className={styles.postMenu__deletePost__icon} /> + <span className={styles.postMenu__deletePost__text}>Delete post</span> + </button> + ) : null} + </div> + ); +}; diff --git a/src/v4/social/internal-components/PostMenu/index.tsx b/src/v4/social/internal-components/PostMenu/index.tsx new file mode 100644 index 000000000..092f2cb26 --- /dev/null +++ b/src/v4/social/internal-components/PostMenu/index.tsx @@ -0,0 +1 @@ +export { PostMenu } from './PostMenu'; diff --git a/src/v4/social/pages/PostDetailPage/PostDetailPage.module.css b/src/v4/social/pages/PostDetailPage/PostDetailPage.module.css index 6fb24e0c2..8d0815c06 100644 --- a/src/v4/social/pages/PostDetailPage/PostDetailPage.module.css +++ b/src/v4/social/pages/PostDetailPage/PostDetailPage.module.css @@ -34,6 +34,10 @@ text-align: center; } +.postDetailPage__topBar__menuBar { + cursor: pointer; +} + .postDetailPage__postContent { padding: 0.5rem 1rem; } diff --git a/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx b/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx index a772c6832..c15c9a65a 100644 --- a/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx +++ b/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx @@ -5,6 +5,7 @@ import { CommentRepository } from '@amityco/ts-sdk'; import { Typography } from '~/v4/core/components'; import { PostContent, PostContentSkeleton } from '~/v4/social/components/PostContent'; import { MenuButton } from '~/v4/social/elements/MenuButton'; +import { PostMenu } from '~/v4/social/internal-components/PostMenu/PostMenu'; import usePost from '~/v4/core/hooks/objects/usePost'; import { useNavigation } from '~/v4/core/providers/NavigationProvider'; @@ -14,6 +15,7 @@ import CommentList from '~/v4/social/internal-components/CommentList/CommentList import CommentComposeBar from '~/v4/social/internal-components/CommentComposeBar/CommentComposeBar'; import { Mentionees, Metadata } from '~/v4/helpers/utils'; import styles from './PostDetailPage.module.css'; +import { useDrawer } from '~/v4/core/providers/DrawerProvider'; interface PostDetailPageProps { id: string; @@ -29,6 +31,8 @@ export function PostDetailPage({ id }: PostDetailPageProps) { const { onBack } = useNavigation(); const [replyComment, setReplyComment] = useState<Amity.Comment | null>(null); + const { setDrawerData, removeDrawerData } = useDrawer(); + const { mutateAsync } = useMutation({ mutationFn: async ({ text, @@ -82,7 +86,18 @@ export function PostDetailPage({ id }: PostDetailPageProps) { onClick={() => onBack()} /> <Typography.Title className={styles.postDetailPage__topBar__title}>Post</Typography.Title> - <MenuButton pageId={pageId} /> + <div className={styles.postDetailPage__topBar__menuBar}> + <MenuButton + pageId={pageId} + onClick={() => + setDrawerData({ + content: ( + <PostMenu post={post} onCloseMenu={() => removeDrawerData()} pageId={pageId} /> + ), + }) + } + /> + </div> </div> <div className={styles.postDetailPage__commentComposeBar}> <CommentComposeBar From c9b6c2a530111488182560e0e5c44c173a9e1452 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Thu, 13 Jun 2024 11:55:03 +0700 Subject: [PATCH 119/300] fix: text overflow (#400) --- .../CommunitySearchResult.module.css | 1 + .../CommunitySearchResult/CommunitySearchResult.tsx | 13 ++++++++++++- .../UserSearchResult/UserSearchResult.module.css | 9 +++++++++ .../UserSearchResult/UserSearchResult.tsx | 4 +++- 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/v4/social/components/CommunitySearchResult/CommunitySearchResult.module.css b/src/v4/social/components/CommunitySearchResult/CommunitySearchResult.module.css index 2d6a8047f..82ca0dfb2 100644 --- a/src/v4/social/components/CommunitySearchResult/CommunitySearchResult.module.css +++ b/src/v4/social/components/CommunitySearchResult/CommunitySearchResult.module.css @@ -5,6 +5,7 @@ align-items: start; width: 100%; background-color: var(--asc-color-base-background); + overflow-x: hidden; } .communitySearchResult__communityItem { diff --git a/src/v4/social/components/CommunitySearchResult/CommunitySearchResult.tsx b/src/v4/social/components/CommunitySearchResult/CommunitySearchResult.tsx index 3453daf02..2b332bd11 100644 --- a/src/v4/social/components/CommunitySearchResult/CommunitySearchResult.tsx +++ b/src/v4/social/components/CommunitySearchResult/CommunitySearchResult.tsx @@ -21,15 +21,26 @@ const CommunityCategories = ({ }) => { const categories = useCategoriesByIds(community.categoryIds); + const maxCategoriesLength = 3; + + const overflowCategoriesLength = categories.length - maxCategoriesLength; + return ( <> - {categories.map((category) => ( + {categories.slice(0, 3).map((category) => ( <CommunityCategoryName pageId={pageId} componentId={componentId} categoryName={category.name} /> ))} + {overflowCategoriesLength > 0 && ( + <CommunityCategoryName + pageId={pageId} + componentId={componentId} + categoryName={`+${overflowCategoriesLength}`} + /> + )} </> ); }; diff --git a/src/v4/social/components/UserSearchResult/UserSearchResult.module.css b/src/v4/social/components/UserSearchResult/UserSearchResult.module.css index bec91a812..d6f933f8d 100644 --- a/src/v4/social/components/UserSearchResult/UserSearchResult.module.css +++ b/src/v4/social/components/UserSearchResult/UserSearchResult.module.css @@ -5,6 +5,7 @@ align-items: start; width: 100%; background-color: var(--asc-color-base-background); + overflow-x: hidden; } .userSearchResult__userItem { @@ -39,4 +40,12 @@ align-items: last baseline; gap: 0.25rem; width: 100%; + color: var(--asc-color-base-default); + overflow: hidden; + text-overflow: ellipsis; +} + +.userItem__userName__text { + overflow: hidden; + text-overflow: ellipsis; } diff --git a/src/v4/social/components/UserSearchResult/UserSearchResult.tsx b/src/v4/social/components/UserSearchResult/UserSearchResult.tsx index 5c1e4e6c7..7e5de68c6 100644 --- a/src/v4/social/components/UserSearchResult/UserSearchResult.tsx +++ b/src/v4/social/components/UserSearchResult/UserSearchResult.tsx @@ -39,7 +39,9 @@ export const UserSearchResult = ({ </div> <div className={styles.userSearchResult__userItem__rightPane}> <div className={styles.userItem__userName}> - <Typography.BodyBold>{user.displayName}</Typography.BodyBold> + <Typography.BodyBold className={styles.userItem__userName__text}> + {user.displayName} + </Typography.BodyBold> </div> </div> </div> From ff1d052e6eb30f7851cf10a1d480a2c085172f21 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Thu, 13 Jun 2024 11:58:21 +0700 Subject: [PATCH 120/300] chore: ASC-22335 - update SocialGlobalSearchPage route (#399) * feat: add SocialGlobalSearchPage routing * fix: update useUserQueryByDisplayName --- .../hooks/collections/useUsersCollection.ts | 23 +++++++------------ src/v4/core/hooks/objects/useUser.ts | 10 +------- src/v4/core/providers/NavigationProvider.tsx | 21 ++++++++++++++++- .../TopNavigation/TopNavigation.tsx | 9 +++++++- .../components/TopSearchBar/TopSearchBar.tsx | 5 +++- src/v4/social/pages/Application/index.tsx | 2 ++ .../SocialGlobalSearchPage.tsx | 4 ++-- 7 files changed, 45 insertions(+), 29 deletions(-) diff --git a/src/v4/core/hooks/collections/useUsersCollection.ts b/src/v4/core/hooks/collections/useUsersCollection.ts index 3c74df077..139a8e2a8 100644 --- a/src/v4/core/hooks/collections/useUsersCollection.ts +++ b/src/v4/core/hooks/collections/useUsersCollection.ts @@ -5,10 +5,8 @@ import useLiveCollection from '~/v4/core/hooks/useLiveCollection'; const MINIMUM_STRING_LENGTH_TO_TRIGGER_QUERY = 1; - export const useUserQueryByDisplayName = ( - displayName: string, - minLength: number = MINIMUM_STRING_LENGTH_TO_TRIGGER_QUERY, + params: Parameters<typeof UserRepository.searchUserByDisplayName>[0], ) => { const [items, setItems] = useState<Amity.User[]>([]); const [isLoading, setIsLoading] = useState(false); @@ -25,29 +23,24 @@ export const useUserQueryByDisplayName = ( }; useEffect(() => { - if (displayName.length < minLength) return; - if (unSubRef.current) { unSubRef.current(); unSubRef.current = null; } - const unSubFn = UserRepository.searchUserByDisplayName( - { displayName, limit: 10 }, - (response) => { - setHasMore(response.hasNextPage || false); - setIsLoading(response.loading); - loadMoreRef.current = response.onNextPage || null; - setItems(response.data); - }, - ); + const unSubFn = UserRepository.searchUserByDisplayName(params, (response) => { + setHasMore(response.hasNextPage || false); + setIsLoading(response.loading); + loadMoreRef.current = response.onNextPage || null; + setItems(response.data); + }); unSubRef.current = unSubFn; return () => { unSubRef.current?.(); unSubRef.current = null; }; - }, [displayName, minLength]); + }, [params]); return { users: items, diff --git a/src/v4/core/hooks/objects/useUser.ts b/src/v4/core/hooks/objects/useUser.ts index c410e1e95..398a41da7 100644 --- a/src/v4/core/hooks/objects/useUser.ts +++ b/src/v4/core/hooks/objects/useUser.ts @@ -1,16 +1,8 @@ -/* eslint-disable no-nested-ternary */ - -import { SubscriptionLevels, UserRepository } from '@amityco/ts-sdk'; +import { UserRepository } from '@amityco/ts-sdk'; import useLiveObject from '~/v4/core/hooks/useLiveObject'; -import useUserSubscription from '~/social/hooks/useUserSubscription'; const useUser = (userId?: string | null) => { - useUserSubscription({ - userId, - level: SubscriptionLevels.USER, - }); - const { item, ...rest } = useLiveObject({ fetcher: UserRepository.getUser, params: userId, diff --git a/src/v4/core/providers/NavigationProvider.tsx b/src/v4/core/providers/NavigationProvider.tsx index cc1c1e1ae..12aa21d77 100644 --- a/src/v4/core/providers/NavigationProvider.tsx +++ b/src/v4/core/providers/NavigationProvider.tsx @@ -13,6 +13,7 @@ export enum PageTypes { PostDetailPage = 'PostDetailPage', CommunityProfilePage = 'CommunityProfilePage', UserProfilePage = 'UserProfilePage', + SocialGlobalSearchPage = 'SocialGlobalSearchPage', } type Page = @@ -67,7 +68,8 @@ type Page = } | { type: PageTypes.CommunityProfilePage; context: { communityId: string } } | { type: PageTypes.UserProfilePage; context: { userId: string; communityId?: string } } - | { type: PageTypes.SocialHomePage; context: { communityId?: string } }; + | { type: PageTypes.SocialHomePage; context: { communityId?: string } } + | { type: PageTypes.SocialGlobalSearchPage; context: { tab?: string } }; type ContextValue = { page: Page; @@ -88,6 +90,7 @@ type ContextValue = { goToUserProfilePage: (userId: string) => void; goToPostDetailPage: (postId: string) => void; goToCommunityProfilePage: (communityId: string) => void; + goToSocialGlobalSearchPage: (tab?: string) => void; setNavigationBlocker?: ( params: | { @@ -118,6 +121,7 @@ let defaultValue: ContextValue = { goToUserProfilePage: (userId: string) => {}, goToPostDetailPage: (postId: string) => {}, goToCommunityProfilePage: (communityId: string) => {}, + goToSocialGlobalSearchPage: (tab?: string) => {}, setNavigationBlocker: () => {}, onBack: () => {}, }; @@ -145,6 +149,8 @@ if (process.env.NODE_ENV !== 'production') { goToPostDetailPage: (postId) => console.log(`NavigationContext goToPostDetailPage(${postId})`), goToCommunityProfilePage: (communityId) => console.log(`NavigationContext goToCommunityProfilePage(${communityId})`), + goToSocialGlobalSearchPage: (tab) => + console.log(`NavigationContext goToSocialGlobalSearchPage(${tab})`), }; } @@ -399,6 +405,18 @@ export default function NavigationProvider({ [onChangePage, pushPage], ); + const goToSocialGlobalSearchPage = useCallback( + (tab?: string) => { + const next = { + type: PageTypes.SocialGlobalSearchPage, + context: { tab }, + }; + + pushPage(next); + }, + [onChangePage, pushPage], + ); + return ( <NavigationContext.Provider value={{ @@ -415,6 +433,7 @@ export default function NavigationProvider({ onBack: handleBack, goToUserProfilePage, goToPostDetailPage, + goToSocialGlobalSearchPage, goToCommunityProfilePage, setNavigationBlocker, }} diff --git a/src/v4/social/components/TopNavigation/TopNavigation.tsx b/src/v4/social/components/TopNavigation/TopNavigation.tsx index 30949262c..91ac8f882 100644 --- a/src/v4/social/components/TopNavigation/TopNavigation.tsx +++ b/src/v4/social/components/TopNavigation/TopNavigation.tsx @@ -4,6 +4,7 @@ import { GlobalSearchButton } from '~/v4/social/elements/GlobalSearchButton'; import { HeaderLabel } from '~/v4/social/elements/HeaderLabel'; import styles from './TopNavigation.module.css'; import { useAmityComponent } from '~/v4/core/hooks/uikit'; +import { useNavigation } from '~/v4/core/providers/NavigationProvider'; export interface TopNavigationProps { pageId?: string; @@ -17,6 +18,8 @@ export function TopNavigation({ pageId = '*' }: TopNavigationProps) { componentId, }); + const { goToSocialGlobalSearchPage } = useNavigation(); + if (isExcluded) return null; return ( @@ -25,7 +28,11 @@ export function TopNavigation({ pageId = '*' }: TopNavigationProps) { <HeaderLabel pageId={pageId} componentId={componentId} /> </div> <div className={styles.topNavigationRightPane}> - <GlobalSearchButton pageId={pageId} componentId={componentId} /> + <GlobalSearchButton + pageId={pageId} + componentId={componentId} + onClick={() => goToSocialGlobalSearchPage()} + /> <PostCreationButton pageId={pageId} componentId={componentId} /> </div> </div> diff --git a/src/v4/social/components/TopSearchBar/TopSearchBar.tsx b/src/v4/social/components/TopSearchBar/TopSearchBar.tsx index 87ac2ddb7..e963aa063 100644 --- a/src/v4/social/components/TopSearchBar/TopSearchBar.tsx +++ b/src/v4/social/components/TopSearchBar/TopSearchBar.tsx @@ -7,6 +7,7 @@ import { useGenerateStylesShadeColors } from '~/v4/core/providers/ThemeProvider' import styles from './TopSearchBar.module.css'; import { useAmityComponent } from '~/v4/core/hooks/uikit'; +import { useNavigation } from '~/v4/core/providers/NavigationProvider'; export interface TopSearchBarProps { pageId?: string; @@ -21,6 +22,8 @@ export function TopSearchBar({ pageId = '*', search }: TopSearchBarProps) { componentId, }); + const { onBack } = useNavigation(); + const [searchValue, setSearchValue] = useState(''); useEffect(() => { @@ -57,7 +60,7 @@ export function TopSearchBar({ pageId = '*', search }: TopSearchBarProps) { /> ) : null} </div> - <CancelButton pageId={pageId} componentId={componentId} /> + <CancelButton pageId={pageId} componentId={componentId} onClick={() => onBack()} /> </div> ); } diff --git a/src/v4/social/pages/Application/index.tsx b/src/v4/social/pages/Application/index.tsx index 63f869ef6..9854e63db 100644 --- a/src/v4/social/pages/Application/index.tsx +++ b/src/v4/social/pages/Application/index.tsx @@ -4,6 +4,7 @@ import { SocialHomePage } from '~/v4/social/pages/SocialHomePage'; import { PostDetailPage } from '~/v4/social/pages/PostDetailPage'; import { PageTypes, useNavigation } from '~/v4/core/providers/NavigationProvider'; import { StoryProvider } from '~/v4/social/providers/StoryProvider'; +import { SocialGlobalSearchPage } from '~/v4/social/pages/SocialGlobalSearchPage'; const Application = () => { const { page } = useNavigation(); @@ -12,6 +13,7 @@ const Application = () => { <StoryProvider> <div className={styles.applicationContainer}> {page.type === PageTypes.SocialHomePage && <SocialHomePage />} + {page.type === PageTypes.SocialGlobalSearchPage && <SocialGlobalSearchPage />} {page.type === PageTypes.PostDetailPage && <PostDetailPage id={page.context.postId} />} </div> </StoryProvider> diff --git a/src/v4/social/pages/SocialGlobalSearchPage/SocialGlobalSearchPage.tsx b/src/v4/social/pages/SocialGlobalSearchPage/SocialGlobalSearchPage.tsx index 8fc86add3..0f82dad3e 100644 --- a/src/v4/social/pages/SocialGlobalSearchPage/SocialGlobalSearchPage.tsx +++ b/src/v4/social/pages/SocialGlobalSearchPage/SocialGlobalSearchPage.tsx @@ -21,10 +21,10 @@ const useGlobalSearchViewModel = () => { AmityGlobalSearchType.Community, ); const communityCollection = useCommunitiesCollection( - { displayName: searchKeyword }, + { displayName: searchKeyword, limit: 20 }, () => searchType === AmityGlobalSearchType.Community, ); - const userCollection = useUserQueryByDisplayName(searchKeyword); + const userCollection = useUserQueryByDisplayName({ displayName: searchKeyword, limit: 20 }); const search = useCallback((keyword: string) => { setSearchKeyword(keyword); From ce29902e8256e7c2694494058537b291f52f9f46 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Thu, 13 Jun 2024 12:02:47 +0700 Subject: [PATCH 121/300] fix: ASC-22335 - VideoViewer styles (#401) * fix: fix VideoViewer * chore: remove console.log --- .../VideoContent/VideoContent.module.css | 18 ++- .../PostContent/VideoContent/VideoContent.tsx | 6 +- .../VideoViewer/VideoViewer.tsx | 121 ++++++++++++------ 3 files changed, 103 insertions(+), 42 deletions(-) diff --git a/src/v4/social/components/PostContent/VideoContent/VideoContent.module.css b/src/v4/social/components/PostContent/VideoContent/VideoContent.module.css index 0a92b8917..1f5b8e9d5 100644 --- a/src/v4/social/components/PostContent/VideoContent/VideoContent.module.css +++ b/src/v4/social/components/PostContent/VideoContent/VideoContent.module.css @@ -32,15 +32,27 @@ } .videoContent__video { - object-fit: cover; - object-position: center; - height: 100%; width: 100%; + position: absolute; + top: 0; + margin: auto; } .videoContent__videoContainer { cursor: pointer; position: relative; + width: 100%; + padding-top: 56.25%; +} + +.videoContent__videoContainer[data-videos-amount='1'], +.videoContent__videoContainer[data-videos-amount='2'] { + padding-top: 56.25%; +} + +.videoContent__videoContainer[data-videos-amount='3'], +.videoContent__videoContainer[data-videos-amount='4'] { + padding-top: 100%; } .videoContent__videoContainer:nth-child(1) { diff --git a/src/v4/social/components/PostContent/VideoContent/VideoContent.tsx b/src/v4/social/components/PostContent/VideoContent/VideoContent.tsx index a59d6aa32..dabe79de4 100644 --- a/src/v4/social/components/PostContent/VideoContent/VideoContent.tsx +++ b/src/v4/social/components/PostContent/VideoContent/VideoContent.tsx @@ -74,7 +74,11 @@ export const VideoContent = ({ data-videos-amount={Math.min(post.children.length, 4)} > {videoPosts.map((post, index) => ( - <div className={styles.videoContent__videoContainer} onClick={() => onVideoClick(index)}> + <div + className={styles.videoContent__videoContainer} + data-videos-amount={Math.min(post.children.length, 4)} + onClick={() => onVideoClick(index)} + > <Video fileId={post.data.thumbnailFileId} /> {videoLeftCount > 0 && index === posts.length - 1 && ( <Typography.Heading className={styles.videoContent__videoCover}> diff --git a/src/v4/social/internal-components/VideoViewer/VideoViewer.tsx b/src/v4/social/internal-components/VideoViewer/VideoViewer.tsx index cdd1c06b6..b61fccdd0 100644 --- a/src/v4/social/internal-components/VideoViewer/VideoViewer.tsx +++ b/src/v4/social/internal-components/VideoViewer/VideoViewer.tsx @@ -1,17 +1,60 @@ -import React from 'react'; +import React, { memo, useMemo, useState } from 'react'; import useFile from '~/core/hooks/useFile'; import { VideoFileStatus } from '~/social/constants'; import usePostByIds from '~/social/hooks/usePostByIds'; import { useAmityElement } from '~/v4/core/hooks/uikit'; +import AngleRight from '~/v4/icons/AngleRight'; import { ClearButton } from '~/v4/social/elements/ClearButton/ClearButton'; import styles from './VideoViewer.module.css'; +const VideoPlayer = memo(({ videoFileId }: { videoFileId: string }) => { + const file: Amity.File<'video'> | undefined = useFile<Amity.File<'video'>>(videoFileId); + + /* + * It's possible that certain video formats uploaded by the user are not + * playable by the browser. So it's best to use the transcoded video file + * which is an mp4 format to play video. + * + * Note: the below logic needs to be smarter based on users bandwidth and also + * should be switchable by the user, which would require a ui update + */ + const url = useMemo(() => { + if (file == null) return null; + if (file.status === VideoFileStatus.Transcoded) { + const { videoUrl } = file; + + return ( + videoUrl?.['1080p'] || + videoUrl?.['720p'] || + videoUrl?.['480p'] || + videoUrl?.['360p'] || + videoUrl?.original || + file.fileUrl + ); + } + return file.fileUrl; + }, [file]); + + if (url == null) return <></>; + + return ( + <video controls controlsList="nodownload" autoPlay className={styles.fullImage}> + <source src={url} type="video/mp4" /> + <p> + Your browser does not support this format of video. Please try again later once the server + transcodes the video into an playable format(mp4). + </p> + </video> + ); +}); + interface VideoViewerProps { pageId?: string; componentId?: string; elementId?: string; post: Amity.Post; onClose(): void; + initialVideoIndex: number; } export function VideoViewer({ @@ -19,60 +62,62 @@ export function VideoViewer({ componentId = '*', elementId = '*', post, + initialVideoIndex, onClose, }: VideoViewerProps) { const { themeStyles } = useAmityElement({ pageId, componentId, elementId }); + const [selectedVideoIndex, setSelectedVideoIndex] = useState(initialVideoIndex); + const posts = usePostByIds(post?.children || []); const videoPosts = posts.filter((post) => post.dataType === 'video'); - const videoFileId = - videoPosts?.[0]?.data.videoFileId.high || - videoPosts?.[0]?.data.videoFileId.medium || - videoPosts?.[0]?.data.videoFileId.low || - videoPosts?.[0]?.data.videoFileId.original || - undefined; + const videoFileId = useMemo(() => { + return ( + videoPosts?.[selectedVideoIndex]?.data.videoFileId.high || + videoPosts?.[selectedVideoIndex]?.data.videoFileId.medium || + videoPosts?.[selectedVideoIndex]?.data.videoFileId.low || + videoPosts?.[selectedVideoIndex]?.data.videoFileId.original || + undefined + ); + }, [videoPosts, selectedVideoIndex]); - const file: Amity.File<'video'> | undefined = useFile<Amity.File<'video'>>(videoFileId); + const hasNext = selectedVideoIndex < videoPosts.length - 1; + const hasPrev = selectedVideoIndex > 0; - if (file == null) return null; - - /* - * It's possible that certain video formats uploaded by the user are not - * playable by the browser. So it's best to use the transcoded video file - * which is an mp4 format to play video. - * - * Note: the below logic needs to be smarter based on users bandwidth and also - * should be switchable by the user, which would require a ui update - */ - const url = (() => { - if (file.status === VideoFileStatus.Transcoded) { - const { videoUrl } = file; + const next = () => { + if (!hasNext) { + return; + } + setSelectedVideoIndex((prev) => prev + 1); + }; - return ( - videoUrl?.['1080p'] || - videoUrl?.['720p'] || - videoUrl?.['480p'] || - videoUrl?.['360p'] || - videoUrl?.original || - file.fileUrl - ); + const prev = () => { + if (!hasPrev) { + return; } - return file.fileUrl; - })(); + setSelectedVideoIndex((prev) => prev - 1); + }; return ( <div style={themeStyles}> <div className={styles.modal} onClick={onClose}> <div className={styles.modalContent} onClick={(e) => e.stopPropagation()}> - <video controls controlsList="nodownload" autoPlay className={styles.fullImage}> - <source src={url} type="video/mp4" /> - <p> - Your browser does not support this format of video. Please try again later once the - server transcodes the video into an playable format(mp4). - </p> - </video> + <VideoPlayer videoFileId={videoFileId} /> + <div className={styles.overlayPanel}> + {hasPrev && ( + <div className={styles.overlayPanel__prev} onClick={prev}> + <AngleRight className={styles.overlayPanel__prevButton} /> + </div> + )} + <div className={styles.overlayPanel__middle} /> + {hasNext && ( + <div className={styles.overlayPanel__next} onClick={next}> + <AngleRight className={styles.overlayPanel__nextButton} /> + </div> + )} + </div> <span className={styles.closeButton} onClick={onClose}> <ClearButton pageId={pageId} From af3a58d817072254b1c46d783df27e601163620b Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Thu, 13 Jun 2024 15:55:50 +0700 Subject: [PATCH 122/300] fix: ASC-21508 - hyperlink to show confirm when back with data (#404) * fix: hyperlink to show confirm when back with data * fix: bring bank old cold * fix: pageId no need to pass prop --- src/i18n/en.json | 4 + src/index.ts | 2 +- .../BottomSheet/BottomSheet.module.css | 10 +-- .../components/BottomSheet/BottomSheet.tsx | 7 +- src/v4/core/providers/NavigationProvider.tsx | 44 ++++++++-- .../core/providers/PageBehaviorProvider.tsx | 48 ++++++++++- .../HyperLinkConfig/HyperLinkConfig.tsx | 81 ++++++++++--------- .../social/components/StoryTab/StoryTab.tsx | 9 +-- .../StoryTab/StoryTabGlobalFeed.tsx | 15 ++-- .../AspectRatioButton.module.css | 20 +++++ .../AspectRatioButton/AspectRatioButton.tsx | 81 +++++++++++-------- .../elements/AspectRatioButton/styles.tsx | 22 ----- .../CancelButton/CancelButton.module.css | 2 +- .../elements/CancelButton/CancelButton.tsx | 2 +- .../elements/DoneButton/DoneButton.module.css | 3 + .../social/elements/DoneButton/DoneButton.tsx | 30 +++++++ src/v4/social/elements/DoneButton/index.ts | 1 + .../ShareStoryButton/ShareStoryButton.tsx | 6 +- src/v4/social/pages/Application/index.tsx | 2 + src/v4/social/pages/DraftsPage/DraftsPage.tsx | 5 +- .../pages/StoryPage/GlobalFeedStory.tsx | 54 +++++++------ .../social/pages/StoryPage/ViewStoryPage.tsx | 17 ++-- src/v4/social/pages/StoryPage/index.tsx | 2 +- src/v4/social/pages/index.ts | 2 +- 24 files changed, 297 insertions(+), 172 deletions(-) create mode 100644 src/v4/social/elements/AspectRatioButton/AspectRatioButton.module.css delete mode 100644 src/v4/social/elements/AspectRatioButton/styles.tsx create mode 100644 src/v4/social/elements/DoneButton/DoneButton.module.css create mode 100644 src/v4/social/elements/DoneButton/DoneButton.tsx create mode 100644 src/v4/social/elements/DoneButton/index.ts diff --git a/src/i18n/en.json b/src/i18n/en.json index 21c0ba99e..c769931d2 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -367,6 +367,10 @@ "storyCreation.hyperlink.validation.error.invalidUrl": "Please enter a valid URL.", "storyCreation.hyperlink.validation.error.whitelisted": "Please enter a whitelisted URL.", "storyCreation.hyperlink.validation.error.blocked": "Your text contains a blocklisted word.", + "storyCreation.hyperlink.unsavedChanges.title": "Unsaved changes", + "storyCreation.hyperlink.unsavedChanges.content": "Are you sure you want to cancel? Your changes won't be saved.", + "storyCreation.hyperlink.unsavedChanges.cancel": "No", + "storyCreation.hyperlink.unsavedChanges.confirm": "Yes", "storyViewer.actions.deleteStory": "Delete story", "storyViewer.action.confirmModal.title": "Delete this story?", diff --git a/src/index.ts b/src/index.ts index 835c8c3ef..1d3bbd08a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,7 +20,7 @@ export { useSDK as useAmitySDK } from '~/core/hooks/useSDK'; // v4 export { default as AmityUIKitManager } from '~/v4/core/AmityUIKitManager'; -export { AmityDraftStoryPage, AmityViewStoryPage } from '~/v4/social/pages'; +export { AmityDraftStoryPage, ViewStoryPage as AmityViewStoryPage } from '~/v4/social/pages'; export { CommentTray as AmityCommentTrayComponent, StoryTab as AmityStoryTabComponent, diff --git a/src/v4/core/components/BottomSheet/BottomSheet.module.css b/src/v4/core/components/BottomSheet/BottomSheet.module.css index b12fe9ae8..5925f02c7 100644 --- a/src/v4/core/components/BottomSheet/BottomSheet.module.css +++ b/src/v4/core/components/BottomSheet/BottomSheet.module.css @@ -5,13 +5,13 @@ which have higher specificity. */ .bottomSheet__container { - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-base-background) !important; } -.bottomSheet__header { - padding: 1rem; - display: flex; - justify-content: center; +.bottomSheet__default__header { + background-color: var(--asc-color-base-background); + border-top-left-radius: 1rem; + border-top-right-radius: 1rem; } .bottomSheet__content { diff --git a/src/v4/core/components/BottomSheet/BottomSheet.tsx b/src/v4/core/components/BottomSheet/BottomSheet.tsx index 5b5653ab9..d8cf0dcf4 100644 --- a/src/v4/core/components/BottomSheet/BottomSheet.tsx +++ b/src/v4/core/components/BottomSheet/BottomSheet.tsx @@ -21,12 +21,7 @@ export const BottomSheet = ({ children, headerTitle, ...props }: BottomSheetProp return ( <Sheet {...props}> <Sheet.Container className={styles.bottomSheet__container}> - <Sheet.Header - style={{ - borderTopLeftRadius: '1.25rem', - borderTopRightRadius: '1.25rem', - }} - > + <Sheet.Header className={styles.bottomSheet__header}> <Sheet.Header /> {headerTitle && ( <Sheet.Header className={styles.bottomSheet__header}> diff --git a/src/v4/core/providers/NavigationProvider.tsx b/src/v4/core/providers/NavigationProvider.tsx index 12aa21d77..645177a34 100644 --- a/src/v4/core/providers/NavigationProvider.tsx +++ b/src/v4/core/providers/NavigationProvider.tsx @@ -8,7 +8,7 @@ export enum PageTypes { Category = 'category', UserFeed = 'userFeed', UserEdit = 'userEdit', - ViewStory = 'viewStory', + ViewStoryPage = 'ViewStoryPage', SocialHomePage = 'SocialHomePage', PostDetailPage = 'PostDetailPage', CommunityProfilePage = 'CommunityProfilePage', @@ -50,13 +50,12 @@ type Page = }; } | { - type: PageTypes.ViewStory; + type: PageTypes.ViewStoryPage; context: { - storyId: string; - targetId?: string; - communityId?: string; + targetId: string; + targetType: Amity.StoryTargetType; + storyType: 'communityFeed' | 'globalFeed'; targetIds?: string[]; - storyType?: 'communityFeed' | 'globalFeed'; }; } | { @@ -91,6 +90,12 @@ type ContextValue = { goToPostDetailPage: (postId: string) => void; goToCommunityProfilePage: (communityId: string) => void; goToSocialGlobalSearchPage: (tab?: string) => void; + goToViewStoryPage: ( + targetId: string, + targetType: string, + storyType: 'communityFeed' | 'globalFeed', + targetIds?: string[], + ) => void; setNavigationBlocker?: ( params: | { @@ -120,6 +125,11 @@ let defaultValue: ContextValue = { onMessageUser: (userId: string) => {}, goToUserProfilePage: (userId: string) => {}, goToPostDetailPage: (postId: string) => {}, + goToViewStoryPage: ( + targetId: string, + targetType: string, + storyType: 'communityFeed' | 'globalFeed', + ) => {}, goToCommunityProfilePage: (communityId: string) => {}, goToSocialGlobalSearchPage: (tab?: string) => {}, setNavigationBlocker: () => {}, @@ -147,6 +157,8 @@ if (process.env.NODE_ENV !== 'production') { goToUserProfilePage: (userId) => console.log(`NavigationContext goToUserProfilePage(${userId})`), goToPostDetailPage: (postId) => console.log(`NavigationContext goToPostDetailPage(${postId})`), + goToViewStoryPage: (targetId, targetType, storyType) => + console.log(`NavigationContext goToViewStoryPage(${targetId}) ${targetType} ${storyType}`), goToCommunityProfilePage: (communityId) => console.log(`NavigationContext goToCommunityProfilePage(${communityId})`), goToSocialGlobalSearchPage: (tab) => @@ -348,7 +360,7 @@ export default function NavigationProvider({ const handleClickStory = useCallback( (targetId, storyType, targetIds) => { const next = { - type: PageTypes.ViewStory, + type: PageTypes.ViewStoryPage, targetId, storyType, targetIds, @@ -384,7 +396,22 @@ export default function NavigationProvider({ }, }; - console.log('postId', postId); + pushPage(next); + }, + [onChangePage, pushPage], + ); + + const goToViewStoryPage = useCallback( + (targetId, targetType, storyType, targetIds) => { + const next = { + type: PageTypes.ViewStoryPage, + context: { + targetId, + targetType, + storyType, + targetIds, + }, + }; pushPage(next); }, @@ -435,6 +462,7 @@ export default function NavigationProvider({ goToPostDetailPage, goToSocialGlobalSearchPage, goToCommunityProfilePage, + goToViewStoryPage, setNavigationBlocker, }} > diff --git a/src/v4/core/providers/PageBehaviorProvider.tsx b/src/v4/core/providers/PageBehaviorProvider.tsx index daad33e25..20ae52895 100644 --- a/src/v4/core/providers/PageBehaviorProvider.tsx +++ b/src/v4/core/providers/PageBehaviorProvider.tsx @@ -2,6 +2,10 @@ import React, { useContext } from 'react'; import { useNavigation } from '~/v4/core/providers/NavigationProvider'; export interface PageBehavior { + AmityStoryViewPageBehavior: { + onCloseAction(): void; + hyperLinkAction?(context: Record<string, unknown>): void; + }; AmityDraftStoryPageBehavior: { onCloseAction(): void; }; @@ -9,6 +13,12 @@ export interface PageBehavior { AmitySocialHomePageBehavior: Record<string, unknown>; AmityGlobalFeedComponentBehavior: { goToPostDetailPage: (context: { postId: string }) => void; + goToViewStoryPage: (context: { + targetId: string; + targetType: string; + storyType: 'communityFeed' | 'globalFeed'; + targetIds?: string[]; + }) => void; }; AmityPostDetailPageBehavior: Record<string, unknown>; AmityPostContentComponentBehavior: { @@ -32,9 +42,27 @@ export const PageBehaviorProvider: React.FC<PageBehaviorProviderProps> = ({ children, pageBehavior = {}, }) => { - const { onBack, goToPostDetailPage, goToCommunityProfilePage, goToUserProfilePage } = - useNavigation(); + const { + onBack, + goToPostDetailPage, + goToCommunityProfilePage, + goToUserProfilePage, + goToViewStoryPage, + } = useNavigation(); const navigationBehavior: PageBehavior = { + AmityStoryViewPageBehavior: { + onCloseAction: () => { + if (pageBehavior?.AmityStoryViewPageBehavior?.onCloseAction) { + return pageBehavior.AmityStoryViewPageBehavior.onCloseAction(); + } + onBack(); + }, + hyperLinkAction: (context: Record<string, unknown>) => { + if (pageBehavior?.AmityStoryViewPageBehavior?.hyperLinkAction) { + return pageBehavior.AmityStoryViewPageBehavior.hyperLinkAction(context); + } + }, + }, AmityDraftStoryPageBehavior: { onCloseAction: () => { if (pageBehavior?.AmityDraftStoryPageBehavior?.onCloseAction) { @@ -52,6 +80,22 @@ export const PageBehaviorProvider: React.FC<PageBehaviorProviderProps> = ({ } goToPostDetailPage(context.postId); }, + goToViewStoryPage: (context: { + targetId: string; + targetType: string; + storyType: 'communityFeed' | 'globalFeed'; + targetIds?: string[]; + }) => { + if (pageBehavior?.AmityGlobalFeedComponentBehavior?.goToViewStoryPage) { + return pageBehavior?.AmityGlobalFeedComponentBehavior.goToViewStoryPage(context); + } + goToViewStoryPage( + context.targetId, + context.targetType, + context.storyType, + context.targetIds, + ); + }, }, AmityPostDetailPageBehavior: {}, AmityPostContentComponentBehavior: { diff --git a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx index e806c0d35..a89a79a27 100644 --- a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx +++ b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx @@ -1,17 +1,20 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { useIntl } from 'react-intl'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; import clsx from 'clsx'; -import useSDK from '~/core/hooks/useSDK'; import { BottomSheet, Typography } from '~/v4/core/components'; -import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; -import { Trash2Icon } from '~/icons'; + import styles from './HyperLinkConfig.module.css'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; import { Button } from '~/v4/core/components/Button'; +import useSDK from '~/v4/core/hooks/useSDK'; +import Trash from '~/v4/social/icons/trash'; +import { useAmityComponent } from '~/v4/core/hooks/uikit'; +import { CancelButton } from '~/v4/social/elements/CancelButton'; +import { DoneButton } from '~/v4/social/elements/DoneButton'; interface HyperLinkConfigProps { pageId: string; @@ -32,19 +35,19 @@ export const HyperLinkConfig = ({ onSubmit, onRemove, }: HyperLinkConfigProps) => { - const { confirm } = useConfirmContext(); - const componentId = 'hyper_link_config_component'; - const { getConfig, isExcluded } = useCustomization(); - - const componentConfig = getConfig(`${pageId}/${componentId}/*`); - const componentTheme = componentConfig?.theme?.light || {}; + const { confirm } = useConfirmContext(); + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityComponent({ + pageId, + componentId, + }); - const cancelButtonConfig = getConfig(`*/hyper_link_config_component/cancel_button`); - const doneButtonConfig = getConfig(`*/hyper_link_config_component/done_button`); + if (isExcluded) return null; const { formatMessage } = useIntl(); const { client } = useSDK(); + const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); const schema = z.object({ url: z @@ -107,6 +110,12 @@ export const HyperLinkConfig = ({ resolver: zodResolver(schema), }); + const { url, customText } = watch(); + + useEffect(() => { + setHasUnsavedChanges(url !== '' || customText !== ''); + }, [url, customText]); + const onSubmitForm = async (data: HyperLinkFormInputs) => { onSubmit(data); onClose(); @@ -128,44 +137,38 @@ export const HyperLinkConfig = ({ }); }; + const handleClose = () => { + if (hasUnsavedChanges) { + confirm({ + title: formatMessage({ id: 'storyCreation.hyperlink.unsavedChanges.title' }), + content: formatMessage({ id: 'storyCreation.hyperlink.unsavedChanges.content' }), + cancelText: formatMessage({ id: 'storyCreation.hyperlink.unsavedChanges.cancel' }), + okText: formatMessage({ id: 'storyCreation.hyperlink.unsavedChanges.confirm' }), + onOk: () => { + reset(); + onClose(); + }, + }); + } else { + onClose(); + } + }; + return ( <BottomSheet detent="full-height" mountPoint={document.getElementById('asc-uikit-create-story') as HTMLElement} rootId="asc-uikit-create-story" isOpen={isOpen} - onClose={onClose} + onClose={handleClose} className={styles.bottomSheet} > <div className={styles.headerContainer}> - <Button className={clsx(styles.cancelButton)} variant="ghost" onClick={onClose}> - <Typography.Body> - {cancelButtonConfig?.cancel_button_text || - formatMessage({ id: 'storyCreation.hyperlink.bottomSheet.cancel' })} - </Typography.Body> - {cancelButtonConfig?.cancel_icon && - typeof cancelButtonConfig?.cancel_icon === 'string' && ( - <img src={cancelButtonConfig?.cancel_icon} width={16} height={16} /> - )} - </Button> + <CancelButton pageId="*" componentId={componentId} onClick={handleClose} /> <Typography.Title> {formatMessage({ id: 'storyCreation.hyperlink.bottomSheet.title' })} </Typography.Title> - <Button - variant="ghost" - form="asc-story-hyperlink-form" - type="submit" - className={clsx(styles.doneButton)} - disabled={!watch('url') || !!errors.url} - > - <Typography.Body> - {doneButtonConfig?.done_button_text || - formatMessage({ id: 'storyCreation.hyperlink.bottomSheet.submit' })} - </Typography.Body> - {doneButtonConfig?.done_icon && typeof doneButtonConfig?.done_icon === 'string' && ( - <img src={doneButtonConfig.done_icon} width={16} height={16} /> - )} - </Button> + <DoneButton pageId="*" componentId={componentId} onClick={handleSubmit(onSubmitForm)} /> </div> <div className={styles.divider} /> <div className={styles.hyperlinkFormContainer}> @@ -230,7 +233,7 @@ export const HyperLinkConfig = ({ onClick={discardHyperlink} className={clsx(styles.removeLinkButton)} > - <Trash2Icon className={styles.removeIcon} /> + <Trash className={styles.removeIcon} /> {formatMessage({ id: 'storyCreation.hyperlink.form.removeButton' })} </Button> <div className={styles.divider} /> diff --git a/src/v4/social/components/StoryTab/StoryTab.tsx b/src/v4/social/components/StoryTab/StoryTab.tsx index 914994c85..7004368ed 100644 --- a/src/v4/social/components/StoryTab/StoryTab.tsx +++ b/src/v4/social/components/StoryTab/StoryTab.tsx @@ -3,17 +3,14 @@ import React from 'react'; import { StoryTabCommunityFeed } from './StoryTabCommunity'; import { StoryTabGlobalFeed } from './StoryTabGlobalFeed'; -type AmityStoryTabComponentType = 'communityFeed' | 'globalFeed'; +type StoryTabType = 'communityFeed' | 'globalFeed'; -type AmityStoryTabComponentProps<T extends AmityStoryTabComponentType> = { +type StoryTabProps<T extends StoryTabType> = { type: T; communityId?: T extends 'communityFeed' ? string : never; }; -export const StoryTab = <T extends AmityStoryTabComponentType>({ - type, - communityId, -}: AmityStoryTabComponentProps<T>) => { +export const StoryTab = <T extends StoryTabType>({ type, communityId }: StoryTabProps<T>) => { const renderStoryTab = () => { switch (type) { case 'communityFeed': diff --git a/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx b/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx index 1d36be6dc..d36ad5436 100644 --- a/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx +++ b/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx @@ -1,8 +1,8 @@ import React, { useRef, useEffect } from 'react'; import styles from './StoryTabGlobalFeed.module.css'; -import { useNavigation } from '~/social/providers/NavigationProvider'; import { StoryTabItem } from './StoryTabItem'; import { useGlobalStoryTargets } from '~/v4/social/hooks/collections/useGlobalStoryTargets'; +import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; const STORIES_PER_PAGE = 10; @@ -11,7 +11,7 @@ export const StoryTabGlobalFeed: React.FC = () => { seenState: 'smart' as Amity.StorySeenQuery, limit: STORIES_PER_PAGE, }); - const { onClickStory } = useNavigation(); + const { AmityGlobalFeedComponentBehavior } = usePageBehavior(); const containerRef = useRef<HTMLDivElement>(null); const observerRef = useRef<IntersectionObserver | null>(null); @@ -67,11 +67,12 @@ export const StoryTabGlobalFeed: React.FC = () => { targetId={story.targetId} hasUnseen={story.hasUnseen} onClick={() => - onClickStory( - story.targetId, - 'globalFeed', - stories.map((s) => s.targetId), - ) + AmityGlobalFeedComponentBehavior.goToViewStoryPage({ + targetId: story.targetId, + targetType: story.targetType, + storyType: 'globalFeed', + targetIds: stories.map((story) => story.targetId), + }) } size={64} /> diff --git a/src/v4/social/elements/AspectRatioButton/AspectRatioButton.module.css b/src/v4/social/elements/AspectRatioButton/AspectRatioButton.module.css new file mode 100644 index 000000000..9da6d9166 --- /dev/null +++ b/src/v4/social/elements/AspectRatioButton/AspectRatioButton.module.css @@ -0,0 +1,20 @@ +.aspectRatioButton { + width: 2rem; + height: 2rem; + display: flex; + align-items: center; + justify-content: center; + border: none; + background-color: rgb(0 0 0 / 50%); + color: var(--asc-color-white); + cursor: pointer; + padding: 0.1875rem 0; + border-radius: 50%; + transition: background-color 0.3s; + flex-shrink: 0; +} + +.aspectRatioButton__icon { + width: 1.5rem; + height: 1.5rem; +} diff --git a/src/v4/social/elements/AspectRatioButton/AspectRatioButton.tsx b/src/v4/social/elements/AspectRatioButton/AspectRatioButton.tsx index 3cb8d85ce..92de404a8 100644 --- a/src/v4/social/elements/AspectRatioButton/AspectRatioButton.tsx +++ b/src/v4/social/elements/AspectRatioButton/AspectRatioButton.tsx @@ -1,46 +1,59 @@ import React from 'react'; +import clsx from 'clsx'; +import { IconComponent } from '~/v4/core/IconComponent'; +import styles from './AspectRatioButton.module.css'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; -import { ActionButton, CustomActionButton } from './styles'; - -import { isValidHttpUrl } from '~/utils'; -import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; +const AspectRatioSvg = (props: React.SVGProps<SVGSVGElement>) => ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="24" + height="24" + fill="none" + viewBox="0 0 24 24" + {...props} + > + <path + fill="currentColor" + d="M4.125 9.578v-4.36c0-.456.352-.843.844-.843h4.36c.21 0 .421.21.421.422v.844c0 .246-.21.421-.422.421H5.813v3.516c0 .246-.211.422-.422.422h-.844a.406.406 0 01-.422-.422zM14.25 4.797c0-.211.176-.422.422-.422h4.36c.456 0 .843.387.843.844v4.36c0 .245-.21.421-.422.421h-.844a.406.406 0 01-.422-.422V6.063h-3.515a.406.406 0 01-.422-.422v-.844zm5.203 9.703c.211 0 .422.21.422.422v4.36a.833.833 0 01-.844.843h-4.36a.406.406 0 01-.421-.422v-.844c0-.21.176-.422.422-.422h3.515v-3.515c0-.211.176-.422.422-.422h.844zM9.75 19.703c0 .246-.21.422-.422.422h-4.36c-.491 0-.843-.352-.843-.844v-4.36c0-.21.176-.421.422-.421h.844c.21 0 .421.21.421.422v3.515h3.516c.211 0 .422.211.422.422v.844z" + ></path> + </svg> +); interface AspectRatioButtonProps { + pageId?: string; + componentId?: string; + defaultIconClassName?: string; + imgIconClassName?: string; onClick: () => void; - pageId: 'create_story_page'; - componentId: '*'; - 'data-qa-anchor'?: string; } -export const AspectRatioButton = ({ - pageId = 'create_story_page', +export function AspectRatioButton({ + pageId = '*', componentId = '*', - onClick = () => {}, - ...props -}: AspectRatioButtonProps) => { + defaultIconClassName, + imgIconClassName, + onClick, +}: AspectRatioButtonProps) { const elementId = 'aspect_ratio_button'; - const { getConfig, isExcluded } = useCustomization(); - const elementConfig = getConfig(`${pageId}/${componentId}/${elementId}`); - const isElementExcluded = isExcluded(`${pageId}/${componentId}/${elementId}`); - const aspectRatioIcon = elementConfig?.aspect_ratio_icon; - - if (isElementExcluded) return null; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference } = useAmityElement({ + pageId, + componentId, + elementId, + }); - const isRemoteImage = aspectRatioIcon && isValidHttpUrl(aspectRatioIcon); + if (isExcluded) return null; - return isRemoteImage ? ( - <CustomActionButton - data-qa-anchor="aspect_ratio_button" - src={aspectRatioIcon} - onClick={onClick} - {...props} - /> - ) : ( - <ActionButton - data-qa-anchor="aspect_ratio_button" - name={'ExpandIcon'} - onClick={onClick} - {...props} - /> + return ( + <button onClick={onClick} className={styles.aspectRatioButton} data-qa-anchor={accessibilityId}> + <IconComponent + defaultIcon={() => ( + <AspectRatioSvg className={clsx(styles.aspectRatioButton__icon, defaultIconClassName)} /> + )} + imgIcon={() => <img src={config.icon} alt={uiReference} className={imgIconClassName} />} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} + /> + </button> ); -}; +} diff --git a/src/v4/social/elements/AspectRatioButton/styles.tsx b/src/v4/social/elements/AspectRatioButton/styles.tsx deleted file mode 100644 index 985425bba..000000000 --- a/src/v4/social/elements/AspectRatioButton/styles.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import styled from 'styled-components'; -import { Icon } from '~/v4/core/components'; - -export const ActionButton = styled(Icon)` - cursor: pointer; - border-radius: 50%; - padding: 0.25rem; - background-color: ${({ theme }) => theme.v4.colors.actionButton.default}; -`; - -export const CustomActionButton = styled.img` - width: 1.5rem; - height: 1.5rem; - cursor: pointer; - border: none; - outline: none; - padding: 0; - margin: 0; - display: flex; - align-items: center; - justify-content: center; -`; diff --git a/src/v4/social/elements/CancelButton/CancelButton.module.css b/src/v4/social/elements/CancelButton/CancelButton.module.css index f8e806e37..aca2bba95 100644 --- a/src/v4/social/elements/CancelButton/CancelButton.module.css +++ b/src/v4/social/elements/CancelButton/CancelButton.module.css @@ -12,6 +12,6 @@ button { } .clearButton { - color: var(--asc-color-primary-default); + color: var(--asc-color-base-default); background-color: var(--asc-color-base-background); } diff --git a/src/v4/social/elements/CancelButton/CancelButton.tsx b/src/v4/social/elements/CancelButton/CancelButton.tsx index 0b2f9ad33..7cd6d74c2 100644 --- a/src/v4/social/elements/CancelButton/CancelButton.tsx +++ b/src/v4/social/elements/CancelButton/CancelButton.tsx @@ -28,7 +28,7 @@ export const CancelButton = ({ return ( <button data-qa-anchor={accessibilityId} style={themeStyles} onClick={onClick}> - <Typography.Body className={styles.clearButton}>{config.text}</Typography.Body> + <Typography.Body className={styles.clearButton}>{config.cancel_button_text}</Typography.Body> </button> ); }; diff --git a/src/v4/social/elements/DoneButton/DoneButton.module.css b/src/v4/social/elements/DoneButton/DoneButton.module.css new file mode 100644 index 000000000..bbf0c36cd --- /dev/null +++ b/src/v4/social/elements/DoneButton/DoneButton.module.css @@ -0,0 +1,3 @@ +.doneButton_text { + color: var(--asc-color-primary-default); +} diff --git a/src/v4/social/elements/DoneButton/DoneButton.tsx b/src/v4/social/elements/DoneButton/DoneButton.tsx new file mode 100644 index 000000000..f1c978a90 --- /dev/null +++ b/src/v4/social/elements/DoneButton/DoneButton.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { Typography } from '~/v4/core/components'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import styles from './DoneButton.module.css'; + +export interface ExploreButtonProps { + pageId?: string; + componentId?: string; + onClick?: () => void; +} + +export function DoneButton({ pageId = '*', componentId = '*', onClick }: ExploreButtonProps) { + const elementId = 'done_button'; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + return ( + <button data-qa-anchor={accessibilityId} style={themeStyles} onClick={onClick}> + <Typography.Body className={styles.doneButton_text}> + {config.done_button_text} + </Typography.Body> + </button> + ); +} diff --git a/src/v4/social/elements/DoneButton/index.ts b/src/v4/social/elements/DoneButton/index.ts new file mode 100644 index 000000000..d18e8ca7e --- /dev/null +++ b/src/v4/social/elements/DoneButton/index.ts @@ -0,0 +1 @@ +export { DoneButton } from './DoneButton'; diff --git a/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx b/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx index 297d0dacc..8813ef1c9 100644 --- a/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx +++ b/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx @@ -10,15 +10,15 @@ import { Avatar } from '~/v4/core/components'; interface ShareButtonProps { onClick: () => void; - pageId: 'create_story_page'; - componentId: '*'; + pageId?: string; + componentId?: string; avatar?: string; style?: React.CSSProperties; 'data-qa-anchor'?: string; } export const ShareStoryButton = ({ - pageId = 'create_story_page', + pageId = '*', componentId = '*', onClick, avatar, diff --git a/src/v4/social/pages/Application/index.tsx b/src/v4/social/pages/Application/index.tsx index 9854e63db..20baa3c3f 100644 --- a/src/v4/social/pages/Application/index.tsx +++ b/src/v4/social/pages/Application/index.tsx @@ -5,6 +5,7 @@ import { PostDetailPage } from '~/v4/social/pages/PostDetailPage'; import { PageTypes, useNavigation } from '~/v4/core/providers/NavigationProvider'; import { StoryProvider } from '~/v4/social/providers/StoryProvider'; import { SocialGlobalSearchPage } from '~/v4/social/pages/SocialGlobalSearchPage'; +import { ViewStoryPage } from '~/v4/social/pages/StoryPage'; const Application = () => { const { page } = useNavigation(); @@ -15,6 +16,7 @@ const Application = () => { {page.type === PageTypes.SocialHomePage && <SocialHomePage />} {page.type === PageTypes.SocialGlobalSearchPage && <SocialGlobalSearchPage />} {page.type === PageTypes.PostDetailPage && <PostDetailPage id={page.context.postId} />} + {page.type === PageTypes.ViewStoryPage && <ViewStoryPage type="globalFeed" />} </div> </StoryProvider> ); diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index 720e76f89..2d1956613 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -39,6 +39,7 @@ type HyperLinkFormInputs = { }; const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStoryPageProps) => { + const pageId = 'create_story_page'; const { page, onChangePage, onClickCommunity } = useNavigation(); const { file, setFile } = useStoryContext(); const { AmityDraftStoryPageBehavior } = usePageBehavior(); @@ -285,12 +286,12 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor <div className={styles.footer}> <ShareStoryButton - pageId="create_story_page" + pageId="*" componentId="*" + avatar={community.avatarFileUrl} onClick={() => onCreateStory(file, imageMode, {}, hyperLink[0]?.data?.url ? hyperLink : []) } - avatar={community.avatarFileUrl} /> </div> </div> diff --git a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx index 4404ef090..8e98aadc9 100644 --- a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useRef, useState } from 'react'; -import useStories from '~/social/hooks/useStories'; + import useSDK from '~/core/hooks/useSDK'; import { useMedia } from 'react-use'; import { useIntl } from 'react-intl'; @@ -9,7 +9,7 @@ import { CreateStoryButton } from '../../elements'; import { Trash2Icon } from '~/icons'; import { isNonNullable } from '~/v4/helpers/utils'; import { extractColors } from 'extract-colors'; -import { useNavigation } from '~/social/providers/NavigationProvider'; + import { HiddenInput, StoryArrowLeftButton, @@ -19,6 +19,7 @@ import { ViewStoryContent, ViewStoryOverlay, } from '../../internal-components/StoryViewer/styles'; + import Stories from 'react-insta-stories'; import { renderers } from '../../internal-components/StoryViewer/Renderers'; import { AmityDraftStoryPage } from '..'; @@ -26,7 +27,10 @@ import { checkStoryPermission } from '~/utils'; import { useStoryContext } from '../../providers/StoryProvider'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; import { useNotifications } from '~/v4/core/providers/NotificationProvider'; -import { PageTypes } from '~/social/constants'; + +import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; +import { PageTypes, useNavigation } from '~/v4/core/providers/NavigationProvider'; +import { useGetActiveStoriesByTarget } from '../../hooks/useGetActiveStories'; const DURATION = 5000; @@ -35,16 +39,18 @@ interface GlobalFeedStoryProps { } export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = () => { - const { page } = useNavigation(); - const { onClickStory, onChangePage } = useNavigation(); + const { page, onChangePage, onClickStory } = useNavigation(); + const { AmityStoryViewPageBehavior } = usePageBehavior(); const { confirm } = useConfirmContext(); const notification = useNotifications(); - const { stories } = useStories({ + const { stories } = useGetActiveStoriesByTarget({ targetType: 'community', targetId: - page.type === PageTypes.ViewStory && page.storyType === 'globalFeed' && page.targetId - ? page.targetId + page.type === PageTypes.ViewStoryPage && + page.context.storyType === 'globalFeed' && + page.context.targetId + ? page.context.targetId : '', options: { orderBy: 'asc', @@ -90,7 +96,7 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = () => { okText: formatMessage({ id: 'delete' }), onOk: async () => { previousStory(); - if (isLastStory) onChangePage(PageTypes.NewsFeed); + // if (isLastStory) onChangePage(PageTypes.SocialHomePage); await StoryRepository.softDeleteStory(storyId); notification.success({ content: formatMessage({ id: 'storyViewer.notification.deleted' }), @@ -195,17 +201,17 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = () => { const nextStory = () => { if ( - page.type === PageTypes.ViewStory && - page.targetIds && - page.targetId && + page.type === PageTypes.ViewStoryPage && + page.context.targetIds && + page.context.targetId && currentIndex === formattedStories?.length - 1 ) { - const currentTargetIndex = page.targetIds.indexOf(page.targetId); + const currentTargetIndex = page.context.targetIds.indexOf(page.context.targetId); const nextTargetIndex = currentTargetIndex + 1; - if (nextTargetIndex < page.targetIds.length) { - const nextTargetId = page.targetIds[nextTargetIndex]; - onClickStory(nextTargetId, 'globalFeed', page.targetIds); + if (nextTargetIndex < page.context.targetIds.length) { + const nextTargetId = page.context.targetIds[nextTargetIndex]; + onClickStory(nextTargetId, 'globalFeed', page.context.targetIds); } else { onChangePage(PageTypes.NewsFeed); } @@ -217,17 +223,17 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = () => { const previousStory = () => { if ( - page.type === PageTypes.ViewStory && - page.targetIds && - page.targetId && + page.type === PageTypes.ViewStoryPage && + page.context.targetIds && + page.context.targetId && currentIndex === 0 ) { - const currentTargetIndex = page.targetIds.indexOf(page.targetId); + const currentTargetIndex = page.context.targetIds.indexOf(page.context.targetId); const previousTargetIndex = currentTargetIndex - 1; if (previousTargetIndex >= 0) { - const previousTargetId = page.targetIds[previousTargetIndex]; - onClickStory(previousTargetId, 'globalFeed', page.targetIds); + const previousTargetId = page.context.targetIds[previousTargetIndex]; + onClickStory(previousTargetId, 'globalFeed', page.context.targetIds); } else { onChangePage(PageTypes.NewsFeed); } @@ -283,7 +289,7 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = () => { } }, [stories, file, currentIndex]); - if (file && page.type === PageTypes.ViewStory && page.storyType === 'globalFeed') { + if (file && page.type === PageTypes.ViewStoryPage && page.context.storyType === 'globalFeed') { return ( <AmityDraftStoryPage mediaType={ @@ -291,7 +297,7 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = () => { ? { type: 'image', url: URL.createObjectURL(file) } : { type: 'video', url: URL.createObjectURL(file) } } - targetId={page.targetId} + targetId={page.context.targetId} targetType="community" /> ); diff --git a/src/v4/social/pages/StoryPage/ViewStoryPage.tsx b/src/v4/social/pages/StoryPage/ViewStoryPage.tsx index 1551ac395..1b490affd 100644 --- a/src/v4/social/pages/StoryPage/ViewStoryPage.tsx +++ b/src/v4/social/pages/StoryPage/ViewStoryPage.tsx @@ -1,24 +1,23 @@ import React from 'react'; import { CommunityFeedStory } from './CommunityFeedStory'; -import { useNavigation } from '~/social/providers/NavigationProvider'; -import { PageTypes } from '~/social/constants'; import { GlobalFeedStory } from './GlobalFeedStory'; +import { PageTypes, useNavigation } from '~/v4/core/providers/NavigationProvider'; -type AmityViewStoryPageType = 'communityFeed' | 'globalFeed'; +type ViewStoryPageType = 'communityFeed' | 'globalFeed'; interface AmityViewStoryPageProps { - type: AmityViewStoryPageType; + type: ViewStoryPageType; } -const AmityViewStoryPage: React.FC<AmityViewStoryPageProps> = ({ type }) => { +const ViewStoryPage: React.FC<AmityViewStoryPageProps> = ({ type }) => { const { page } = useNavigation(); - if (page.type !== PageTypes.ViewStory || !page.targetId) return null; + if (page.type !== PageTypes.ViewStoryPage || !page.context.targetId) return null; - if (type === 'communityFeed') return <CommunityFeedStory communityId={page.targetId} />; - if (type === 'globalFeed') return <GlobalFeedStory targetId={page.targetId} />; + if (type === 'communityFeed') return <CommunityFeedStory communityId={page.context.targetId} />; + if (type === 'globalFeed') return <GlobalFeedStory targetId={page.context.targetId} />; return null; }; -export default AmityViewStoryPage; +export default ViewStoryPage; diff --git a/src/v4/social/pages/StoryPage/index.tsx b/src/v4/social/pages/StoryPage/index.tsx index a076468d9..52443d10f 100644 --- a/src/v4/social/pages/StoryPage/index.tsx +++ b/src/v4/social/pages/StoryPage/index.tsx @@ -1 +1 @@ -export { default as AmityViewStoryPage } from './ViewStoryPage'; +export { default as ViewStoryPage } from './ViewStoryPage'; diff --git a/src/v4/social/pages/index.ts b/src/v4/social/pages/index.ts index 4bc69f1a8..55c7a0ff8 100644 --- a/src/v4/social/pages/index.ts +++ b/src/v4/social/pages/index.ts @@ -1,2 +1,2 @@ export { AmityDraftStoryPage } from './DraftsPage'; -export { AmityViewStoryPage } from './StoryPage'; +export { ViewStoryPage } from './StoryPage'; From 11178ed21f8380a26955f1094434834eea2682ef Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Thu, 13 Jun 2024 16:02:17 +0700 Subject: [PATCH 123/300] fix: ASC-20694 - react story condition for non member (#370) * fix: comment condition * fix: story react condition for non member * fix: import * fix: params --- src/i18n/en.json | 1 + .../StoryViewer/Renderers/Image.tsx | 8 ++++++-- .../StoryViewer/Renderers/Video.tsx | 8 ++++++-- .../StoryViewer/Renderers/Wrappers/Footer/index.tsx | 12 +++++++++++- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/i18n/en.json b/src/i18n/en.json index c769931d2..0781af9e1 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -387,6 +387,7 @@ "storyViewer.toast.like.disabled": "Join community to interact with all stories", "storyViewer.toast.comment.reported": "Comment reported", "storyViewer.toast.comment.unreported": "Comment unreported", + "storyViewer.toast.disable.react": "Join community to interact with all stories", "storyViewer.commentComposeBar.submit": "Post", diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index 62fad1c09..185dcb42e 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -26,6 +26,7 @@ import { BottomSheet } from '~/v4/core/components/BottomSheet'; import { Typography } from '~/v4/core/components'; import { Button } from '~/v4/core/components/Button'; import { isAdmin, isModerator } from '~/helpers/permissions'; +import useCommunityMembersCollection from '~/v4/social/hooks/collections/useCommunityMembersCollection'; export const renderer: CustomRenderer = ({ story, action, config }) => { const { formatMessage } = useIntl(); @@ -54,7 +55,9 @@ export const renderer: CustomRenderer = ({ story, action, config }) => { fileInputRef, } = story; - const isJoined = community?.isJoined || false; + const { members } = useCommunityMembersCollection(community?.communityId as string); + const member = members?.find((member) => member.userId === client?.userId); + const isMember = member != null; const avatarUrl = useImage({ fileId: community?.avatarFileId || '', @@ -249,7 +252,7 @@ export const renderer: CustomRenderer = ({ story, action, config }) => { referenceType="story" community={community as Amity.Community} shouldAllowCreation={community?.allowCommentInStory} - shouldAllowInteraction={isJoined} + shouldAllowInteraction={isMember} /> </BottomSheet> @@ -284,6 +287,7 @@ export const renderer: CustomRenderer = ({ story, action, config }) => { isLiked={isLiked} onClickComment={openCommentSheet} showImpression={isCreator || haveStoryPermission} + isMember={isMember} /> </motion.div> ); diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx index f536ea8c0..8c815b041 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx @@ -25,6 +25,7 @@ import { motion, PanInfo, useAnimationControls } from 'framer-motion'; import rendererStyles from './Renderers.module.css'; import useUser from '~/core/hooks/useUser'; import { isAdmin, isModerator } from '~/helpers/permissions'; +import useCommunityMembersCollection from '~/v4/social/hooks/collections/useCommunityMembersCollection'; export const renderer: CustomRenderer = ({ story, action, config, messageHandler }) => { const { formatMessage } = useIntl(); @@ -55,7 +56,9 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler fileInputRef, } = story; - const isJoined = community?.isJoined || false; + const { members } = useCommunityMembersCollection(community?.communityId as string); + const member = members?.find((member) => member.userId === client?.userId); + const isMember = member != null; const avatarUrl = useImage({ fileId: community?.avatarFileId || '', @@ -288,7 +291,7 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler referenceType={'story'} community={community as Amity.Community} shouldAllowCreation={community?.allowCommentInStory} - shouldAllowInteraction={isJoined} + shouldAllowInteraction={isMember} /> </BottomSheet> {story.items?.[0]?.data?.url && ( @@ -321,6 +324,7 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler isLiked={isLiked} onClickComment={openCommentSheet} showImpression={isCreator || haveStoryPermission} + isMember={isMember} /> </motion.div> ); diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx index 5820bde03..dd598c3f3 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import styles from './Footer.module.css'; import { DotsIcon, ErrorIcon } from '~/icons'; @@ -8,6 +8,7 @@ import { ReactionRepository } from '@amityco/ts-sdk'; import { LIKE_REACTION_KEY } from '~/constants'; import Spinner from '~/social/components/Spinner'; import { StoryCommentButton, ImpressionButton, ReactButton } from '~/v4/social/elements'; +import { useNotifications } from '~/v4/core/providers/NotificationProvider'; const Footer: React.FC< React.PropsWithChildren<{ @@ -19,6 +20,7 @@ const Footer: React.FC< isLiked: boolean; onClickComment: () => void; syncState?: Amity.SyncState; + isMember?: boolean; }> > = ({ syncState, @@ -29,11 +31,19 @@ const Footer: React.FC< storyId, onClickComment, showImpression, + isMember, }) => { + const notification = useNotifications(); const { formatMessage } = useIntl(); const handleLike = async () => { try { + if (!isMember) { + notification.show({ + content: formatMessage({ id: 'storyViewer.toast.disable.react' }), + }); + return; + } if (!isLiked) { await ReactionRepository.addReaction('story', storyId, LIKE_REACTION_KEY); } else { From f985532fbc213d2d5c5d14caa903445c3dc320b6 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Fri, 14 Jun 2024 14:21:37 +0700 Subject: [PATCH 124/300] fix: ASC-22312 - moderator badge (#384) * fix: moderator badge * fix: moderator badge * fix: moderator badge * fix: use css var * fix: remove unused * fix: remove package manager * fix: comment tray stories * fix: remove unused * fix: remove console.log * fix: sub story community rte --- package.json | 2 +- pnpm-lock.yaml | 10 +- .../BottomSheet/BottomSheet.module.css | 12 +- .../components/BottomSheet/BottomSheet.tsx | 2 +- .../components/CommentTray/ui.stories.tsx | 128 +++++------------- .../hooks/useCommunityStoriesSubscription.tsx | 29 ++++ .../Badege/Badge.module.css | 10 -- .../internal-components/Badege/Badge.tsx | 12 -- .../internal-components/Badege/index.ts | 1 - .../internal-components/Badege/types.ts | 4 - .../internal-components/Badege/ui.stories.tsx | 29 ---- .../Comment/UIComment.module.css | 41 +++--- .../internal-components/Comment/UIComment.tsx | 9 ++ .../internal-components/Comment/index.tsx | 10 +- .../StoryViewer/Renderers/Image.tsx | 8 ++ .../StoryViewer/Renderers/Video.tsx | 7 + 16 files changed, 132 insertions(+), 182 deletions(-) create mode 100644 src/v4/social/hooks/useCommunityStoriesSubscription.tsx delete mode 100644 src/v4/social/internal-components/Badege/Badge.module.css delete mode 100644 src/v4/social/internal-components/Badege/Badge.tsx delete mode 100644 src/v4/social/internal-components/Badege/index.ts delete mode 100644 src/v4/social/internal-components/Badege/types.ts delete mode 100644 src/v4/social/internal-components/Badege/ui.stories.tsx diff --git a/package.json b/package.json index 6c8c79ee6..5a87716b6 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "react-dom": ">=17.0.2" }, "devDependencies": { - "@amityco/ts-sdk": "^6.25.1", + "@amityco/ts-sdk": "^6.26.3", "@storybook/addon-a11y": "^7.6.7", "@storybook/addon-actions": "^7.6.7", "@storybook/addon-backgrounds": "^7.6.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 422496fbc..35ecc2545 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -118,8 +118,8 @@ importers: version: 3.23.8 devDependencies: '@amityco/ts-sdk': - specifier: ^6.25.1 - version: 6.26.2 + specifier: ^6.26.3 + version: 6.26.3 '@storybook/addon-a11y': specifier: ^7.6.7 version: 7.6.19 @@ -288,8 +288,8 @@ packages: '@adobe/css-tools@4.3.3': resolution: {integrity: sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==} - '@amityco/ts-sdk@6.26.2': - resolution: {integrity: sha512-FUwpkQEwhDCsvZ16qFem9iqVUzuv2gdXs0TPTvOItDfaxEZk46nOW7Pv4n5UvJ3UhSdtgW4hibrKRbNKT29Kxg==} + '@amityco/ts-sdk@6.26.3': + resolution: {integrity: sha512-Fw9g0hLVs8GYUBHVdSQpV/n7JZRRFkFolHTKh42w4aVy7dwYRj0NhiRtQpSNfIxCoQWHG6fwL7ttC3L1ZWheOQ==} engines: {node: '>=12', npm: '>=6'} '@ampproject/remapping@2.3.0': @@ -6987,7 +6987,7 @@ snapshots: '@adobe/css-tools@4.3.3': {} - '@amityco/ts-sdk@6.26.2': + '@amityco/ts-sdk@6.26.3': dependencies: agentkeepalive: 4.5.0 axios: 1.7.2(debug@4.3.5) diff --git a/src/v4/core/components/BottomSheet/BottomSheet.module.css b/src/v4/core/components/BottomSheet/BottomSheet.module.css index 5925f02c7..18f070685 100644 --- a/src/v4/core/components/BottomSheet/BottomSheet.module.css +++ b/src/v4/core/components/BottomSheet/BottomSheet.module.css @@ -8,15 +8,19 @@ which have higher specificity. background-color: var(--asc-color-base-background) !important; } -.bottomSheet__default__header { +.bottomSheet__header { background-color: var(--asc-color-base-background); - border-top-left-radius: 1rem; - border-top-right-radius: 1rem; + border-top-left-radius: var(--asc-border-radius-xl); + border-top-right-radius: var(--asc-border-radius-xl); + text-align: center; + color: var(--asc-color-base-default); + border-bottom: 1px solid var(--asc-color-base-shade4); + padding-bottom: var(--asc-spacing-m1); } .bottomSheet__content { background-color: var(--asc-color-base-background); - padding: 1rem; + padding: var(--asc-spacing-m1); } .bottomSheet__backdrop { diff --git a/src/v4/core/components/BottomSheet/BottomSheet.tsx b/src/v4/core/components/BottomSheet/BottomSheet.tsx index d8cf0dcf4..42e1b1316 100644 --- a/src/v4/core/components/BottomSheet/BottomSheet.tsx +++ b/src/v4/core/components/BottomSheet/BottomSheet.tsx @@ -21,7 +21,7 @@ export const BottomSheet = ({ children, headerTitle, ...props }: BottomSheetProp return ( <Sheet {...props}> <Sheet.Container className={styles.bottomSheet__container}> - <Sheet.Header className={styles.bottomSheet__header}> + <Sheet.Header> <Sheet.Header /> {headerTitle && ( <Sheet.Header className={styles.bottomSheet__header}> diff --git a/src/v4/social/components/CommentTray/ui.stories.tsx b/src/v4/social/components/CommentTray/ui.stories.tsx index fd6c64553..e5edcc72b 100644 --- a/src/v4/social/components/CommentTray/ui.stories.tsx +++ b/src/v4/social/components/CommentTray/ui.stories.tsx @@ -1,100 +1,46 @@ +import { StoryObj } from '@storybook/react'; import React from 'react'; -import { ComponentStory, ComponentMeta } from '@storybook/react'; -import { ThemeProvider } from 'styled-components'; -import { CustomizationProvider } from '~/v4/core/providers/CustomizationProvider'; import { CommentTray } from './CommentTray'; -import buildGlobalTheme from '~/core/providers/UiKitProvider/theme'; -import { theme } from '../../theme'; export default { - title: 'Components/CommentTray', + title: 'v4-social/components/CommentTrayComponent', component: CommentTray, - decorators: [ - (Story) => ( - <ThemeProvider theme={buildGlobalTheme(theme)}> - <CustomizationProvider - initialConfig={{ - preferred_theme: 'light', - theme: { - light: { - primary_color: '#FF0000', - secondary_color: '#00FF00', - base_color: '#0000FF', - base_shade1_color: '#000000', - base_shade2_color: '#000000', - base_shade3_color: '#000000', - base_shade4_color: '#000000', - alert_color: '#000000', - background_color: '#FFFFFF', - base_inverse_color: '#FFFFFF', - }, - dark: { - primary_color: '#FF0000', - secondary_color: '#00FF00', - base_color: '#0000FF', - base_shade1_color: '#000000', - base_shade2_color: '#000000', - base_shade3_color: '#000000', - base_shade4_color: '#000000', - alert_color: '#000000', - background_color: '#000000', - base_inverse_color: '#FFFFFF', - }, - }, - excludes: [], - customizations: { - '*/comment_tray_component/*': { - theme: { - light: { - primary_color: '#FF0000', - secondary_color: '#00FF00', - }, - }, - }, - }, - }} - > - <div - style={{ - width: '50%', - }} - > - <Story /> - </div> - </CustomizationProvider> - </ThemeProvider> - ), - ], -} as ComponentMeta<typeof CommentTray>; - -const Template: ComponentStory<typeof CommentTray> = (args) => <CommentTray {...args} />; - -export const Default = Template.bind({}); -Default.args = { - pageId: '*', - storyId: 'story123', - commentId: 'comment123', - referenceType: 'story', - referenceId: 'story123', - replyTo: 'user123', - isReplying: false, - limit: 5, - isOpen: true, - isJoined: true, - allowCommentInStory: true, - onClose: () => {}, - onClickReply: () => {}, - onCancelReply: () => {}, + argTypes: { + referenceType: { + control: 'select', + options: ['content', 'post', 'story'], + }, + referenceId: { + control: 'text', + }, + shouldAllowInteraction: { + control: 'boolean', + }, + shouldAllowCreation: { + control: 'boolean', + }, + }, }; -export const Replying = Template.bind({}); -Replying.args = { - ...Default.args, - isReplying: true, -}; +type Story = StoryObj<typeof CommentTray>; -export const NotJoined = Template.bind({}); -NotJoined.args = { - ...Default.args, - isJoined: false, +export const CommentTrayComponent: Story = { + render: (args) => { + return ( + <CommentTray + community={args.community} + referenceType={args.referenceType} + referenceId={args.referenceId} + shouldAllowInteraction={args.shouldAllowInteraction} + shouldAllowCreation={args.shouldAllowCreation} + /> + ); + }, + args: { + referenceType: 'story', + referenceId: '', + shouldAllowInteraction: true, + shouldAllowCreation: true, + }, + name: 'CommentTrayComponent', }; diff --git a/src/v4/social/hooks/useCommunityStoriesSubscription.tsx b/src/v4/social/hooks/useCommunityStoriesSubscription.tsx new file mode 100644 index 000000000..b5e26e928 --- /dev/null +++ b/src/v4/social/hooks/useCommunityStoriesSubscription.tsx @@ -0,0 +1,29 @@ +import { StoryRepository, getCommunityStoriesTopic } from '@amityco/ts-sdk'; +import useSubscription from '~/v4/core/hooks/subscriptions/useSubscription'; + +export default function useCommunityStoriesSubscription({ + targetId, + targetType, + shouldSubscribe = () => true, + callback, +}: { + targetId: string; + targetType: Amity.StoryTargetType; + shouldSubscribe?: () => boolean; + callback?: Amity.Listener; +}) { + return useSubscription({ + fetcher: StoryRepository.getActiveStoriesByTarget, + params: { + targetId, + targetType, + }, + callback, + shouldSubscribe: () => !!targetId && shouldSubscribe(), + getSubscribedTopic: () => + getCommunityStoriesTopic({ + targetId, + targetType, + }), + }); +} diff --git a/src/v4/social/internal-components/Badege/Badge.module.css b/src/v4/social/internal-components/Badege/Badge.module.css deleted file mode 100644 index dcf1d32b6..000000000 --- a/src/v4/social/internal-components/Badege/Badge.module.css +++ /dev/null @@ -1,10 +0,0 @@ -.badge { - display: inline-flex; - justify-content: center; - align-items: center; - gap: 3px; - background-color: var(--asc-color-primary-shade3); - color: var(--asc-color-primary-main); - border-radius: 20px; - padding: 0 6px 0 4px; -} diff --git a/src/v4/social/internal-components/Badege/Badge.tsx b/src/v4/social/internal-components/Badege/Badge.tsx deleted file mode 100644 index d70e5cf73..000000000 --- a/src/v4/social/internal-components/Badege/Badge.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import styles from './Badge.module.css'; -import { BadgeProps } from './types'; - -export const Badge = ({ icon, communityRole }: BadgeProps) => { - return ( - <div className={styles.badge}> - {icon} - {communityRole} - </div> - ); -}; diff --git a/src/v4/social/internal-components/Badege/index.ts b/src/v4/social/internal-components/Badege/index.ts deleted file mode 100644 index 26a9e305c..000000000 --- a/src/v4/social/internal-components/Badege/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { Badge } from './Badge'; diff --git a/src/v4/social/internal-components/Badege/types.ts b/src/v4/social/internal-components/Badege/types.ts deleted file mode 100644 index 16228ce56..000000000 --- a/src/v4/social/internal-components/Badege/types.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface BadgeProps { - icon: React.ReactNode; - communityRole: string; -} diff --git a/src/v4/social/internal-components/Badege/ui.stories.tsx b/src/v4/social/internal-components/Badege/ui.stories.tsx deleted file mode 100644 index 4c347bc5c..000000000 --- a/src/v4/social/internal-components/Badege/ui.stories.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; -import { Meta, Story } from '@storybook/react'; -import { Badge } from './Badge'; -import { BadgeProps } from './types'; -import { ModeratorBadgeIcon } from '~/icons'; - -export default { - title: 'V4/Components/Badge', - component: Badge, - argTypes: { - icon: { - control: { - type: 'select', - options: ['crown', 'star', 'heart'], // Adjust the options based on your available icons - }, - }, - communityRole: { - control: 'text', - }, - }, -} as Meta; - -const Template: Story<BadgeProps> = (args) => <Badge {...args} />; - -export const Default = Template.bind({}); -Default.args = { - icon: <ModeratorBadgeIcon />, - communityRole: 'Moderator', -}; diff --git a/src/v4/social/internal-components/Comment/UIComment.module.css b/src/v4/social/internal-components/Comment/UIComment.module.css index 239181043..8245f5366 100644 --- a/src/v4/social/internal-components/Comment/UIComment.module.css +++ b/src/v4/social/internal-components/Comment/UIComment.module.css @@ -1,10 +1,10 @@ .avatar { - margin-right: 0.5rem; + margin-right: var(--asc-spacing-s1); } .container { display: flex; - gap: 1rem; + gap: var(--asc-spacing-m1); width: 100%; } @@ -14,7 +14,7 @@ } .editedMark { - margin-left: 0.3125rem; + margin-left: var(--asc-spacing-xxs2); color: var(--asc-color-neutral-shade1); font-size: var(--asc-text-font-size-sm); font-weight: var(--asc-text-font-weight-normal); @@ -33,20 +33,20 @@ display: flex; justify-content: flex-start; align-items: center; - gap: 0.75rem; + gap: var(--asc-spacing-s2); } .reactionsWrapper { display: flex; align-items: center; - gap: 0.5rem; + gap: var(--asc-spacing-s1); } .interactionBar { display: flex; width: 100%; justify-content: space-between; - padding: 0.25rem 1rem 0.75rem 0; + padding: var(--asc-spacing-xxs2) var(--asc-spacing-m1) var(--asc-spacing-s2) 0; align-items: center; } @@ -58,27 +58,27 @@ .buttonContainer { display: flex; justify-content: flex-end; - margin-top: 0.5rem; + margin-top: var(--asc-spacing-s1); } .buttonContainer > * { - margin-left: 0.5rem; + margin-left: var(--asc-spacing-s1); } .commentEditTextarea { font-size: var(--asc-text-font-size-md); font-weight: var(--asc-text-font-weight-normal); line-height: var(--asc-line-height-lg); - border-radius: 0 0.75rem 0.75rem; + border-radius: 0 var(--asc-border-radius-lg) var(--asc-border-radius-lg); height: 7.5rem; - padding: 0.75rem; + padding: var(--asc-spacing-s2); } .reactionListButtonContainer { display: flex; align-items: center; justify-content: center; - gap: 0.5rem; + gap: var(--asc-spacing-s1); color: var(--asc-color-base-shade2); } @@ -95,7 +95,7 @@ width: 1.25rem; height: 1.25rem; border-radius: 50%; - margin-left: -0.25rem; + margin-left: calc(-1 * var(--asc-spacing-xxs2)); } .reactionIcon:first-child { @@ -103,23 +103,23 @@ } .commentInteractionButton { - padding: 0.25rem; + padding: var(--asc-spacing-xxs2); display: flex; align-items: center; - gap: 0.5rem; + gap: var(--asc-spacing-s1); color: var(--asc-color-base-shade2); cursor: pointer; border: none; } .likeButton { - padding: 0.25rem; + padding: var(--asc-spacing-xxs2); color: var(--asc-color-base-shade2); border: none; } .liked { - padding: 0.25rem; + padding: var(--asc-spacing-xxs2); color: var(--asc-color-primary-default); border: none; } @@ -133,7 +133,7 @@ .content { display: inline-flex; flex-direction: column; - gap: 0.25rem; + gap: var(--asc-spacing-xxs2); width: 100%; } @@ -141,7 +141,7 @@ display: inline-flex; justify-content: flex-start; align-items: center; - gap: 0.125rem; + gap: var(--asc-spacing-xxs2); color: var(--asc-color-base-default); } @@ -152,3 +152,8 @@ text-align: center; color: var(--asc-color-base-shade2); } + +.moderatorBadge__container { + display: flex; + margin-bottom: var(--asc-spacing-xxs2); +} diff --git a/src/v4/social/internal-components/Comment/UIComment.tsx b/src/v4/social/internal-components/Comment/UIComment.tsx index 927ff22d1..09a421751 100644 --- a/src/v4/social/internal-components/Comment/UIComment.tsx +++ b/src/v4/social/internal-components/Comment/UIComment.tsx @@ -18,6 +18,7 @@ import InputText from '~/v4/core/components/InputText'; import { Avatar, Typography } from '~/v4/core/components'; import Button from '~/v4/core/components/Button/Button'; import clsx from 'clsx'; +import { ModeratorBadge } from '../../elements/ModeratorBadge/ModeratorBadge'; interface StyledCommentProps { commentId?: string; @@ -74,6 +75,7 @@ interface StyledCommentProps { referenceType?: Amity.Comment['referenceType']; referenceId?: Amity.Comment['referenceId']; onClickReactionList: () => void; + isModerator?: boolean; } const UIComment = ({ @@ -104,6 +106,7 @@ const UIComment = ({ referenceType, commentId, onClickReactionList, + isModerator, }: StyledCommentProps) => { return ( <div className={styles.container}> @@ -113,6 +116,12 @@ const UIComment = ({ <Typography.CaptionBold>{authorName}</Typography.CaptionBold> </div> + {isModerator && ( + <div className={styles.moderatorBadge__container}> + <ModeratorBadge /> + </div> + )} + {isEditing ? ( <div className={styles.commentEditContainer}> <InputText diff --git a/src/v4/social/internal-components/Comment/index.tsx b/src/v4/social/internal-components/Comment/index.tsx index 00fc0a068..7665d2cda 100644 --- a/src/v4/social/internal-components/Comment/index.tsx +++ b/src/v4/social/internal-components/Comment/index.tsx @@ -37,6 +37,7 @@ import { TrashIcon, PenIcon, FlagIcon, MinusCircleIcon } from '~/v4/social/icons import { LoadingIndicator } from '~/v4/social/internal-components/LoadingIndicator'; import useCommunityMembersCollection from '~/v4/social/hooks/collections/useCommunityMembersCollection'; import { useCommentFlaggedByMe } from '~/v4/social/hooks'; +import { isModerator } from '~/helpers/permissions'; const REPLIES_PER_PAGE = 5; @@ -82,10 +83,6 @@ export const Comment = ({ commentId, readonly, onClickReply }: CommentProps) => const toggleBottomSheet = () => setBottomSheet((prev) => !prev); - useCommentSubscription({ - commentId, - }); - const { text, markup, mentions, onChange, queryMentionees, resetState, clearAll } = useMention({ targetId: story?.targetId, targetType: story?.targetType, @@ -187,8 +184,8 @@ export const Comment = ({ commentId, readonly, onClickReply }: CommentProps) => }); }; - const { currentUserId } = useSDK(); - const currentMember = members.find((member) => member.userId === currentUserId); + const currentMember = members.find((member) => member.userId === comment?.userId); + const isCommunityModerator = isModerator(currentMember?.roles); const isMember = isCommunityMember(currentMember); const options = [ @@ -279,6 +276,7 @@ export const Comment = ({ commentId, readonly, onClickReply }: CommentProps) => onClickReactionList={() => { setSelectedCommentId(comment.commentId); }} + isModerator={isCommunityModerator} /> ); diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index 185dcb42e..d12a8858e 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -27,6 +27,7 @@ import { Typography } from '~/v4/core/components'; import { Button } from '~/v4/core/components/Button'; import { isAdmin, isModerator } from '~/helpers/permissions'; import useCommunityMembersCollection from '~/v4/social/hooks/collections/useCommunityMembersCollection'; +import useCommunityStoriesSubscription from '~/v4/social/hooks/useCommunityStoriesSubscription'; export const renderer: CustomRenderer = ({ story, action, config }) => { const { formatMessage } = useIntl(); @@ -41,6 +42,7 @@ export const renderer: CustomRenderer = ({ story, action, config }) => { const isLiked = !!(story && story.myReactions && story.myReactions.includes(LIKE_REACTION_KEY)); const totalLikes = story.reactions[LIKE_REACTION_KEY] || 0; + const { storyId, syncState, @@ -167,6 +169,12 @@ export const renderer: CustomRenderer = ({ story, action, config }) => { }; }, []); + useCommunityStoriesSubscription({ + targetId: community?.communityId as string, + targetType: 'community', + shouldSubscribe: () => !!community?.communityId, + }); + return ( <motion.div className={styles.rendererContainer} diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx index 8c815b041..b88aeb500 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx @@ -26,6 +26,7 @@ import rendererStyles from './Renderers.module.css'; import useUser from '~/core/hooks/useUser'; import { isAdmin, isModerator } from '~/helpers/permissions'; import useCommunityMembersCollection from '~/v4/social/hooks/collections/useCommunityMembersCollection'; +import useCommunityStoriesSubscription from '~/v4/social/hooks/useCommunityStoriesSubscription'; export const renderer: CustomRenderer = ({ story, action, config, messageHandler }) => { const { formatMessage } = useIntl(); @@ -196,6 +197,12 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler }; }, []); + useCommunityStoriesSubscription({ + targetId: community?.communityId as string, + targetType: 'community', + shouldSubscribe: () => !!community?.communityId, + }); + return ( <motion.div className={rendererStyles.rendererContainer} From f20534bb3132a2e921d34f117d481e9091958906 Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Fri, 14 Jun 2024 14:32:44 +0700 Subject: [PATCH 125/300] feat: ASC-22893 - create post menu & select post target page (#405) * style: change create menu color * feat: implement ui CreatePostMenu * feat: add event when click createPostMenuButton * feat: implement CreateStoryButtons * feat: implement CreatePollButton * feat: implement CreateLivestreamButton * fix: text Livestream * fix: changeable text * feat: implement SelectPostTargetPage * feat: onBack * fix: pageId * fix: text static * style: pointer * feat: integrate API communites to SelectPostTargetPage * feat: custom communities avatar size * feat: add UserAvatar * feat: apply infinity scroll * fix: map key * feat: add userId * style: change px to rem * fix: dark theme styles * reafactor: remove blank line * style: add pointer * fix: pr comments * refactor: wrap button in IconComponent * style: adapt global var * fix: optional pageId * refactor: remove comment --- src/v4/core/IconComponent.tsx | 18 +++- .../collections/useCommunitiesCollection.ts | 2 - .../core/providers/CustomizationProvider.tsx | 82 ++++++++++++++++++ src/v4/core/providers/NavigationProvider.tsx | 15 +++- .../core/providers/PageBehaviorProvider.tsx | 12 +++ .../CreatePostMenu/CreatePostMenu.module.css | 10 +++ .../CreatePostMenu/CreatePostMenu.tsx | 22 +++++ .../components/CreatePostMenu/index.tsx | 1 + .../TopNavigation/TopNavigation.tsx | 24 ++++-- .../social/elements/BackButton/BackButton.tsx | 16 ++-- .../CloseButton/CloseButton.module.css | 28 ++++++ .../elements/CloseButton/CloseButton.tsx | 63 +++++++++----- src/v4/social/elements/CloseButton/styles.tsx | 30 ------- .../CommunityAvatar/CommunityAvatar.tsx | 2 +- .../CommunityDisplayName.tsx | 2 - .../CreateLivestreamButton.module.css | 15 ++++ .../CreateLivestreamButton.tsx | 67 +++++++++++++++ .../elements/CreateLivestreamButton/index.tsx | 1 + .../CreatePollButton.module.css | 15 ++++ .../CreatePollButton/CreatePollButton.tsx | 63 ++++++++++++++ .../elements/CreatePollButton/index.tsx | 1 + .../CreatePostButton.module.css | 16 ++++ .../CreatePostButton/CreatePostButton.tsx | 59 +++++++++++++ .../elements/CreatePostButton/index.tsx | 1 + .../CreateStoryButton.module.css | 15 ++++ .../CreateStoryButtons/CreateStoryButton.tsx | 63 ++++++++++++++ .../elements/CreateStoryButtons/index.tsx | 1 + .../MyTimelineAvatar.module.css | 10 +++ .../MyTimelineAvatar/MyTimelineAvatar.tsx | 26 ++++++ .../elements/MyTimelineAvatar/index.tsx | 1 + .../MyTimelineText/MyTimelineText.module.css | 11 +++ .../MyTimelineText/MyTimelineText.tsx | 29 +++++++ .../social/elements/MyTimelineText/index.tsx | 1 + .../PostCreationButton.module.css | 2 + .../PostCreationButton/PostCreationButton.tsx | 3 + src/v4/social/elements/Title/Title.tsx | 23 ++--- .../collections/useCommunitiesCollection.ts | 18 ++++ src/v4/social/hooks/useLiveCollection.ts | 80 +++++++++++++++++ src/v4/social/pages/Application/index.tsx | 2 + .../SelectPostTargetPage.module.css | 48 +++++++++++ .../SelectPostTargetPage.stories.tsx | 12 +++ .../SelectPostTargetPage.tsx | 86 +++++++++++++++++++ .../pages/SelectPostTargetPage/index.tsx | 1 + .../pages/SocialHomePage/SocialHomePage.tsx | 47 ++++++++-- 44 files changed, 950 insertions(+), 94 deletions(-) create mode 100644 src/v4/social/components/CreatePostMenu/CreatePostMenu.module.css create mode 100644 src/v4/social/components/CreatePostMenu/CreatePostMenu.tsx create mode 100644 src/v4/social/components/CreatePostMenu/index.tsx create mode 100644 src/v4/social/elements/CloseButton/CloseButton.module.css delete mode 100644 src/v4/social/elements/CloseButton/styles.tsx create mode 100644 src/v4/social/elements/CreateLivestreamButton/CreateLivestreamButton.module.css create mode 100644 src/v4/social/elements/CreateLivestreamButton/CreateLivestreamButton.tsx create mode 100644 src/v4/social/elements/CreateLivestreamButton/index.tsx create mode 100644 src/v4/social/elements/CreatePollButton/CreatePollButton.module.css create mode 100644 src/v4/social/elements/CreatePollButton/CreatePollButton.tsx create mode 100644 src/v4/social/elements/CreatePollButton/index.tsx create mode 100644 src/v4/social/elements/CreatePostButton/CreatePostButton.module.css create mode 100644 src/v4/social/elements/CreatePostButton/CreatePostButton.tsx create mode 100644 src/v4/social/elements/CreatePostButton/index.tsx create mode 100644 src/v4/social/elements/CreateStoryButtons/CreateStoryButton.module.css create mode 100644 src/v4/social/elements/CreateStoryButtons/CreateStoryButton.tsx create mode 100644 src/v4/social/elements/CreateStoryButtons/index.tsx create mode 100644 src/v4/social/elements/MyTimelineAvatar/MyTimelineAvatar.module.css create mode 100644 src/v4/social/elements/MyTimelineAvatar/MyTimelineAvatar.tsx create mode 100644 src/v4/social/elements/MyTimelineAvatar/index.tsx create mode 100644 src/v4/social/elements/MyTimelineText/MyTimelineText.module.css create mode 100644 src/v4/social/elements/MyTimelineText/MyTimelineText.tsx create mode 100644 src/v4/social/elements/MyTimelineText/index.tsx create mode 100644 src/v4/social/hooks/collections/useCommunitiesCollection.ts create mode 100644 src/v4/social/hooks/useLiveCollection.ts create mode 100644 src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.module.css create mode 100644 src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.stories.tsx create mode 100644 src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.tsx create mode 100644 src/v4/social/pages/SelectPostTargetPage/index.tsx diff --git a/src/v4/core/IconComponent.tsx b/src/v4/core/IconComponent.tsx index cc32144e3..9042345e6 100644 --- a/src/v4/core/IconComponent.tsx +++ b/src/v4/core/IconComponent.tsx @@ -1,19 +1,29 @@ +import React from 'react'; + export interface IconComponentProps { defaultIcon: () => JSX.Element; imgIcon: () => JSX.Element; + onClick?: (e: React.MouseEvent) => void; defaultIconName?: string; configIconName?: string; + 'data-qa-anchor'?: string; + style?: React.CSSProperties; + className?: string; } export const IconComponent = ({ defaultIcon, imgIcon, + onClick, + style, defaultIconName, configIconName, + className, }: IconComponentProps) => { - if (defaultIconName === configIconName) { - return defaultIcon(); - } - return imgIcon(); + return ( + <button className={className} data-qa-anchor={'data-qa-anchor'} onClick={onClick} style={style}> + {defaultIconName === configIconName ? defaultIcon() : imgIcon()} + </button> + ); }; diff --git a/src/v4/core/hooks/collections/useCommunitiesCollection.ts b/src/v4/core/hooks/collections/useCommunitiesCollection.ts index a8329461f..771190893 100644 --- a/src/v4/core/hooks/collections/useCommunitiesCollection.ts +++ b/src/v4/core/hooks/collections/useCommunitiesCollection.ts @@ -1,8 +1,6 @@ import { CommunityRepository } from '@amityco/ts-sdk'; import useLiveCollection from '~/core/hooks/useLiveCollection'; -// breaking changes - export default function useCommunitiesCollection( queryParams?: Parameters<typeof CommunityRepository.getCommunities>[0], shouldCall?: () => boolean, diff --git a/src/v4/core/providers/CustomizationProvider.tsx b/src/v4/core/providers/CustomizationProvider.tsx index 29d18c066..e06fed883 100644 --- a/src/v4/core/providers/CustomizationProvider.tsx +++ b/src/v4/core/providers/CustomizationProvider.tsx @@ -97,6 +97,7 @@ interface CustomizationProviderProps { type IconConfiguration = { icon?: string; + image?: string; }; type TextConfiguration = { text?: string; @@ -316,6 +317,87 @@ export const defaultConfig: DefaultConfig = { icon: 'shareButtonIcon', text: 'Share', }, + 'post_composer_page/*/*': {}, + 'post_composer_page/*/close_button': { + image: 'platformValue', + }, + 'post_composer_page/*/community_display_name': {}, + 'post_composer_page/*/create_button': { + text: 'Post', + }, + 'post_composer_page/*/edit_post_button': { + text: 'Save', + }, + 'post_composer_page/*/edit_post_title': { + text: 'Edit post', + }, + 'post_composer_page/media_attachment/*': {}, + 'post_composer_page/media_attachment/camera_button': { + image: 'platformValue', + }, + 'post_composer_page/media_attachment/image_button': { + image: 'platformValue', + }, + 'post_composer_page/media_attachment/video_button': { + image: 'platformValue', + }, + 'post_composer_page/media_attachment/file_button': { + image: 'platformValue', + }, + 'post_composer_page/media_attachment/detailed_button': { + image: 'platformValue', + }, + 'post_composer_page/detailed_media_attachment/*': {}, + 'post_composer_page/detailed_media_attachment/camera_button': { + text: 'Camera', + image: 'platformValue', + }, + 'post_composer_page/detailed_media_attachment/image_button': { + text: 'Photo', + image: 'platformValue', + }, + 'post_composer_page/detailed_media_attachment/video_button': { + text: 'Video', + image: 'platformValue', + }, + 'create_post_page/detailed_media_attachment/file_button': { + textpost_composer_page: 'Attachment', + image: 'platformValue', + }, + 'social_home_page/*/*': {}, + 'social_home_page/create_post_menu/*': {}, + 'social_home_page/create_post_menu/create_post_button': { + text: 'Post', + image: 'Post', + }, + 'social_home_page/create_post_menu/create_story_button': { + text: 'Story', + image: 'Story', + }, + 'social_home_page/create_post_menu/create_poll_button': { + text: 'Poll', + image: 'Poll', + }, + 'social_home_page/create_post_menu/create_livestream_button': { + text: 'Livestream', + image: 'Livestream', + }, + 'select_post_target_page/*/close_button': { + image: 'platformValue', + }, + 'select_post_target_page/*/my_timeline_avatar': {}, + 'select_post_target_page/*/title': { + text: 'Post to', + }, + 'select_post_target_page/*/my_timeline_text': { + text: 'My Timeline', + }, + '*/*/community_official_badge': { + image: 'platformValue', + }, + '*/*/community_private_badge': { + image: 'platformValue', + }, 'social_global_search_page/*/*': {}, 'social_global_search_page/top_search_bar/*': {}, 'social_global_search_page/top_search_bar/search_icon': { diff --git a/src/v4/core/providers/NavigationProvider.tsx b/src/v4/core/providers/NavigationProvider.tsx index 645177a34..cf6a03957 100644 --- a/src/v4/core/providers/NavigationProvider.tsx +++ b/src/v4/core/providers/NavigationProvider.tsx @@ -14,6 +14,7 @@ export enum PageTypes { CommunityProfilePage = 'CommunityProfilePage', UserProfilePage = 'UserProfilePage', SocialGlobalSearchPage = 'SocialGlobalSearchPage', + SelectPostTargetPage = 'SelectPostTargetPage', } type Page = @@ -68,7 +69,8 @@ type Page = | { type: PageTypes.CommunityProfilePage; context: { communityId: string } } | { type: PageTypes.UserProfilePage; context: { userId: string; communityId?: string } } | { type: PageTypes.SocialHomePage; context: { communityId?: string } } - | { type: PageTypes.SocialGlobalSearchPage; context: { tab?: string } }; + | { type: PageTypes.SocialGlobalSearchPage; context: { tab?: string } } + | { type: PageTypes.SelectPostTargetPage }; type ContextValue = { page: Page; @@ -96,6 +98,7 @@ type ContextValue = { storyType: 'communityFeed' | 'globalFeed', targetIds?: string[], ) => void; + goToSelectPostTargetPage: () => void; setNavigationBlocker?: ( params: | { @@ -132,6 +135,7 @@ let defaultValue: ContextValue = { ) => {}, goToCommunityProfilePage: (communityId: string) => {}, goToSocialGlobalSearchPage: (tab?: string) => {}, + goToSelectPostTargetPage: () => {}, setNavigationBlocker: () => {}, onBack: () => {}, }; @@ -163,6 +167,7 @@ if (process.env.NODE_ENV !== 'production') { console.log(`NavigationContext goToCommunityProfilePage(${communityId})`), goToSocialGlobalSearchPage: (tab) => console.log(`NavigationContext goToSocialGlobalSearchPage(${tab})`), + goToSelectPostTargetPage: () => console.log('NavigationContext goToTargetPage()'), }; } @@ -443,6 +448,13 @@ export default function NavigationProvider({ }, [onChangePage, pushPage], ); + const goToSelectPostTargetPage = useCallback(() => { + const next = { + type: PageTypes.SelectPostTargetPage, + }; + + pushPage(next); + }, [onChangePage, pushPage]); return ( <NavigationContext.Provider @@ -463,6 +475,7 @@ export default function NavigationProvider({ goToSocialGlobalSearchPage, goToCommunityProfilePage, goToViewStoryPage, + goToSelectPostTargetPage, setNavigationBlocker, }} > diff --git a/src/v4/core/providers/PageBehaviorProvider.tsx b/src/v4/core/providers/PageBehaviorProvider.tsx index 20ae52895..34baedf0a 100644 --- a/src/v4/core/providers/PageBehaviorProvider.tsx +++ b/src/v4/core/providers/PageBehaviorProvider.tsx @@ -29,6 +29,9 @@ export interface PageBehavior { AmityCommunitySearchResultComponentBehavior: { goToCommunityProfilePage: (context: { communityId: string }) => void; }; + AmityCreatePostMenuComponentBehavior: { + goToSelectPostTargetPage(): void; + }; } const PageBehaviorContext = React.createContext<PageBehavior | undefined>(undefined); @@ -48,6 +51,7 @@ export const PageBehaviorProvider: React.FC<PageBehaviorProviderProps> = ({ goToCommunityProfilePage, goToUserProfilePage, goToViewStoryPage, + goToSelectPostTargetPage, } = useNavigation(); const navigationBehavior: PageBehavior = { AmityStoryViewPageBehavior: { @@ -123,6 +127,14 @@ export const PageBehaviorProvider: React.FC<PageBehaviorProviderProps> = ({ goToCommunityProfilePage(context.communityId); }, }, + AmityCreatePostMenuComponentBehavior: { + goToSelectPostTargetPage() { + if (pageBehavior?.AmityCreatePostMenuComponentBehavior?.goToSelectPostTargetPage) { + return pageBehavior.AmityCreatePostMenuComponentBehavior.goToSelectPostTargetPage(); + } + goToSelectPostTargetPage(); + }, + }, }; return ( diff --git a/src/v4/social/components/CreatePostMenu/CreatePostMenu.module.css b/src/v4/social/components/CreatePostMenu/CreatePostMenu.module.css new file mode 100644 index 000000000..e2bbd90ef --- /dev/null +++ b/src/v4/social/components/CreatePostMenu/CreatePostMenu.module.css @@ -0,0 +1,10 @@ +.createPostMenu { + background-color: var(--asc-color-base-background); + padding: 0.75rem 1rem; + border-radius: 0.75rem; + position: absolute; + right: 1rem; + top: 3.25rem; + width: 12.5rem; + box-shadow: var(--asc-box-shadow-03); +} diff --git a/src/v4/social/components/CreatePostMenu/CreatePostMenu.tsx b/src/v4/social/components/CreatePostMenu/CreatePostMenu.tsx new file mode 100644 index 000000000..d4ae2666c --- /dev/null +++ b/src/v4/social/components/CreatePostMenu/CreatePostMenu.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import styles from './CreatePostMenu.module.css'; +import { CreatePostButton } from '~/v4/social/elements/CreatePostButton'; +import { CreateStoryButton } from '~/v4/social/elements/CreateStoryButtons'; +import { CreatePollButton } from '~/v4/social/elements/CreatePollButton/CreatePollButton'; +import { CreateLivestreamButton } from '~/v4/social/elements/CreateLivestreamButton'; + +interface CreatePostMenuProps { + pageId: string; +} + +export function CreatePostMenu({ pageId }: CreatePostMenuProps) { + const componentId = 'create_post_menu'; + return ( + <div className={styles.createPostMenu}> + <CreatePostButton pageId={pageId} componentId={componentId} /> + <CreateStoryButton pageId={pageId} componentId={componentId} /> + <CreatePollButton pageId={pageId} componentId={componentId} /> + <CreateLivestreamButton pageId={pageId} componentId={componentId} /> + </div> + ); +} diff --git a/src/v4/social/components/CreatePostMenu/index.tsx b/src/v4/social/components/CreatePostMenu/index.tsx new file mode 100644 index 000000000..fcd3d0bd7 --- /dev/null +++ b/src/v4/social/components/CreatePostMenu/index.tsx @@ -0,0 +1 @@ +export {CreatePostMenu} from './CreatePostMenu' \ No newline at end of file diff --git a/src/v4/social/components/TopNavigation/TopNavigation.tsx b/src/v4/social/components/TopNavigation/TopNavigation.tsx index 91ac8f882..12f92b7c5 100644 --- a/src/v4/social/components/TopNavigation/TopNavigation.tsx +++ b/src/v4/social/components/TopNavigation/TopNavigation.tsx @@ -8,15 +8,20 @@ import { useNavigation } from '~/v4/core/providers/NavigationProvider'; export interface TopNavigationProps { pageId?: string; + onClickPostCreationButton: (event: React.MouseEvent) => void; + createPostButtonRef: React.RefObject<HTMLDivElement>; } -export function TopNavigation({ pageId = '*' }: TopNavigationProps) { +export function TopNavigation({ + pageId = '*', + onClickPostCreationButton, + createPostButtonRef, +}: TopNavigationProps) { const componentId = 'top_navigation'; - const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = - useAmityComponent({ - pageId, - componentId, - }); + const { isExcluded, themeStyles } = useAmityComponent({ + pageId, + componentId, + }); const { goToSocialGlobalSearchPage } = useNavigation(); @@ -33,7 +38,12 @@ export function TopNavigation({ pageId = '*' }: TopNavigationProps) { componentId={componentId} onClick={() => goToSocialGlobalSearchPage()} /> - <PostCreationButton pageId={pageId} componentId={componentId} /> + <PostCreationButton + pageId={pageId} + componentId={componentId} + onClick={onClickPostCreationButton} + createPostButtonRef={createPostButtonRef} + /> </div> </div> ); diff --git a/src/v4/social/elements/BackButton/BackButton.tsx b/src/v4/social/elements/BackButton/BackButton.tsx index 267f0ef47..4ad522ed0 100644 --- a/src/v4/social/elements/BackButton/BackButton.tsx +++ b/src/v4/social/elements/BackButton/BackButton.tsx @@ -2,7 +2,6 @@ import React from 'react'; import { getDefaultConfig, useCustomization } from '~/v4/core/providers/CustomizationProvider'; import { IconComponent } from '~/v4/core/IconComponent'; import styles from './BackButton.module.css'; -import { useGenerateStylesShadeColors } from '~/v4/core/providers/ThemeProvider'; import { useAmityElement } from '~/v4/core/hooks/uikit'; const BackButtonSvg = (props: React.SVGProps<SVGSVGElement>) => ( @@ -44,18 +43,15 @@ export const BackButton = ({ if (isExcluded) return null; return ( - <button + <IconComponent data-qa-anchor={accessibilityId} className={styles.backButton} + defaultIcon={() => <BackButtonSvg className={defaultClassName} />} + imgIcon={() => <img src={config.icon} alt={uiReference} className={imgClassName} />} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} onClick={onClick} style={themeStyles} - > - <IconComponent - defaultIcon={() => <BackButtonSvg className={defaultClassName} />} - imgIcon={() => <img src={config.icon} alt={uiReference} className={imgClassName} />} - defaultIconName={defaultConfig.icon} - configIconName={config.icon} - /> - </button> + /> ); }; diff --git a/src/v4/social/elements/CloseButton/CloseButton.module.css b/src/v4/social/elements/CloseButton/CloseButton.module.css new file mode 100644 index 000000000..b2cfb2de1 --- /dev/null +++ b/src/v4/social/elements/CloseButton/CloseButton.module.css @@ -0,0 +1,28 @@ +.remoteImageButton { + width: 1.5rem; + height: 1.5rem; + cursor: pointer; + background-color: transparent; + border: none; + outline: none; + padding: var(--asc-spacing-none); + margin: var(--asc-spacing-none); + display: flex; + align-items: center; + justify-content: center; + + &:hover { + opacity: 0.8; + } + + &:active { + opacity: 0.6; + } +} + +.closeButton { + cursor: pointer; + fill: var(--asc-color-base-default); + width: 1.5rem; + height: 1.25rem; +} diff --git a/src/v4/social/elements/CloseButton/CloseButton.tsx b/src/v4/social/elements/CloseButton/CloseButton.tsx index b9346e1a4..6c69ce5f4 100644 --- a/src/v4/social/elements/CloseButton/CloseButton.tsx +++ b/src/v4/social/elements/CloseButton/CloseButton.tsx @@ -1,15 +1,17 @@ import React from 'react'; - -import { UICloseButton, UIRemoteImageButton } from './styles'; -import { isValidHttpUrl } from '~/utils'; -import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; +import styles from './CloseButton.module.css'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import clsx from 'clsx'; +import { IconComponent } from '~/v4/core/IconComponent'; interface CloseButtonProps { - pageId: string; - componentId: string; + pageId?: string; + componentId?: string; onClick?: (e: React.MouseEvent) => void; style?: React.CSSProperties; 'data-qa-anchor'?: string; + defaultClassName?: string; + imgClassName?: string; } export const CloseButton = ({ @@ -17,26 +19,45 @@ export const CloseButton = ({ componentId = '*', onClick = () => {}, style, - ...props + defaultClassName, + imgClassName, }: CloseButtonProps) => { - const elementId = 'close_button'; - const { getConfig, isExcluded } = useCustomization(); - const elementConfig = getConfig(`${pageId}/${componentId}/${elementId}`); - const isElementExcluded = isExcluded(`${pageId}/${componentId}/${elementId}`); - const closeIcon = elementConfig?.close_icon; + const CloseIconSVG = (props: React.SVGProps<SVGSVGElement>) => ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="100%" + height="100%" + viewBox="0 0 320 512" + fill="currentColor" + {...props} + > + <path + d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 + 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 + 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 + 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 + 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 + 0-22.58L207.6 256z" + /> + </svg> + ); - if (isElementExcluded) return null; + const elementId = 'close_button'; + const { isExcluded, config, uiReference } = useAmityElement({ + pageId, + componentId, + elementId, + }); - const isRemoteImage = closeIcon && isValidHttpUrl(closeIcon); + if (isExcluded) return null; - return isRemoteImage ? ( - <UIRemoteImageButton - data-qa-anchor="close_button" - src={closeIcon} + return ( + <IconComponent onClick={onClick} - {...props} + data-qa-anchor="close_button" + defaultIcon={() => <CloseIconSVG className={clsx(styles.closeButton, defaultClassName)} />} + imgIcon={() => <img src={config.icon} alt={uiReference} className={imgClassName} />} + configIconName={config.icon} /> - ) : ( - <UICloseButton data-qa-anchor="close_button" name={'Close'} onClick={onClick} {...props} /> ); }; diff --git a/src/v4/social/elements/CloseButton/styles.tsx b/src/v4/social/elements/CloseButton/styles.tsx deleted file mode 100644 index a2b371f96..000000000 --- a/src/v4/social/elements/CloseButton/styles.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import styled from 'styled-components'; -import { Icon } from '~/v4/core/components'; - -export const UICloseButton = styled(Icon)<{ backgroundColor?: string }>` - cursor: pointer; - fill: #ffffff; - background-color: ${({ backgroundColor }) => backgroundColor}; -`; - -export const UIRemoteImageButton = styled.img` - width: 1.5rem; - height: 1.5rem; - cursor: pointer; - background-color: transparent; - border: none; - outline: none; - padding: 0; - margin: 0; - display: flex; - align-items: center; - justify-content: center; - - &:hover { - opacity: 0.8; - } - - &:active { - opacity: 0.6; - } -`; diff --git a/src/v4/social/elements/CommunityAvatar/CommunityAvatar.tsx b/src/v4/social/elements/CommunityAvatar/CommunityAvatar.tsx index 3ecbc6ad6..170fd2300 100644 --- a/src/v4/social/elements/CommunityAvatar/CommunityAvatar.tsx +++ b/src/v4/social/elements/CommunityAvatar/CommunityAvatar.tsx @@ -33,7 +33,7 @@ export function CommunityAvatar({ community, }: CommunityAvatarProps) { const elementId = 'community_avatar'; - const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + const { accessibilityId, isExcluded, themeStyles } = useAmityElement({ pageId, componentId, diff --git a/src/v4/social/elements/CommunityDisplayName/CommunityDisplayName.tsx b/src/v4/social/elements/CommunityDisplayName/CommunityDisplayName.tsx index 4974a3704..6fbbe19a6 100644 --- a/src/v4/social/elements/CommunityDisplayName/CommunityDisplayName.tsx +++ b/src/v4/social/elements/CommunityDisplayName/CommunityDisplayName.tsx @@ -1,8 +1,6 @@ import React from 'react'; import { Typography } from '~/v4/core/components'; import { useAmityElement } from '~/v4/core/hooks/uikit'; -import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; -import { useGenerateStylesShadeColors } from '~/v4/core/providers/ThemeProvider'; import styles from './CommunityDisplayName.module.css'; export interface CommunityDisplayNameProps { diff --git a/src/v4/social/elements/CreateLivestreamButton/CreateLivestreamButton.module.css b/src/v4/social/elements/CreateLivestreamButton/CreateLivestreamButton.module.css new file mode 100644 index 000000000..def70d8a3 --- /dev/null +++ b/src/v4/social/elements/CreateLivestreamButton/CreateLivestreamButton.module.css @@ -0,0 +1,15 @@ +.createLivestreamButton { + display: flex; + padding: 0.75rem 0; +} + +.createLivestreamButton__text { + margin-left: 0.5rem; + color: var(--asc-color-base-default); +} + +.createLivestreamButton__icon { + fill: var(--asc-color-base-default); + width: 1.25rem; + height: 1.25rem; +} diff --git a/src/v4/social/elements/CreateLivestreamButton/CreateLivestreamButton.tsx b/src/v4/social/elements/CreateLivestreamButton/CreateLivestreamButton.tsx new file mode 100644 index 000000000..737211490 --- /dev/null +++ b/src/v4/social/elements/CreateLivestreamButton/CreateLivestreamButton.tsx @@ -0,0 +1,67 @@ +import React from 'react'; +import { IconComponent } from '~/v4/core/IconComponent'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { Typography } from '~/v4/core/components'; +import styles from './CreateLivestreamButton.module.css'; +import clsx from 'clsx'; + +const CreateLivestreamButtonSvg = (props: React.SVGProps<SVGSVGElement>) => ( + <svg + width="21" + height="20" + viewBox="0 0 21 20" + fill="none" + xmlns="http://www.w3.org/2000/svg" + {...props} + > + <path d="M6.91406 6.80469C7.01953 6.875 7.08984 7.05078 7.08984 7.19141C7.08984 7.29688 7.05469 7.4375 6.98438 7.50781C6.42188 8.17578 6 9.40625 6 10.25C6 11.1289 6.42188 12.3594 6.98438 13.0273C7.05469 13.0977 7.08984 13.2383 7.08984 13.3438C7.08984 13.4844 7.01953 13.6602 6.91406 13.7305L6.52734 14.1172C6.42188 14.2227 6.24609 14.293 6.14062 14.293C5.96484 14.293 5.78906 14.1875 5.68359 14.082C4.91016 13.168 4.27734 11.4453 4.27734 10.25C4.27734 9.08984 4.91016 7.36719 5.68359 6.45312C5.78906 6.34766 5.96484 6.24219 6.14062 6.24219C6.24609 6.24219 6.42188 6.3125 6.52734 6.41797L6.91406 6.80469ZM4.13672 4.0625C4.34766 4.27344 4.34766 4.58984 4.13672 4.83594C2.97656 6.10156 2.0625 8.5625 2.0625 10.2852C2.0625 12.0078 2.97656 14.4336 4.13672 15.6992C4.34766 15.9453 4.34766 16.2617 4.13672 16.4727L3.75 16.8594C3.64453 16.9648 3.46875 17.0352 3.32812 17.0352C3.1875 17.0352 3.01172 16.9297 2.90625 16.8242C1.25391 14.9961 0.375 12.7109 0.375 10.25C0.375 7.82422 1.25391 5.50391 2.90625 3.71094C3.01172 3.60547 3.1875 3.53516 3.32812 3.53516C3.46875 3.53516 3.64453 3.60547 3.75 3.67578L4.13672 4.0625ZM18.0586 3.71094C19.7109 5.50391 20.625 7.82422 20.625 10.25C20.625 12.7109 19.7109 14.9961 18.0586 16.8242C17.9531 16.9297 17.7773 17 17.6367 17C17.4961 17 17.3203 16.9297 17.25 16.8594L16.8281 16.4727C16.6172 16.2617 16.6172 15.9453 16.8281 15.6992C17.9883 14.4336 18.9023 12.0078 18.9023 10.2852C18.9023 8.52734 17.9883 6.10156 16.8281 4.83594C16.6172 4.58984 16.6172 4.27344 16.8281 4.0625L17.25 3.67578C17.3203 3.57031 17.4961 3.5 17.6367 3.5C17.7773 3.5 17.9531 3.60547 18.0586 3.71094ZM10.5 8.28125C11.5547 8.28125 12.4688 9.19531 12.4688 10.25C12.4688 11.3398 11.5547 12.2188 10.5 12.2188C9.41016 12.2188 8.53125 11.3398 8.53125 10.25C8.53125 9.19531 9.41016 8.28125 10.5 8.28125ZM14.4375 6.41797C14.543 6.3125 14.7188 6.24219 14.8242 6.24219C15 6.24219 15.1758 6.34766 15.2812 6.45312C16.0547 7.36719 16.6875 9.08984 16.6875 10.25C16.6875 11.4453 16.0547 13.168 15.2812 14.082C15.1758 14.1875 15 14.293 14.8242 14.293C14.7188 14.293 14.543 14.2227 14.4375 14.1172L14.0508 13.7305C13.9453 13.6602 13.875 13.4844 13.875 13.3438C13.875 13.2383 13.9102 13.0977 13.9805 13.0273C14.543 12.3594 14.9648 11.1289 14.9648 10.25C14.9648 9.40625 14.543 8.17578 13.9805 7.50781C13.9102 7.4375 13.875 7.29688 13.875 7.19141C13.875 7.05078 13.9453 6.875 14.0508 6.80469L14.4375 6.41797Z" /> + </svg> +); + +interface CreateLivestreamButtonProps { + pageId?: string; + componentId: string; + onClick?: (e: React.MouseEvent) => void; + defaultClassName?: string; +} + +export function CreateLivestreamButton({ + pageId = '*', + componentId = '*', + onClick, + defaultClassName, +}: CreateLivestreamButtonProps) { + const elementId = 'create_livestream_button'; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + return ( + <div + className={styles.createLivestreamButton} + onClick={() => {}} //TODO : Add event create livestream + data-qa-anchor={accessibilityId} + style={themeStyles} + > + <IconComponent + defaultIcon={() => ( + <CreateLivestreamButtonSvg + className={clsx(styles.createLivestreamButton__icon, defaultClassName)} + /> + )} + imgIcon={() => <img src={config.image} alt={uiReference} />} + configIconName={config.image} + defaultIconName={defaultConfig.image} + /> + <Typography.Body className={styles.createLivestreamButton__text}> + {config.text} + </Typography.Body> + </div> + ); +} + +export default CreateLivestreamButton; diff --git a/src/v4/social/elements/CreateLivestreamButton/index.tsx b/src/v4/social/elements/CreateLivestreamButton/index.tsx new file mode 100644 index 000000000..9bbc187b0 --- /dev/null +++ b/src/v4/social/elements/CreateLivestreamButton/index.tsx @@ -0,0 +1 @@ +export { CreateLivestreamButton } from './CreateLivestreamButton'; diff --git a/src/v4/social/elements/CreatePollButton/CreatePollButton.module.css b/src/v4/social/elements/CreatePollButton/CreatePollButton.module.css new file mode 100644 index 000000000..12c0551e4 --- /dev/null +++ b/src/v4/social/elements/CreatePollButton/CreatePollButton.module.css @@ -0,0 +1,15 @@ +.createPollButton { + display: flex; + padding: 0.75rem 0; +} + +.createPollButton__text { + margin-left: 0.5rem; + color: var(--asc-color-base-default); +} + +.createPollButton__icon { + fill: var(--asc-color-base-default); + width: 1.25rem; + height: 1.25rem; +} diff --git a/src/v4/social/elements/CreatePollButton/CreatePollButton.tsx b/src/v4/social/elements/CreatePollButton/CreatePollButton.tsx new file mode 100644 index 000000000..83aeff8ff --- /dev/null +++ b/src/v4/social/elements/CreatePollButton/CreatePollButton.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { IconComponent } from '~/v4/core/IconComponent'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { Typography } from '~/v4/core/components'; +import styles from './CreatePollButton.module.css'; +import clsx from 'clsx'; + +const CreatePollButtonSvg = (props: React.SVGProps<SVGSVGElement>) => ( + <svg + width="17" + height="17" + viewBox="0 0 17 17" + fill="none" + xmlns="http://www.w3.org/2000/svg" + {...props} + > + <path d="M14.6875 0.375C15.6016 0.375 16.375 1.14844 16.375 2.0625V14.4375C16.375 15.3867 15.6016 16.125 14.6875 16.125H2.3125C1.36328 16.125 0.625 15.3867 0.625 14.4375V2.0625C0.625 1.14844 1.36328 0.375 2.3125 0.375H14.6875ZM14.6875 14.4375V2.0625H2.3125V14.4375H14.6875ZM4.84375 12.75C4.52734 12.75 4.28125 12.5039 4.28125 12.1875V7.6875C4.28125 7.40625 4.52734 7.125 4.84375 7.125H5.40625C5.6875 7.125 5.96875 7.40625 5.96875 7.6875V12.1875C5.96875 12.5039 5.6875 12.75 5.40625 12.75H4.84375ZM8.21875 12.75C7.90234 12.75 7.65625 12.5039 7.65625 12.1875V4.3125C7.65625 4.03125 7.90234 3.75 8.21875 3.75H8.78125C9.0625 3.75 9.34375 4.03125 9.34375 4.3125V12.1875C9.34375 12.5039 9.0625 12.75 8.78125 12.75H8.21875ZM11.5938 12.75C11.2773 12.75 11.0312 12.5039 11.0312 12.1875V9.9375C11.0312 9.65625 11.2773 9.375 11.5938 9.375H12.1562C12.4375 9.375 12.7188 9.65625 12.7188 9.9375V12.1875C12.7188 12.5039 12.4375 12.75 12.1562 12.75H11.5938Z" /> + </svg> +); + +interface CreatePollButtonProps { + pageId?: string; + componentId: string; + onClick?: (e: React.MouseEvent) => void; + defaultClassName?: string; +} + +export function CreatePollButton({ + pageId = '*', + componentId = '*', + onClick, + defaultClassName, +}: CreatePollButtonProps) { + const elementId = 'create_poll_button'; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + return ( + <div + className={styles.createPollButton} + onClick={() => {}} //TODO : Add event create poll + data-qa-anchor={accessibilityId} + style={themeStyles} + > + <IconComponent + defaultIcon={() => ( + <CreatePollButtonSvg className={clsx(styles.createPollButton__icon, defaultClassName)} /> + )} + imgIcon={() => <img src={config.image} alt={uiReference} />} + configIconName={config.image} + defaultIconName={defaultConfig.image} + /> + <Typography.Body className={styles.createPollButton__text}>{config.text}</Typography.Body> + </div> + ); +} + +export default CreatePollButton; diff --git a/src/v4/social/elements/CreatePollButton/index.tsx b/src/v4/social/elements/CreatePollButton/index.tsx new file mode 100644 index 000000000..d5f390e93 --- /dev/null +++ b/src/v4/social/elements/CreatePollButton/index.tsx @@ -0,0 +1 @@ +export { CreatePollButton } from './CreatePollButton'; diff --git a/src/v4/social/elements/CreatePostButton/CreatePostButton.module.css b/src/v4/social/elements/CreatePostButton/CreatePostButton.module.css new file mode 100644 index 000000000..bac185ecf --- /dev/null +++ b/src/v4/social/elements/CreatePostButton/CreatePostButton.module.css @@ -0,0 +1,16 @@ +.createPostButton { + display: flex; + padding: 0.75rem 0; + cursor: pointer; +} + +.createPostButton__text { + margin-left: 0.5rem; + color: var(--asc-color-base-default); +} + +.createPostButton__icon { + fill: var(--asc-color-base-default); + width: 1.25rem; + height: 1.25rem; +} diff --git a/src/v4/social/elements/CreatePostButton/CreatePostButton.tsx b/src/v4/social/elements/CreatePostButton/CreatePostButton.tsx new file mode 100644 index 000000000..ff7ac1aa2 --- /dev/null +++ b/src/v4/social/elements/CreatePostButton/CreatePostButton.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { IconComponent } from '~/v4/core/IconComponent'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { Typography } from '~/v4/core/components'; +import styles from './CreatePostButton.module.css'; +import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; +import clsx from 'clsx'; + +const CreatePostButtonSvg = (props: React.SVGProps<SVGSVGElement>) => ( + <svg width="21" height="20" viewBox="0 0 21 20" xmlns="http://www.w3.org/2000/svg" {...props}> + <path d="M14.0625 12.7812L15.0625 11.7812C15.2188 11.625 15.5 11.75 15.5 11.9688V16.5C15.5 17.3438 14.8125 18 14 18H3C2.15625 18 1.5 17.3438 1.5 16.5V5.5C1.5 4.6875 2.15625 4 3 4H11.5312C11.75 4 11.875 4.28125 11.7188 4.4375L10.7188 5.4375C10.6562 5.5 10.5938 5.5 10.5312 5.5H3V16.5H14V12.9688C14 12.9062 14 12.8438 14.0625 12.7812ZM18.9375 6.5L10.75 14.6875L7.90625 15C7.09375 15.0938 6.40625 14.4062 6.5 13.5938L6.8125 10.75L15 2.5625C15.7188 1.84375 16.875 1.84375 17.5938 2.5625L18.9375 3.90625C19.6562 4.625 19.6562 5.78125 18.9375 6.5ZM15.875 7.4375L14.0625 5.625L8.25 11.4375L8 13.5L10.0625 13.25L15.875 7.4375ZM17.875 4.96875L16.5312 3.625C16.4062 3.46875 16.1875 3.46875 16.0625 3.625L15.125 4.5625L16.9375 6.40625L17.9062 5.4375C18.0312 5.28125 18.0312 5.09375 17.875 4.96875Z" /> + </svg> +); + +interface CreatePostButtonProps { + pageId?: string; + componentId: string; + onClick?: (e: React.MouseEvent) => void; + defaultClassName?: string; +} + +export function CreatePostButton({ + pageId = '*', + componentId = '*', + onClick, + defaultClassName, +}: CreatePostButtonProps) { + const elementId = 'create_post_button'; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityElement({ + pageId, + componentId, + elementId, + }); + const { AmityCreatePostMenuComponentBehavior } = usePageBehavior(); + + if (isExcluded) return null; + + return ( + <button + className={styles.createPostButton} + onClick={() => AmityCreatePostMenuComponentBehavior.goToSelectPostTargetPage()} + data-qa-anchor={accessibilityId} + style={themeStyles} + > + <IconComponent + defaultIcon={() => ( + <CreatePostButtonSvg className={clsx(styles.createPostButton__icon, defaultClassName)} /> + )} + imgIcon={() => <img src={config.image} alt={uiReference} />} + configIconName={config.image} + defaultIconName={defaultConfig.image} + /> + <Typography.Body className={styles.createPostButton__text}>{config.text}</Typography.Body> + </button> + ); +} + +export default CreatePostButton; diff --git a/src/v4/social/elements/CreatePostButton/index.tsx b/src/v4/social/elements/CreatePostButton/index.tsx new file mode 100644 index 000000000..32e08009a --- /dev/null +++ b/src/v4/social/elements/CreatePostButton/index.tsx @@ -0,0 +1 @@ +export { CreatePostButton } from "./CreatePostButton"; \ No newline at end of file diff --git a/src/v4/social/elements/CreateStoryButtons/CreateStoryButton.module.css b/src/v4/social/elements/CreateStoryButtons/CreateStoryButton.module.css new file mode 100644 index 000000000..8a15884ee --- /dev/null +++ b/src/v4/social/elements/CreateStoryButtons/CreateStoryButton.module.css @@ -0,0 +1,15 @@ +.createStoryButton { + display: flex; + padding: 0.75rem 0; +} + +.createStoryButton__text { + margin-left: 0.5rem; + color: var(--asc-color-base-default); +} + +.createStoryButton__icon { + fill: var(--asc-color-base-default); + width: 1.25rem; + height: 1.25rem; +} diff --git a/src/v4/social/elements/CreateStoryButtons/CreateStoryButton.tsx b/src/v4/social/elements/CreateStoryButtons/CreateStoryButton.tsx new file mode 100644 index 000000000..1078a7a76 --- /dev/null +++ b/src/v4/social/elements/CreateStoryButtons/CreateStoryButton.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { IconComponent } from '~/v4/core/IconComponent'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { Typography } from '~/v4/core/components'; +import styles from './CreateStoryButton.module.css'; +import clsx from 'clsx'; + +const CreateStoryButtonSvg = (props: React.SVGProps<SVGSVGElement>) => ( + <svg width="17" height="18" viewBox="0 0 17 18" xmlns="http://www.w3.org/2000/svg" {...props}> + <path + fillRule="evenodd" + clipRule="evenodd" + d="M0.771159 12.2248C1.00906 12.7861 1.30474 13.3088 1.65819 13.7927C1.85468 14.0618 2.2428 14.0749 2.47778 13.8387L2.85238 13.4622C3.05585 13.2578 3.07242 12.9352 2.91359 12.6944C2.68511 12.3479 2.4918 11.9785 2.33366 11.5862C2.17162 11.1841 2.04651 10.7654 1.95831 10.33C1.90156 10.0497 1.66209 9.83785 1.37619 9.83785H0.7981C0.460143 9.83785 0.194237 10.1301 0.252477 10.463C0.359316 11.0737 0.53221 11.661 0.771159 12.2248ZM0.791992 5.77587C0.565717 6.33406 0.392758 6.91522 0.273115 7.51935C0.206005 7.85821 0.473794 8.1628 0.819238 8.1628H1.37619C1.66209 8.1628 1.90156 7.95091 1.95831 7.6707C2.04651 7.23526 2.17162 6.81652 2.33366 6.41448C2.4918 6.0221 2.68511 5.6527 2.91359 5.30628C3.07242 5.06546 3.05585 4.7429 2.85238 4.53841L2.47778 4.16192C2.2428 3.92576 1.85456 3.93884 1.65906 4.2086C1.30847 4.69235 1.01944 5.21477 0.791992 5.77587ZM5.26074 16.7265C5.81526 16.966 6.39491 17.1396 6.9997 17.247C7.33267 17.3061 7.62533 17.0401 7.62533 16.7019V16.1571C7.62533 15.8715 7.41372 15.6325 7.13436 15.5731C6.70488 15.4817 6.29562 15.3497 5.90658 15.177C5.52127 15.0061 5.14859 14.8025 4.78852 14.5662C4.5475 14.4081 4.22544 14.4327 4.02574 14.6406L3.64108 15.041C3.40868 15.2829 3.43286 15.6735 3.7084 15.8649C4.19354 16.2017 4.71098 16.4889 5.26074 16.7265ZM3.71752 2.12947C3.43846 2.32272 3.41731 2.71924 3.65673 2.95986L4.05715 3.36229C4.26214 3.56831 4.58771 3.58533 4.82847 3.42255C5.1704 3.19137 5.53324 2.99173 5.91699 2.82361C6.3137 2.64981 6.73277 2.5172 7.17418 2.42577C7.45449 2.3677 7.66699 2.12833 7.66699 1.84207V1.29669C7.66699 0.959243 7.37557 0.693429 7.04294 0.750218C6.41529 0.857371 5.82122 1.03203 5.26074 1.27419C4.71443 1.51024 4.20002 1.79533 3.71752 2.12947ZM9.82271 15.5735C9.54395 15.6315 9.33366 15.8703 9.33366 16.155V16.6953C9.33366 17.0359 9.63023 17.3024 9.96541 17.2422C11.8264 16.908 13.4005 16.0311 14.6878 14.6117C16.1184 13.0344 16.8337 11.1639 16.8337 9.00033C16.8337 6.83673 16.1184 4.96627 14.6878 3.38894C13.4005 1.96952 11.8264 1.09268 9.96541 0.758409C9.63023 0.698204 9.33366 0.964784 9.33366 1.30532V1.84562C9.33366 2.13035 9.54395 2.36916 9.82271 2.42716C11.2862 2.73166 12.5155 3.45008 13.5107 4.5824C14.6149 5.83868 15.167 7.31132 15.167 9.00033C15.167 10.6893 14.6149 12.162 13.5107 13.4182C12.5155 14.5506 11.2862 15.269 9.82271 15.5735Z" + /> + <path d="M12.7189 8.47933C12.9012 8.47933 13.0835 8.66162 13.0835 8.84391V9.57308C13.0835 9.77816 12.9012 9.93766 12.7189 9.93766H9.43766V13.2189C9.43766 13.424 9.25537 13.5835 9.07308 13.5835H8.34391C8.13883 13.5835 7.97933 13.424 7.97933 13.2189V9.93766H4.69808C4.493 9.93766 4.3335 9.77816 4.3335 9.57308V8.84391C4.3335 8.66162 4.493 8.47933 4.69808 8.47933H7.97933V5.19808C7.97933 5.01579 8.13883 4.8335 8.34391 4.8335H9.07308C9.25537 4.8335 9.43766 5.01579 9.43766 5.19808V8.47933H12.7189Z" /> + </svg> +); + +interface CreateStoryButtonProps { + pageId?: string; + componentId: string; + onClick?: (e: React.MouseEvent) => void; + defaultClassName?: string; +} + +export function CreateStoryButton({ + pageId = '*', + componentId = '*', + onClick, + defaultClassName, +}: CreateStoryButtonProps) { + const elementId = 'create_story_button'; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + return ( + <div + className={styles.createStoryButton} + onClick={() => {}} //TODO : Add event create story + data-qa-anchor={accessibilityId} + style={themeStyles} + > + <IconComponent + defaultIcon={() => ( + <CreateStoryButtonSvg + className={clsx(styles.createStoryButton__icon, defaultClassName)} + /> + )} + imgIcon={() => <img src={config.image} alt={uiReference} />} + configIconName={config.image} + defaultIconName={defaultConfig.image} + /> + <Typography.Body className={styles.createStoryButton__text}>{config.text}</Typography.Body> + </div> + ); +} + +export default CreateStoryButton; diff --git a/src/v4/social/elements/CreateStoryButtons/index.tsx b/src/v4/social/elements/CreateStoryButtons/index.tsx new file mode 100644 index 000000000..701322549 --- /dev/null +++ b/src/v4/social/elements/CreateStoryButtons/index.tsx @@ -0,0 +1 @@ +export { CreateStoryButton } from './CreateStoryButton'; diff --git a/src/v4/social/elements/MyTimelineAvatar/MyTimelineAvatar.module.css b/src/v4/social/elements/MyTimelineAvatar/MyTimelineAvatar.module.css new file mode 100644 index 000000000..e5dd43e6f --- /dev/null +++ b/src/v4/social/elements/MyTimelineAvatar/MyTimelineAvatar.module.css @@ -0,0 +1,10 @@ +.myTimelineAvatar { + display: flex; + align-items: center; + margin-right: var(--asc-spacing-s1); +} + +.myTimelineAvatar__userAvatar { + width: 2.5rem; + height: 2.5rem; +} diff --git a/src/v4/social/elements/MyTimelineAvatar/MyTimelineAvatar.tsx b/src/v4/social/elements/MyTimelineAvatar/MyTimelineAvatar.tsx new file mode 100644 index 000000000..d741a4b60 --- /dev/null +++ b/src/v4/social/elements/MyTimelineAvatar/MyTimelineAvatar.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import styles from './MyTimelineAvatar.module.css'; +import { UserAvatar } from '~/v4/social/internal-components/UserAvatar/UserAvatar'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; + +interface MyTimelineAvatarProps { + pageId?: string; + componentId?: string; + userId?: string | null; +} + +export function MyTimelineAvatar({ pageId = '*', componentId = '*', userId }: MyTimelineAvatarProps) { + const elementId = 'my_timeline_avatar'; + const { accessibilityId, isExcluded } = useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + return ( + <div className={styles.myTimelineAvatar} data-qa-anchor={accessibilityId}> + <UserAvatar className={styles.myTimelineAvatar__userAvatar} userId={userId} /> + </div> + ); +} diff --git a/src/v4/social/elements/MyTimelineAvatar/index.tsx b/src/v4/social/elements/MyTimelineAvatar/index.tsx new file mode 100644 index 000000000..082c877c5 --- /dev/null +++ b/src/v4/social/elements/MyTimelineAvatar/index.tsx @@ -0,0 +1 @@ +export { MyTimelineAvatar } from './MyTimelineAvatar' \ No newline at end of file diff --git a/src/v4/social/elements/MyTimelineText/MyTimelineText.module.css b/src/v4/social/elements/MyTimelineText/MyTimelineText.module.css new file mode 100644 index 000000000..8a7e9ae61 --- /dev/null +++ b/src/v4/social/elements/MyTimelineText/MyTimelineText.module.css @@ -0,0 +1,11 @@ +.myTimelineText { + font-size: 0.9375rem; + font-weight: var(--asc-text-font-weight-bold); + line-height: var(--asc-line-height-md); + letter-spacing: -0.015rem; + display: block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + color: var(--asc-color-base-default); +} diff --git a/src/v4/social/elements/MyTimelineText/MyTimelineText.tsx b/src/v4/social/elements/MyTimelineText/MyTimelineText.tsx new file mode 100644 index 000000000..aab4e9d56 --- /dev/null +++ b/src/v4/social/elements/MyTimelineText/MyTimelineText.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { Typography } from '~/v4/core/components/Typography'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import styles from './MyTimelineText.module.css'; + +interface MyTimelineTextProps { + pageId?: string; + componentId?: string; +} + +export function MyTimelineText({ pageId = '*', componentId = '*' }: MyTimelineTextProps) { + const elementId = 'my_timeline_text'; + const { accessibilityId, config, isExcluded, themeStyles } = useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + return ( + <Typography.Body + className={styles.myTimelineText} + style={{ ...themeStyles }} + data-qa-anchor={accessibilityId} + > + {config.text} + </Typography.Body> + ); +} diff --git a/src/v4/social/elements/MyTimelineText/index.tsx b/src/v4/social/elements/MyTimelineText/index.tsx new file mode 100644 index 000000000..e01c05eb3 --- /dev/null +++ b/src/v4/social/elements/MyTimelineText/index.tsx @@ -0,0 +1 @@ +export { MyTimelineText } from './MyTimelineText'; diff --git a/src/v4/social/elements/PostCreationButton/PostCreationButton.module.css b/src/v4/social/elements/PostCreationButton/PostCreationButton.module.css index fcf6abee5..3ac83ccb8 100644 --- a/src/v4/social/elements/PostCreationButton/PostCreationButton.module.css +++ b/src/v4/social/elements/PostCreationButton/PostCreationButton.module.css @@ -3,9 +3,11 @@ display: flex; justify-content: center; align-items: center; + position: relative; border-radius: 50%; width: 2rem; height: 2rem; + cursor: pointer; } .postCreationButton__icon { diff --git a/src/v4/social/elements/PostCreationButton/PostCreationButton.tsx b/src/v4/social/elements/PostCreationButton/PostCreationButton.tsx index 86030ad00..8c97d1a3e 100644 --- a/src/v4/social/elements/PostCreationButton/PostCreationButton.tsx +++ b/src/v4/social/elements/PostCreationButton/PostCreationButton.tsx @@ -25,6 +25,7 @@ export interface PostCreationButtonProps { defaultClassName?: string; imgClassName?: string; onClick?: (e: React.MouseEvent) => void; + createPostButtonRef: React.RefObject<HTMLDivElement>; } export function PostCreationButton({ @@ -33,6 +34,7 @@ export function PostCreationButton({ defaultClassName, imgClassName, onClick, + createPostButtonRef, }: PostCreationButtonProps) { const elementId = 'post_creation_button'; const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = @@ -52,6 +54,7 @@ export function PostCreationButton({ className={styles.postCreationButton} onClick={onClick} data-qa-anchor={accessibilityId} + ref={createPostButtonRef} > <PostCreationButtonSvg className={clsx(styles.postCreationButton__icon, defaultClassName)} diff --git a/src/v4/social/elements/Title/Title.tsx b/src/v4/social/elements/Title/Title.tsx index 925a39a84..fa05f0a5b 100644 --- a/src/v4/social/elements/Title/Title.tsx +++ b/src/v4/social/elements/Title/Title.tsx @@ -1,28 +1,31 @@ import React from 'react'; import styles from './Title.module.css'; -import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; import { Typography } from '~/v4/core/components'; -import { useGenerateStylesShadeColors } from '~/v4/core/providers/ThemeProvider'; import { useAmityElement } from '~/v4/core/hooks/uikit'; +import clsx from 'clsx'; interface TitleProps { pageId?: string; componentId?: string; + titleClassName?: string; } -export function Title({ pageId = '*', componentId = '*' }: TitleProps) { +export function Title({ pageId = '*', componentId = '*', titleClassName }: TitleProps) { const elementId = 'title'; - const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = - useAmityElement({ - pageId, - componentId, - elementId, - }); + const { accessibilityId, config, isExcluded, themeStyles } = useAmityElement({ + pageId, + componentId, + elementId, + }); if (isExcluded) return null; return ( - <Typography.Title className={styles.title} style={themeStyles} data-qa-anchor={accessibilityId}> + <Typography.Title + className={clsx(styles.title, titleClassName)} + style={themeStyles} + data-qa-anchor={accessibilityId} + > {config.text} </Typography.Title> ); diff --git a/src/v4/social/hooks/collections/useCommunitiesCollection.ts b/src/v4/social/hooks/collections/useCommunitiesCollection.ts new file mode 100644 index 000000000..210bdc16d --- /dev/null +++ b/src/v4/social/hooks/collections/useCommunitiesCollection.ts @@ -0,0 +1,18 @@ +import { CommunityRepository } from '@amityco/ts-sdk'; +import useLiveCollection from '~/v4/core/hooks/useLiveCollection'; + +export default function useCommunitiesCollection( + queryParams?: Parameters<typeof CommunityRepository.getCommunities>[0], + shouldCall?: () => boolean, +) { + const { items, ...rest } = useLiveCollection({ + fetcher: CommunityRepository.getCommunities, + params: queryParams as Parameters<typeof CommunityRepository.getCommunities>[0], + shouldCall: () => !!queryParams && (shouldCall ? shouldCall?.() : true), + }); + + return { + communities: items, + ...rest, + }; +} diff --git a/src/v4/social/hooks/useLiveCollection.ts b/src/v4/social/hooks/useLiveCollection.ts new file mode 100644 index 000000000..a78b0dd14 --- /dev/null +++ b/src/v4/social/hooks/useLiveCollection.ts @@ -0,0 +1,80 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import { useSDKLiveCollectionConnector } from '~/v4/core/providers/SDKConnectorProvider'; + + +function useLiveCollection<TCallback, TParams>({ + fetcher, + params, + callback = () => {}, + config, + shouldCall = () => true, +}: { + fetcher: ( + params: Amity.LiveCollectionParams<TParams>, + callback: Amity.LiveCollectionCallback<TCallback>, + config?: Amity.LiveCollectionConfig, + ) => Amity.Unsubscriber; + params: Amity.LiveCollectionParams<TParams>; + callback?: Amity.LiveCollectionCallback<TCallback>; + config?: Amity.LiveCollectionConfig; + shouldCall?: () => boolean; +}): { + items: TCallback[]; + isLoading: boolean; + hasMore: boolean; + loadMore: () => void; + error: Error | null; + loadMoreHasBeenCalled: boolean; +} { + const { subscribe } = useSDKLiveCollectionConnector(); + const [loadMoreHasBeenCalled, setLoadMoreHasBeenCalled] = useState(false); + const [isLoading, setIsLoading] = useState(shouldCall ? shouldCall() : true); + const [items, setItems] = useState<TCallback[]>([]); + const [error, setError] = useState<Error | null>(null); + const [hasMore, setHasMore] = useState(false); + const loadMoreFnRef = useRef<(() => void) | null>(null); + + const loadMore = useCallback(() => { + if (loadMoreFnRef.current) { + setLoadMoreHasBeenCalled(true); + loadMoreFnRef.current?.(); + } + }, [loadMoreFnRef, loadMoreHasBeenCalled, isLoading, setIsLoading]); + + const callbackFn = useCallback( + (response) => { + if (!shouldCall()) return; + if (response.data) setItems(response.data); + setIsLoading(response.loading); + setHasMore(response.hasNextPage); + setError(response.error); + loadMoreFnRef.current = response.onNextPage; + callback(response); + }, + [shouldCall, setItems, setIsLoading, setHasMore, loadMoreFnRef, callback], + ); + + useEffect(() => { + if (!shouldCall()) return; + const { unsubscribe } = subscribe({ + fetcher, + params, + callback: callbackFn, + }); + + return () => { + unsubscribe(); + }; + }, [params, shouldCall]); + + return { + items, + hasMore, + isLoading, + loadMore, + error, + loadMoreHasBeenCalled, + }; +} + +export default useLiveCollection; diff --git a/src/v4/social/pages/Application/index.tsx b/src/v4/social/pages/Application/index.tsx index 20baa3c3f..369665351 100644 --- a/src/v4/social/pages/Application/index.tsx +++ b/src/v4/social/pages/Application/index.tsx @@ -6,6 +6,7 @@ import { PageTypes, useNavigation } from '~/v4/core/providers/NavigationProvider import { StoryProvider } from '~/v4/social/providers/StoryProvider'; import { SocialGlobalSearchPage } from '~/v4/social/pages/SocialGlobalSearchPage'; import { ViewStoryPage } from '~/v4/social/pages/StoryPage'; +import { SelectPostTargetPage } from '../SelectPostTargetPage'; const Application = () => { const { page } = useNavigation(); @@ -17,6 +18,7 @@ const Application = () => { {page.type === PageTypes.SocialGlobalSearchPage && <SocialGlobalSearchPage />} {page.type === PageTypes.PostDetailPage && <PostDetailPage id={page.context.postId} />} {page.type === PageTypes.ViewStoryPage && <ViewStoryPage type="globalFeed" />} + {page.type === PageTypes.SelectPostTargetPage && <SelectPostTargetPage />} </div> </StoryProvider> ); diff --git a/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.module.css b/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.module.css new file mode 100644 index 000000000..a57d1b193 --- /dev/null +++ b/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.module.css @@ -0,0 +1,48 @@ +.selectPostTargetPage { + max-width: 100vw; + height: 100vh; + padding: var(--asc-spacing-none) var(--asc-spacing-m1); + background-color: var(--asc-color-base-background); +} + +.selectPostTargetPage__topBar { + display: flex; + justify-content: space-between; + align-items: center; + padding: var(--asc-spacing-m1) var(--asc-spacing-none); +} + +.selectPostTargetPage__timeline { + display: flex; + align-items: center; + padding: var(--asc-spacing-s1) var(--asc-spacing-none); +} + +.selectPostTargetPage__line { + margin-top: var(--asc-spacing-s1); + border-top: 1px solid var(--asc-color-base-shade4); +} + +.selectPostTargetPage__myCommunities { + display: flex; + text-align: left; + color: var(--asc-color-base-default); + padding: var(--asc-spacing-m1) var(--asc-spacing-none) var(--asc-spacing-s1); + font-size: 0.9375rem; + line-height: var(--asc-line-height-md); + letter-spacing: -0.015rem; + opacity: 0.4; +} + +.selectPostTargetPage__communityAvatar { + margin-right: var(--asc-spacing-s1); +} + +.selectPostTargetPage__closeButton { + display: flex; + fill: var(--asc-color-base-default); +} + +.selectPostTargetPage__title { + color: var(--asc-color-base-default); +} diff --git a/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.stories.tsx b/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.stories.tsx new file mode 100644 index 000000000..1c5bfbf45 --- /dev/null +++ b/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.stories.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { SelectPostTargetPage } from './SelectPostTargetPage'; + +export default { + title: 'v4-social/pages/SelectPostTargetPage', +}; + +export const SelectPostTargetPageStories = { + render: () => { + return <SelectPostTargetPage />; + }, +}; diff --git a/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.tsx b/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.tsx new file mode 100644 index 000000000..7cae309e0 --- /dev/null +++ b/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.tsx @@ -0,0 +1,86 @@ +import React, { useRef } from 'react'; +import styles from './SelectPostTargetPage.module.css'; +import { useAmityPage } from '~/v4/core/hooks/uikit'; +import { CloseButton } from '~/v4/social/elements/CloseButton/CloseButton'; +import { Title } from '~/v4/social/elements/Title/Title'; +import { MyTimelineAvatar } from '~/v4/social/elements/MyTimelineAvatar'; +import { MyTimelineText } from '~/v4/social/elements/MyTimelineText'; +import { useNavigation } from '~/v4/core/providers/NavigationProvider'; +import { CommunityOfficialBadge } from '~/v4/social/elements/CommunityOfficialBadge'; +import useCommunitiesCollection from '~/v4/social/hooks/collections/useCommunitiesCollection'; +import { CommunityPrivateBadge } from '~/v4/social/elements/CommunityPrivateBadge'; +import { CommunityDisplayName } from '~/v4/social/elements/CommunityDisplayName'; +import { CommunityAvatar } from '~/v4/social/elements/CommunityAvatar'; +import useIntersectionObserver from '~/v4/core/hooks/useIntersectionObserver'; +import useUser from '~/v4/core/hooks/objects/useUser'; + +export function SelectPostTargetPage() { + const pageId = 'select_post_target_page'; + const { themeStyles } = useAmityPage({ + pageId, + }); + const { onBack } = useNavigation(); + const { communities, hasMore, loadMore, isLoading } = useCommunitiesCollection({ + sortBy: 'displayName', + limit: 20, + }); + const intersectionRef = useRef<HTMLDivElement>(null); + const { user } = useUser(); + + useIntersectionObserver({ + onIntersect: () => { + if (hasMore && isLoading === false) { + loadMore(); + } + }, + ref: intersectionRef, + }); + + const renderCommunity = communities.map((community) => { + return ( + <div + onClick={() => { + //TODO: Navigate to create post page + }} + key={community.communityId} + className={styles.selectPostTargetPage__timeline} + > + <div className={styles.selectPostTargetPage__communityAvatar}> + <CommunityAvatar pageId={pageId} community={community} /> + </div> + <CommunityDisplayName pageId={pageId} community={community} /> + <div> + {community.isOfficial && <CommunityOfficialBadge />} + {!community.isPublic && <CommunityPrivateBadge />} + </div> + </div> + ); + }); + + return ( + <div className={styles.selectPostTargetPage} style={themeStyles}> + <div className={styles.selectPostTargetPage__topBar}> + <CloseButton + imgClassName={styles.selectPostTargetPage__closeButton} + pageId={pageId} + onClick={onBack} + /> + <Title pageId={pageId} titleClassName={styles.selectPostTargetPage__title} /> + <div /> + </div> + <div + onClick={() => { + // TODO: Navigate to create post page + }} + className={styles.selectPostTargetPage__timeline} + > + <MyTimelineAvatar pageId={pageId} userId={user?.userId} /> + <MyTimelineText pageId={pageId} /> + </div> + <div className={styles.selectPostTargetPage__line} /> + <div className={styles.selectPostTargetPage__myCommunities}>My Communities</div> + {renderCommunity} + <div ref={intersectionRef} /> + </div> + ); +} diff --git a/src/v4/social/pages/SelectPostTargetPage/index.tsx b/src/v4/social/pages/SelectPostTargetPage/index.tsx new file mode 100644 index 000000000..f8931ab0b --- /dev/null +++ b/src/v4/social/pages/SelectPostTargetPage/index.tsx @@ -0,0 +1 @@ +export { SelectPostTargetPage } from './SelectPostTargetPage'; diff --git a/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx b/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx index a19c74e28..c65a10721 100644 --- a/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx +++ b/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import styles from './SocialHomePage.module.css'; import { TopNavigation } from '~/v4/social/components/TopNavigation'; @@ -8,6 +8,7 @@ import { ExploreButton } from '~/v4/social/elements/ExploreButton'; import { MyCommunitiesButton } from '~/v4/social/elements/MyCommunitiesButton'; import { Newsfeed } from '~/v4/social/components/Newsfeed'; import { useAmityPage } from '~/v4/core/hooks/uikit'; +import { CreatePostMenu } from '~/v4/social/components/CreatePostMenu'; enum EnumTabNames { Newsfeed = 'Newsfeed', @@ -17,17 +18,51 @@ enum EnumTabNames { export function SocialHomePage() { const pageId = 'social_home_page'; - const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = - useAmityPage({ - pageId, - }); + const { themeStyles } = useAmityPage({ + pageId, + }); const [activeTab, setActiveTab] = useState(EnumTabNames.Newsfeed); + const [isShowCreatePostMenu, setIsShowCreatePostMenu] = useState(false); + const createPostMenuRef = useRef<HTMLDivElement | null>(null); + const createPostButtonRef = useRef<HTMLDivElement>(null); + + const handleClickButton = (event: React.MouseEvent) => { + event.stopPropagation(); + setIsShowCreatePostMenu((prev) => !prev); + }; + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + createPostMenuRef.current && + !createPostMenuRef.current.contains(event.target as Node) && + createPostButtonRef.current !== event.target + ) { + setIsShowCreatePostMenu(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + return ( <div className={styles.socialHomePage} style={themeStyles}> <div className={styles.socialHomePage__topBar}> - <TopNavigation pageId={pageId} /> + <TopNavigation + pageId={pageId} + onClickPostCreationButton={handleClickButton} + createPostButtonRef={createPostButtonRef} + /> + {isShowCreatePostMenu && ( + <div ref={createPostMenuRef}> + <CreatePostMenu pageId={pageId} /> + </div> + )} <div className={styles.socialHomePage__tabs}> <NewsfeedButton pageId={pageId} From f2b97bb64750250f57bcd982c2fdcf56f26354fc Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Fri, 14 Jun 2024 15:21:16 +0700 Subject: [PATCH 126/300] feat: ASC-22888 - hide create post menu for current release (#413) * feat: comment code create post menu for current release * fix: remove SelectPostTargetPage --- .../TopNavigation/TopNavigation.tsx | 13 +++-- .../PostCreationButton.module.css | 3 +- .../PostCreationButton/PostCreationButton.tsx | 6 +- src/v4/social/pages/Application/index.tsx | 2 +- .../SelectPostTargetPage.stories.tsx | 12 ---- .../pages/SocialHomePage/SocialHomePage.tsx | 56 ++++++++++--------- 6 files changed, 42 insertions(+), 50 deletions(-) delete mode 100644 src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.stories.tsx diff --git a/src/v4/social/components/TopNavigation/TopNavigation.tsx b/src/v4/social/components/TopNavigation/TopNavigation.tsx index 12f92b7c5..cf3c830af 100644 --- a/src/v4/social/components/TopNavigation/TopNavigation.tsx +++ b/src/v4/social/components/TopNavigation/TopNavigation.tsx @@ -8,14 +8,14 @@ import { useNavigation } from '~/v4/core/providers/NavigationProvider'; export interface TopNavigationProps { pageId?: string; - onClickPostCreationButton: (event: React.MouseEvent) => void; - createPostButtonRef: React.RefObject<HTMLDivElement>; + // onClickPostCreationButton: (event: React.MouseEvent) => void; + // createPostButtonRef: React.RefObject<HTMLDivElement>; } export function TopNavigation({ pageId = '*', - onClickPostCreationButton, - createPostButtonRef, + // onClickPostCreationButton, + // createPostButtonRef, }: TopNavigationProps) { const componentId = 'top_navigation'; const { isExcluded, themeStyles } = useAmityComponent({ @@ -41,8 +41,9 @@ export function TopNavigation({ <PostCreationButton pageId={pageId} componentId={componentId} - onClick={onClickPostCreationButton} - createPostButtonRef={createPostButtonRef} + onClick={() => {}} + // onClick={onClickPostCreationButton} + // createPostButtonRef={createPostButtonRef} /> </div> </div> diff --git a/src/v4/social/elements/PostCreationButton/PostCreationButton.module.css b/src/v4/social/elements/PostCreationButton/PostCreationButton.module.css index 3ac83ccb8..efd771ff9 100644 --- a/src/v4/social/elements/PostCreationButton/PostCreationButton.module.css +++ b/src/v4/social/elements/PostCreationButton/PostCreationButton.module.css @@ -7,7 +7,8 @@ border-radius: 50%; width: 2rem; height: 2rem; - cursor: pointer; + + /* cursor: pointer; */ } .postCreationButton__icon { diff --git a/src/v4/social/elements/PostCreationButton/PostCreationButton.tsx b/src/v4/social/elements/PostCreationButton/PostCreationButton.tsx index 8c97d1a3e..1bc8c6dee 100644 --- a/src/v4/social/elements/PostCreationButton/PostCreationButton.tsx +++ b/src/v4/social/elements/PostCreationButton/PostCreationButton.tsx @@ -25,7 +25,7 @@ export interface PostCreationButtonProps { defaultClassName?: string; imgClassName?: string; onClick?: (e: React.MouseEvent) => void; - createPostButtonRef: React.RefObject<HTMLDivElement>; + // createPostButtonRef: React.RefObject<HTMLDivElement>; } export function PostCreationButton({ @@ -34,7 +34,7 @@ export function PostCreationButton({ defaultClassName, imgClassName, onClick, - createPostButtonRef, + // createPostButtonRef, }: PostCreationButtonProps) { const elementId = 'post_creation_button'; const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = @@ -54,7 +54,7 @@ export function PostCreationButton({ className={styles.postCreationButton} onClick={onClick} data-qa-anchor={accessibilityId} - ref={createPostButtonRef} + // ref={createPostButtonRef} > <PostCreationButtonSvg className={clsx(styles.postCreationButton__icon, defaultClassName)} diff --git a/src/v4/social/pages/Application/index.tsx b/src/v4/social/pages/Application/index.tsx index 369665351..3f98d21c4 100644 --- a/src/v4/social/pages/Application/index.tsx +++ b/src/v4/social/pages/Application/index.tsx @@ -18,7 +18,7 @@ const Application = () => { {page.type === PageTypes.SocialGlobalSearchPage && <SocialGlobalSearchPage />} {page.type === PageTypes.PostDetailPage && <PostDetailPage id={page.context.postId} />} {page.type === PageTypes.ViewStoryPage && <ViewStoryPage type="globalFeed" />} - {page.type === PageTypes.SelectPostTargetPage && <SelectPostTargetPage />} + {/* {page.type === PageTypes.SelectPostTargetPage && <SelectPostTargetPage />} */} </div> </StoryProvider> ); diff --git a/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.stories.tsx b/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.stories.tsx deleted file mode 100644 index 1c5bfbf45..000000000 --- a/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.stories.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import { SelectPostTargetPage } from './SelectPostTargetPage'; - -export default { - title: 'v4-social/pages/SelectPostTargetPage', -}; - -export const SelectPostTargetPageStories = { - render: () => { - return <SelectPostTargetPage />; - }, -}; diff --git a/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx b/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx index c65a10721..9db2cb64c 100644 --- a/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx +++ b/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx @@ -8,7 +8,7 @@ import { ExploreButton } from '~/v4/social/elements/ExploreButton'; import { MyCommunitiesButton } from '~/v4/social/elements/MyCommunitiesButton'; import { Newsfeed } from '~/v4/social/components/Newsfeed'; import { useAmityPage } from '~/v4/core/hooks/uikit'; -import { CreatePostMenu } from '~/v4/social/components/CreatePostMenu'; +// import { CreatePostMenu } from '~/v4/social/components/CreatePostMenu'; enum EnumTabNames { Newsfeed = 'Newsfeed', @@ -24,45 +24,47 @@ export function SocialHomePage() { const [activeTab, setActiveTab] = useState(EnumTabNames.Newsfeed); - const [isShowCreatePostMenu, setIsShowCreatePostMenu] = useState(false); - const createPostMenuRef = useRef<HTMLDivElement | null>(null); - const createPostButtonRef = useRef<HTMLDivElement>(null); + // const [isShowCreatePostMenu, setIsShowCreatePostMenu] = useState(false); + // const createPostMenuRef = useRef<HTMLDivElement | null>(null); + // const createPostButtonRef = useRef<HTMLDivElement>(null); - const handleClickButton = (event: React.MouseEvent) => { - event.stopPropagation(); - setIsShowCreatePostMenu((prev) => !prev); - }; + // const handleClickButton = (event: React.MouseEvent) => { + // event.stopPropagation(); + // setIsShowCreatePostMenu((prev) => !prev); + // }; + + // Hide code for current release - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if ( - createPostMenuRef.current && - !createPostMenuRef.current.contains(event.target as Node) && - createPostButtonRef.current !== event.target - ) { - setIsShowCreatePostMenu(false); - } - }; + // useEffect(() => { + // const handleClickOutside = (event: MouseEvent) => { + // if ( + // createPostMenuRef.current && + // !createPostMenuRef.current.contains(event.target as Node) && + // createPostButtonRef.current !== event.target + // ) { + // setIsShowCreatePostMenu(false); + // } + // }; - document.addEventListener('mousedown', handleClickOutside); - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; - }, []); + // document.addEventListener('mousedown', handleClickOutside); + // return () => { + // document.removeEventListener('mousedown', handleClickOutside); + // }; + // }, []); return ( <div className={styles.socialHomePage} style={themeStyles}> <div className={styles.socialHomePage__topBar}> <TopNavigation pageId={pageId} - onClickPostCreationButton={handleClickButton} - createPostButtonRef={createPostButtonRef} + // onClickPostCreationButton={handleClickButton} + // createPostButtonRef={createPostButtonRef} /> - {isShowCreatePostMenu && ( + {/* {isShowCreatePostMenu && ( <div ref={createPostMenuRef}> <CreatePostMenu pageId={pageId} /> </div> - )} + )} */} <div className={styles.socialHomePage__tabs}> <NewsfeedButton pageId={pageId} From 43fbcd0d8bd3277edb5a1a426ecfeb814328c893 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Fri, 14 Jun 2024 20:07:31 +0700 Subject: [PATCH 127/300] fix: hyperlink confirm remove link (#406) --- .../HyperLinkConfig.module.css | 7 ++++++ .../HyperLinkConfig/HyperLinkConfig.tsx | 25 +++++++++---------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css index 6827ea9bb..ad2c8065f 100644 --- a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css +++ b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css @@ -145,3 +145,10 @@ background-color: var(--asc-color-base-shade4); margin: 1rem 0; } + +.removeLinkContainer { + margin-top: var(--asc-spacing-l1); + display: flex; + flex-direction: column; + border-bottom: 1px solid var(--asc-color-base-shade4); +} diff --git a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx index a89a79a27..2e16cc813 100644 --- a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx +++ b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx @@ -226,20 +226,19 @@ export const HyperLinkConfig = ({ </label> </Typography.Caption> </div> - {isHaveHyperLink && ( - <div className={styles.inputContainer}> - <Button - variant="secondary" - onClick={discardHyperlink} - className={clsx(styles.removeLinkButton)} - > - <Trash className={styles.removeIcon} /> - {formatMessage({ id: 'storyCreation.hyperlink.form.removeButton' })} - </Button> - <div className={styles.divider} /> - </div> - )} </form> + {isHaveHyperLink && ( + <div className={styles.removeLinkContainer}> + <Button + variant="secondary" + onClick={discardHyperlink} + className={clsx(styles.removeLinkButton)} + > + <Trash className={styles.removeIcon} /> + {formatMessage({ id: 'storyCreation.hyperlink.form.removeButton' })} + </Button> + </div> + )} </div> </BottomSheet> ); From 18444ad1c8f40480efe702f33d38216e36b8db7d Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Fri, 14 Jun 2024 20:34:36 +0700 Subject: [PATCH 128/300] fix: ASC-23219 - story view page onClose (#408) * fix: story view page onClose * fix: to v4 * fix: removed deprecated * fix: import --- .../core/providers/PageBehaviorProvider.tsx | 5 +- src/v4/social/constants/memberRoles.ts | 7 + src/v4/social/constants/permissions.ts | 38 ++++ src/v4/social/constants/reactions.ts | 3 + .../StoryViewer/Renderers/Image.tsx | 50 +++-- .../StoryViewer/Renderers/Video.tsx | 86 ++++--- src/v4/social/utils/index.ts | 212 ++++++++++++++++++ src/v4/utils/permissions.ts | 47 ++++ 8 files changed, 377 insertions(+), 71 deletions(-) create mode 100644 src/v4/social/constants/memberRoles.ts create mode 100644 src/v4/social/constants/permissions.ts create mode 100644 src/v4/social/constants/reactions.ts create mode 100644 src/v4/social/utils/index.ts create mode 100644 src/v4/utils/permissions.ts diff --git a/src/v4/core/providers/PageBehaviorProvider.tsx b/src/v4/core/providers/PageBehaviorProvider.tsx index 34baedf0a..23ecf9f64 100644 --- a/src/v4/core/providers/PageBehaviorProvider.tsx +++ b/src/v4/core/providers/PageBehaviorProvider.tsx @@ -1,5 +1,5 @@ import React, { useContext } from 'react'; -import { useNavigation } from '~/v4/core/providers/NavigationProvider'; +import { PageTypes, useNavigation } from '~/v4/core/providers/NavigationProvider'; export interface PageBehavior { AmityStoryViewPageBehavior: { @@ -51,6 +51,7 @@ export const PageBehaviorProvider: React.FC<PageBehaviorProviderProps> = ({ goToCommunityProfilePage, goToUserProfilePage, goToViewStoryPage, + onChangePage, goToSelectPostTargetPage, } = useNavigation(); const navigationBehavior: PageBehavior = { @@ -59,7 +60,7 @@ export const PageBehaviorProvider: React.FC<PageBehaviorProviderProps> = ({ if (pageBehavior?.AmityStoryViewPageBehavior?.onCloseAction) { return pageBehavior.AmityStoryViewPageBehavior.onCloseAction(); } - onBack(); + onChangePage(PageTypes.SocialHomePage); }, hyperLinkAction: (context: Record<string, unknown>) => { if (pageBehavior?.AmityStoryViewPageBehavior?.hyperLinkAction) { diff --git a/src/v4/social/constants/memberRoles.ts b/src/v4/social/constants/memberRoles.ts new file mode 100644 index 000000000..3fe2760e6 --- /dev/null +++ b/src/v4/social/constants/memberRoles.ts @@ -0,0 +1,7 @@ +export enum MemberRoles { + MEMBER = 'member', + MODERATOR = 'moderator', + SUPER_MODERATOR = 'super-moderator', + COMMUNITY_MODERATOR = 'community-moderator', + CHANNEL_MODERATOR = 'channel-moderator', +} diff --git a/src/v4/social/constants/permissions.ts b/src/v4/social/constants/permissions.ts new file mode 100644 index 000000000..9edb4f26c --- /dev/null +++ b/src/v4/social/constants/permissions.ts @@ -0,0 +1,38 @@ +export enum Permissions { + EditUserPermission = 'EDIT_USER', + BanUserPermission = 'BAN_USER', + CreateRolePermission = 'CREATE_ROLE', + EditRolePermission = 'EDIT_ROLE', + DeleteRolePermission = 'DELETE_ROLE', + AssignRolePermission = 'ASSIGN_USER_ROLE', + EditChannelPermission = 'EDIT_CHANNEL', + EditChannelRatelimitPermission = 'EDIT_CHANNEL_RATELIMIT', + MuteChannelPermission = 'MUTE_CHANNEL', + CloseChannelPermission = 'CLOSE_CHANNEL', + AddChannelUserPermission = 'ADD_CHANNEL_USER', + EditChannelUserPermission = 'EDIT_CHANNEL_USER', + RemoveChannelUserPermission = 'REMOVE_CHANNEL_USER', + MuteChannelUserPermission = 'MUTE_CHANNEL_USER', + BanChannelUserPermission = 'BAN_CHANNEL_USER', + EditMessagePermission = 'EDIT_MESSAGE', + DeleteMessagePermission = 'DELETE_MESSAGE', + EditCommunityPermission = 'EDIT_COMMUNITY', + DeleteCommunityPermission = 'DELETE_COMMUNITY', + AddChannelCommunityPermission = 'ADD_COMMUNITY_USER', + EditChannelCommunityPermission = 'EDIT_COMMUNITY_USER', + RemoveChannelCommunityPermission = 'REMOVE_COMMUNITY_USER', + MuteChannelCommunityPermission = 'MUTE_COMMUNITY_USER', + BanChannelCommunityPermission = 'BAN_COMMUNITY_USER', + EditUserFeedPostPermission = 'EDIT_USER_FEED_POST', + DeleteUserFeedPostPermission = 'DELETE_USER_FEED_POST', + EditUserFeedCommentPermission = 'EDIT_USER_FEED_COMMENT', + DeleteUserFeedCommentPermission = 'DELETE_USER_FEED_COMMENT', + EditCommunityFeedPostPermission = 'EDIT_COMMUNITY_FEED_POST', + DeleteCommunityFeedPostPermission = 'DELETE_COMMUNITY_FEED_POST', + EditCommunityFeedCommentPermission = 'EDIT_COMMUNITY_FEED_COMMENT', + DeleteCommunityFeedCommentPermission = 'DELETE_COMMUNITY_FEED_COMMENT', + CreateCommunityCategoryPermission = 'CREATE_COMMUNITY_CATEGORY', + EditCommunityCategoryPermission = 'EDIT_COMMUNITY_CATEGORY', + DeleteCommunityCategoryPermission = 'DELETE_COMMUNITY_CATEGORY', + ManageStoryPermission = 'MANAGE_COMMUNITY_STORY', +} diff --git a/src/v4/social/constants/reactions.ts b/src/v4/social/constants/reactions.ts new file mode 100644 index 000000000..893647a0f --- /dev/null +++ b/src/v4/social/constants/reactions.ts @@ -0,0 +1,3 @@ +export const LIKE_REACTION_KEY = 'like'; +export const LOVE_REACTION_KEY = 'love'; +export const FIRE_REACTION_KEY = 'fire'; diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index d12a8858e..c091f3f10 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -2,35 +2,39 @@ import React, { useState, useEffect } from 'react'; import { Tester } from 'react-insta-stories/dist/interfaces'; import styles from './Renderers.module.css'; -import { useNavigation } from '~/social/providers/NavigationProvider'; -import useImage from '~/core/hooks/useImage'; -import { checkStoryPermission, formatTimeAgo } from '~/utils'; - -import useSDK from '~/core/hooks/useSDK'; import { useIntl } from 'react-intl'; -import { LIKE_REACTION_KEY } from '~/constants'; import Truncate from 'react-truncate-markup'; -import { CustomRenderer } from './types'; +import { CustomRenderer } from '~/v4/social/internal-components/StoryViewer/Renderers/types'; import { CommentTray } from '~/v4/social/components'; import { HyperLink } from '~/v4/social/elements/HyperLink'; -import Footer from './Wrappers/Footer'; -import Header from './Wrappers/Header'; -import { PageTypes } from '~/social/constants'; +import Footer from '~/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer'; +import Header from '~/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header'; import { motion, PanInfo, useAnimationControls } from 'framer-motion'; -import useUser from '~/core/hooks/useUser'; + import { BottomSheet } from '~/v4/core/components/BottomSheet'; import { Typography } from '~/v4/core/components'; import { Button } from '~/v4/core/components/Button'; -import { isAdmin, isModerator } from '~/helpers/permissions'; + import useCommunityMembersCollection from '~/v4/social/hooks/collections/useCommunityMembersCollection'; +import { PageTypes, useNavigation } from '~/v4/core/providers/NavigationProvider'; + +import useSDK from '~/v4/core/hooks/useSDK'; +import useImage from '~/v4/core/hooks/useImage'; +import useUser from '~/v4/core/hooks/objects/useUser'; + import useCommunityStoriesSubscription from '~/v4/social/hooks/useCommunityStoriesSubscription'; +import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; +import { LIKE_REACTION_KEY } from '~/v4/social/constants/reactions'; +import { isAdmin, isModerator } from '~/v4/utils/permissions'; +import { checkStoryPermission, formatTimeAgo } from '~/v4/social/utils'; export const renderer: CustomRenderer = ({ story, action, config }) => { const { formatMessage } = useIntl(); + const { AmityStoryViewPageBehavior } = usePageBehavior(); const { page, onChangePage, onClickCommunity } = useNavigation(); const [loaded, setLoaded] = useState(false); const [isOpenBottomSheet, setIsOpenBottomSheet] = useState(false); @@ -66,7 +70,7 @@ export const renderer: CustomRenderer = ({ story, action, config }) => { imageSize: 'small', }); - const user = useUser(client?.userId); + const { user } = useUser(client?.userId); const heading = <div data-qa-anchor="community_display_name">{community?.displayName}</div>; const subheading = @@ -118,8 +122,8 @@ export const renderer: CustomRenderer = ({ story, action, config }) => { transition: { duration: 0.3, ease: 'easeOut' }, }) .then(() => { - if (page.type === PageTypes.ViewStory && page.storyType === 'globalFeed') { - onChangePage(PageTypes.NewsFeed); + if (page.type === PageTypes.ViewStoryPage && page.context.storyType === 'globalFeed') { + onChangePage(PageTypes.SocialHomePage); } else { onClickCommunity(community?.communityId as string); } @@ -140,6 +144,14 @@ export const renderer: CustomRenderer = ({ story, action, config }) => { } }; + const handleOnClose = () => { + if (page.type === PageTypes.ViewStoryPage && page.context.storyType === 'globalFeed') { + AmityStoryViewPageBehavior.onCloseAction(); + return; + } + onClickCommunity(community?.communityId as string); + }; + useEffect(() => { if (isPaused || isOpenBottomSheet || isOpenCommentSheet) { action('pause', true); @@ -199,13 +211,7 @@ export const renderer: CustomRenderer = ({ story, action, config }) => { onAction={openBottomSheet} onAddStory={handleAddIconClick} onClickCommunity={() => onClickCommunity(community?.communityId as string)} - onClose={() => { - if (page.type === PageTypes.ViewStory && page.storyType === 'globalFeed') { - onChangePage(PageTypes.NewsFeed); - return; - } - onClickCommunity(community?.communityId as string); - }} + onClose={handleOnClose} addStoryButton={addStoryButton} /> diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx index b88aeb500..f00b81f0e 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx @@ -1,44 +1,49 @@ import React, { useEffect, useRef, useState } from 'react'; import { Tester } from 'react-insta-stories/dist/interfaces'; -import useImage from '~/core/hooks/useImage'; -import { checkStoryPermission, formatTimeAgo } from '~/utils'; -import { useNavigation } from '~/social/providers/NavigationProvider'; import { useIntl } from 'react-intl'; -import useSDK from '~/core/hooks/useSDK'; - -import { LIKE_REACTION_KEY } from '~/constants'; import Truncate from 'react-truncate-markup'; -import { CustomRenderer } from './types'; -import { LoadingOverlay, StoryVideo } from './styles'; +import { CustomRenderer } from '~/v4/social/internal-components/StoryViewer/Renderers/types'; import { SpeakerButton } from '~/v4/social/elements'; -import Header from './Wrappers/Header'; + import { BottomSheet, Button, Typography } from '~/v4/core/components'; import { CommentTray } from '~/v4/social/components'; import { HyperLink } from '~/v4/social/elements/HyperLink'; -import Footer from './Wrappers/Footer'; -import { PageTypes } from '~/social/constants'; +import Header from '~/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header'; +import Footer from '~/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer'; + import { motion, PanInfo, useAnimationControls } from 'framer-motion'; -import rendererStyles from './Renderers.module.css'; -import useUser from '~/core/hooks/useUser'; -import { isAdmin, isModerator } from '~/helpers/permissions'; import useCommunityMembersCollection from '~/v4/social/hooks/collections/useCommunityMembersCollection'; +import { PageTypes, useNavigation } from '~/v4/core/providers/NavigationProvider'; + +import useSDK from '~/v4/core/hooks/useSDK'; +import useImage from '~/v4/core/hooks/useImage'; +import useUser from '~/v4/core/hooks/objects/useUser'; + +import clsx from 'clsx'; + +import rendererStyles from './Renderers.module.css'; import useCommunityStoriesSubscription from '~/v4/social/hooks/useCommunityStoriesSubscription'; +import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; +import { LIKE_REACTION_KEY } from '~/v4/social/constants/reactions'; +import { checkStoryPermission, formatTimeAgo, isAdmin } from '~/v4/social/utils'; +import { isModerator } from '~/v4/utils/permissions'; export const renderer: CustomRenderer = ({ story, action, config, messageHandler }) => { + const { AmityStoryViewPageBehavior } = usePageBehavior(); const { formatMessage } = useIntl(); - const { page, onClickCommunity, onChangePage } = useNavigation(); + const { page, onClickCommunity } = useNavigation(); const [loaded, setLoaded] = useState(false); const [muted, setMuted] = useState(false); const [isPaused, setIsPaused] = useState(false); const [isOpenBottomSheet, setIsOpenBottomSheet] = useState(false); const [isOpenCommentSheet, setIsOpenCommentSheet] = useState(false); - const { width, height, loader, storyStyles } = config; + const { loader } = config; const { client } = useSDK(); - const user = useUser(client?.userId); + const { user } = useUser(client?.userId); const isLiked = !!(story && story.myReactions && story.myReactions.includes(LIKE_REACTION_KEY)); const totalLikes = story?.reactions[LIKE_REACTION_KEY] || 0; @@ -84,11 +89,6 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler const haveStoryPermission = isGlobalAdmin || isCommunityModerator || checkStoryPermission(client, community?.communityId); - const computedStyles = { - ...storyContentStyles, - ...(storyStyles || {}), - }; - const vid = useRef<HTMLVideoElement>(null); const controls = useAnimationControls(); @@ -140,8 +140,8 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler transition: { duration: 0.3, ease: 'easeOut' }, }) .then(() => { - if (page.type === PageTypes.ViewStory && page.storyType === 'globalFeed') { - onChangePage(PageTypes.NewsFeed); + if (page.type === PageTypes.ViewStoryPage && page.context.storyType === 'globalFeed') { + AmityStoryViewPageBehavior.onCloseAction(); } else { onClickCommunity(community?.communityId as string); } @@ -164,6 +164,14 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler } }; + const handleOnClose = () => { + if (page.type === PageTypes.ViewStoryPage && page.context.storyType === 'globalFeed') { + AmityStoryViewPageBehavior.onCloseAction(); + return; + } + onClickCommunity(community?.communityId as string); + }; + useEffect(() => { if (vid.current) { if (isPaused || isOpenBottomSheet || isOpenCommentSheet) { @@ -205,7 +213,7 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler return ( <motion.div - className={rendererStyles.rendererContainer} + className={clsx(rendererStyles.rendererContainer)} animate={controls} drag="y" dragConstraints={{ top: 0, bottom: 0 }} @@ -235,19 +243,13 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler onAction={openBottomSheet} onAddStory={handleAddIconClick} onClickCommunity={() => onClickCommunity(community?.communityId as string)} - onClose={() => { - if (page.type === PageTypes.ViewStory && page.storyType === 'globalFeed') { - onChangePage(PageTypes.NewsFeed); - return; - } - onClickCommunity(community?.communityId as string); - }} + onClose={handleOnClose} addStoryButton={addStoryButton} /> - <StoryVideo + <video data-qa-anchor="video_view" ref={vid} - style={computedStyles} + className={clsx(rendererStyles.storyVideo)} src={story?.videoData?.fileUrl || story?.videoData?.videoUrl?.original} controls={false} onLoadedData={videoLoaded} @@ -259,9 +261,7 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler webkit-playsinline="true" /> {!loaded && ( - <LoadingOverlay width={width} height={height}> - {loader || <div>loading...</div>} - </LoadingOverlay> + <div className={clsx(rendererStyles.loadingOverlay)}>{loader || <div>loading...</div>}</div> )} <BottomSheet @@ -273,7 +273,7 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler > {actions?.map((bottomSheetAction) => ( <Button - className={rendererStyles.actionButton} + className={clsx(rendererStyles.actionButton)} onClick={() => { bottomSheetAction.action(); }} @@ -302,7 +302,7 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler /> </BottomSheet> {story.items?.[0]?.data?.url && ( - <div className={rendererStyles.hyperLinkContainer}> + <div className={clsx(rendererStyles.hyperLinkContainer)}> <HyperLink href={ story.items[0].data.url.startsWith('http') @@ -337,14 +337,6 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler ); }; -const storyContentStyles = { - width: 'auto', - maxWidth: '100%', - maxHeight: '100%', - margin: 'auto', - position: 'relative' as const, -}; - export const tester: Tester = (story) => { return { condition: story.type === 'video', diff --git a/src/v4/social/utils/index.ts b/src/v4/social/utils/index.ts new file mode 100644 index 000000000..0aadd9aa5 --- /dev/null +++ b/src/v4/social/utils/index.ts @@ -0,0 +1,212 @@ +// // TODO: refactor to align with SDK roles once available. + +import { isCommunityMember, isCommunityPost, isPostUnderReview } from '~/v4/helpers/utils'; +import { MemberRoles } from '~/v4/social/constants/memberRoles'; +import { Permissions } from '~/v4/social/constants/permissions'; + +const ADMIN = 'global-admin'; +const { COMMUNITY_MODERATOR, CHANNEL_MODERATOR, MODERATOR, SUPER_MODERATOR } = MemberRoles; + +export const isModerator = (userRoles?: string[]) => { + if (!userRoles?.length) { + return false; + } + + const roles: string[] = [COMMUNITY_MODERATOR, CHANNEL_MODERATOR, MODERATOR, SUPER_MODERATOR]; + + return userRoles.some((role) => roles.includes(role)); +}; + +export const isAdmin = (userRoles?: string[]) => { + if (!userRoles?.length) { + return false; + } + + return userRoles.includes(ADMIN); +}; + +/** + * + * @deprecated + */ +function isPostModerator({ + user, + communityUser, + post, +}: { + user?: Pick<Amity.User, 'roles'>; + communityUser?: Amity.Membership<'community'>; + post?: Amity.Post; +}) { + const hasModeratorPermissions = + isAdmin(user?.roles) || isModerator(user?.roles) || isModerator(communityUser?.roles); + + if (isCommunityPost(post)) { + return hasModeratorPermissions && isCommunityMember(communityUser); + } + + return hasModeratorPermissions; +} + +/** + * + * @deprecated + */ +export function canEditCommunity({ + user, + communityUser, +}: { + user?: Pick<Amity.User, 'roles'>; + communityUser?: Amity.Membership<'community'>; +}) { + return isAdmin(user?.roles) || isModerator(user?.roles) || isModerator(communityUser?.roles); +} + +/** + * + * @deprecated + */ +export function canReviewCommunityPosts(data: { + user?: Pick<Amity.User, 'roles'>; + communityUser?: Amity.Membership<'community'>; +}) { + return canEditCommunity(data); +} + +/** + * + * @deprecated + */ +export function canDeletePost({ + userId, + user, + communityUser, + post, + community, +}: { + userId?: string; + user?: Pick<Amity.User, 'roles'>; + communityUser?: Amity.Membership<'community'>; + post?: Amity.Post; + community?: Amity.Community; +}) { + const isPostModer = isPostModerator({ user, communityUser, post }); + const isMyPost = post?.postedUserId === userId; + + if (isCommunityPost(post)) { + const isUnderReview = isPostUnderReview(post, community); + const isMember = isCommunityMember(communityUser); + + return (!isUnderReview && isPostModer) || (isMyPost && isMember); + } + + return isPostModer || isMyPost; +} + +/** + * + * @deprecated + */ +export function canEditPost({ + userId, + user, + communityUser, + post, + community, + childrenPosts = [], +}: { + userId?: string; + user?: Pick<Amity.User, 'roles'>; + communityUser?: Amity.Membership<'community'>; + post?: Amity.Post; + community?: Amity.Community; + childrenPosts?: Amity.Post[]; +}) { + if ( + childrenPosts.find( + (childPost) => childPost.dataType === 'liveStream' || childPost.dataType === 'poll', + ) + ) { + return false; + } + + const isPostModer = isPostModerator({ user, communityUser, post }); + const isMyPost = post?.postedUserId === userId; + + if (isCommunityPost(post)) { + if (isPostUnderReview(post, community)) { + return false; + } + + return isPostModer || (isMyPost && isCommunityMember(communityUser)); + } + + return isPostModer || isMyPost; +} + +/** + * + * @deprecated + */ +export function canReportPost({ + userId, + user, + communityUser, + post, + community, +}: { + userId?: string; + user?: Pick<Amity.User, 'roles'>; + communityUser?: Amity.Membership<'community'>; + post?: Amity.Post; + community?: Amity.Community; +}) { + const isPostModer = isPostModerator({ user, communityUser, post }); + const isMyPost = post?.postedUserId === userId; + + if (isCommunityPost(post)) { + if (isPostUnderReview(post, community)) { + return false; + } + + return isMyPost === false && (isPostModer || isCommunityMember(communityUser)); + } + + return isMyPost === false; +} + +export const checkStoryPermission = ( + client: Amity.Client | null | undefined, + communityId?: string, +): boolean => { + if (!client) { + return false; + } + + if (communityId) { + const communityPermission = client + .hasPermission(Permissions.ManageStoryPermission) + .community(communityId); + return communityPermission; + } + + return false; +}; + +export function formatTimeAgo(dateString: string | Date | undefined) { + if (!dateString) return; + const givenDate = new Date(dateString); + const currentDate = new Date(); + + const timeDifferenceInSeconds = Math.floor((currentDate.getTime() - givenDate.getTime()) / 1000); + + if (timeDifferenceInSeconds < 60) { + return 'Just now'; + } else if (timeDifferenceInSeconds >= 60 && timeDifferenceInSeconds < 3600) { + const minutes = Math.floor(timeDifferenceInSeconds / 60); + return `${minutes}m`; + } else { + const hours = Math.floor(timeDifferenceInSeconds / 3600); + return `${hours}h`; + } +} diff --git a/src/v4/utils/permissions.ts b/src/v4/utils/permissions.ts new file mode 100644 index 000000000..f4f141e07 --- /dev/null +++ b/src/v4/utils/permissions.ts @@ -0,0 +1,47 @@ +// // TODO: refactor to align with SDK roles once available. +import { MemberRoles } from '~/social/constants'; +import { isCommunityMember, isCommunityPost, isPostUnderReview } from '~/helpers/utils'; + +const ADMIN = 'global-admin'; +const { COMMUNITY_MODERATOR, CHANNEL_MODERATOR, MODERATOR, SUPER_MODERATOR } = MemberRoles; + +export const isModerator = (userRoles?: string[]) => { + if (!userRoles?.length) { + return false; + } + + const roles: string[] = [COMMUNITY_MODERATOR, CHANNEL_MODERATOR, MODERATOR, SUPER_MODERATOR]; + + return userRoles.some((role) => roles.includes(role)); +}; + +export const isAdmin = (userRoles?: string[]) => { + if (!userRoles?.length) { + return false; + } + + return userRoles.includes(ADMIN); +}; + +/** + * + * @deprecated + */ +function isPostModerator({ + user, + communityUser, + post, +}: { + user?: Pick<Amity.User, 'roles'>; + communityUser?: Amity.Membership<'community'>; + post?: Amity.Post; +}) { + const hasModeratorPermissions = + isAdmin(user?.roles) || isModerator(user?.roles) || isModerator(communityUser?.roles); + + if (isCommunityPost(post)) { + return hasModeratorPermissions && isCommunityMember(communityUser); + } + + return hasModeratorPermissions; +} From 3881e122539cfe87711ffb8e9e117543703883d7 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Mon, 17 Jun 2024 12:21:46 +0700 Subject: [PATCH 129/300] fix: ASC-20522 - navigate view story in mobile overlay (#409) * fix: story view page onClose * fix: to v4 * fix: overlay bug * fix: remove console.log * fix: remove styled component * fix: add story arrow left right to customization provider * fix: remove unused * fix: import * fix: import * fix: global story hook import --- .../core/providers/CustomizationProvider.tsx | 8 ++ .../ArrowLeftButton.module.css | 26 ++++++ .../ArrowLeftButton/ArrowLeftButton.tsx | 41 ++++++++++ .../social/elements/ArrowLeftButton/index.ts | 1 + .../ArrowRightButton.module.css | 26 ++++++ .../ArrowRightButton/ArrowRightButton.tsx | 42 ++++++++++ .../social/elements/ArrowRightButton/index.ts | 1 + .../collections/useGlobalStoryTargets.tsx | 2 +- .../Renderers/Renderers.module.css | 16 ++++ .../StoryViewer/Renderers/Video.tsx | 4 +- src/v4/social/pages/DraftsPage/DraftsPage.tsx | 2 +- .../pages/StoryPage/CommunityFeedStory.tsx | 2 - .../pages/StoryPage/GlobalFeedStory.tsx | 80 +++++++++---------- .../pages/StoryPage/StoryPage.module.css | 56 +++++++++++++ 14 files changed, 258 insertions(+), 49 deletions(-) create mode 100644 src/v4/social/elements/ArrowLeftButton/ArrowLeftButton.module.css create mode 100644 src/v4/social/elements/ArrowLeftButton/ArrowLeftButton.tsx create mode 100644 src/v4/social/elements/ArrowLeftButton/index.ts create mode 100644 src/v4/social/elements/ArrowRightButton/ArrowRightButton.module.css create mode 100644 src/v4/social/elements/ArrowRightButton/ArrowRightButton.tsx create mode 100644 src/v4/social/elements/ArrowRightButton/index.ts create mode 100644 src/v4/social/pages/StoryPage/StoryPage.module.css diff --git a/src/v4/core/providers/CustomizationProvider.tsx b/src/v4/core/providers/CustomizationProvider.tsx index e06fed883..d6522cb11 100644 --- a/src/v4/core/providers/CustomizationProvider.tsx +++ b/src/v4/core/providers/CustomizationProvider.tsx @@ -202,6 +202,14 @@ export const defaultConfig: DefaultConfig = { unmute_icon: 'unmute.png', background_color: '#1243EE', }, + 'story_page/*/arrow_left_button': { + arrow_left_icon: 'arrow_left.png', + background_color: '#1243EE', + }, + 'story_page/*/arrow_right_button': { + arrow_right_icon: 'arrow_right.png', + background_color: '#1243EE', + }, '*/edit_comment_component/*': { theme: {}, }, diff --git a/src/v4/social/elements/ArrowLeftButton/ArrowLeftButton.module.css b/src/v4/social/elements/ArrowLeftButton/ArrowLeftButton.module.css new file mode 100644 index 000000000..0072c4005 --- /dev/null +++ b/src/v4/social/elements/ArrowLeftButton/ArrowLeftButton.module.css @@ -0,0 +1,26 @@ +.storyButton { + background: none; + border: none; + padding: 0; + cursor: pointer; + outline: none; + display: flex; + align-items: center; + justify-content: center; +} + +.storyButton svg { + width: 2rem; + height: 1.75rem; + fill: var(--asc-color-base-inverse); +} + +.desktopOnly { + display: none; +} + +@media screen and (width >= 768px) { + .desktopOnly { + display: flex; + } +} diff --git a/src/v4/social/elements/ArrowLeftButton/ArrowLeftButton.tsx b/src/v4/social/elements/ArrowLeftButton/ArrowLeftButton.tsx new file mode 100644 index 000000000..24fdf8268 --- /dev/null +++ b/src/v4/social/elements/ArrowLeftButton/ArrowLeftButton.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import clsx from 'clsx'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import styles from './ArrowLeftButton.module.css'; + +interface ArrowLeftButtonProps { + pageId?: string; + componentId?: string; + onClick?: (e: React.MouseEvent) => void; +} + +const ArrowLeftButtonSvg = (props: React.SVGProps<SVGSVGElement>) => ( + <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" {...props}> + <path d="M12 23.91C5.578 23.91.375 18.709.375 12.287.375 5.864 5.578.66 12 .66s11.625 5.203 11.625 11.625S18.422 23.91 12 23.91zM6.656 13.084l6.328 6.375c.47.422 1.172.422 1.594 0l.797-.797c.422-.422.422-1.172 0-1.594l-4.781-4.781 4.781-4.735c.422-.468.422-1.171 0-1.593l-.797-.797c-.422-.422-1.172-.422-1.594 0l-6.328 6.328c-.468.469-.468 1.172 0 1.594z"></path> + </svg> +); +export const ArrowLeftButton = ({ + pageId = '*', + componentId = '*', + onClick, +}: ArrowLeftButtonProps) => { + const elementId = 'arrow_left_button'; + const { accessibilityId, isExcluded, themeStyles } = useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + return ( + <button + data-qa-anchor={accessibilityId} + className={clsx(styles.storyButton, styles.desktopOnly)} + onClick={onClick} + style={themeStyles} + > + <ArrowLeftButtonSvg /> + </button> + ); +}; diff --git a/src/v4/social/elements/ArrowLeftButton/index.ts b/src/v4/social/elements/ArrowLeftButton/index.ts new file mode 100644 index 000000000..e9c113fb4 --- /dev/null +++ b/src/v4/social/elements/ArrowLeftButton/index.ts @@ -0,0 +1 @@ +export { ArrowLeftButton } from './ArrowLeftButton'; diff --git a/src/v4/social/elements/ArrowRightButton/ArrowRightButton.module.css b/src/v4/social/elements/ArrowRightButton/ArrowRightButton.module.css new file mode 100644 index 000000000..0072c4005 --- /dev/null +++ b/src/v4/social/elements/ArrowRightButton/ArrowRightButton.module.css @@ -0,0 +1,26 @@ +.storyButton { + background: none; + border: none; + padding: 0; + cursor: pointer; + outline: none; + display: flex; + align-items: center; + justify-content: center; +} + +.storyButton svg { + width: 2rem; + height: 1.75rem; + fill: var(--asc-color-base-inverse); +} + +.desktopOnly { + display: none; +} + +@media screen and (width >= 768px) { + .desktopOnly { + display: flex; + } +} diff --git a/src/v4/social/elements/ArrowRightButton/ArrowRightButton.tsx b/src/v4/social/elements/ArrowRightButton/ArrowRightButton.tsx new file mode 100644 index 000000000..cb3baeb3d --- /dev/null +++ b/src/v4/social/elements/ArrowRightButton/ArrowRightButton.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import clsx from 'clsx'; + +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import styles from './ArrowRightButton.module.css'; + +interface ArrowRightButtonProps { + pageId?: string; + componentId?: string; + onClick?: (e: React.MouseEvent) => void; +} + +const ArrowRightButtonSvg = (props: React.SVGProps<SVGSVGElement>) => ( + <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" {...props}> + <path d="M12 .66c6.422 0 11.625 5.204 11.625 11.626S18.422 23.91 12 23.91.375 18.708.375 12.286 5.578.66 12 .66zm5.297 10.829L10.969 5.16c-.422-.422-1.172-.422-1.594 0l-.797.797c-.422.422-.422 1.172 0 1.593l4.781 4.735-4.78 4.781c-.423.422-.423 1.172 0 1.594l.796.797c.422.422 1.172.422 1.594 0l6.328-6.375c.469-.422.469-1.125 0-1.594z"></path> + </svg> +); +export const ArrowRightButton = ({ + pageId = '*', + componentId = '*', + onClick, +}: ArrowRightButtonProps) => { + const elementId = 'arrow_right_button'; + const { accessibilityId, isExcluded, themeStyles } = useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + return ( + <button + data-qa-anchor={accessibilityId} + className={clsx(styles.storyButton, styles.desktopOnly)} + onClick={onClick} + style={themeStyles} + > + <ArrowRightButtonSvg /> + </button> + ); +}; diff --git a/src/v4/social/elements/ArrowRightButton/index.ts b/src/v4/social/elements/ArrowRightButton/index.ts new file mode 100644 index 000000000..12597df6c --- /dev/null +++ b/src/v4/social/elements/ArrowRightButton/index.ts @@ -0,0 +1 @@ +export { ArrowRightButton } from './ArrowRightButton'; diff --git a/src/v4/social/hooks/collections/useGlobalStoryTargets.tsx b/src/v4/social/hooks/collections/useGlobalStoryTargets.tsx index 127de2c3f..571a7b122 100644 --- a/src/v4/social/hooks/collections/useGlobalStoryTargets.tsx +++ b/src/v4/social/hooks/collections/useGlobalStoryTargets.tsx @@ -1,5 +1,5 @@ import { StoryRepository } from '@amityco/ts-sdk'; -import useLiveCollection from '~/core/hooks/useLiveCollection'; +import useLiveCollection from '~/v4/core/hooks/useLiveCollection'; export const useGlobalStoryTargets = ( params: Amity.LiveCollectionParams<Amity.StoryGlobalQuery>, diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css b/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css index 163456c74..04d323080 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css @@ -256,3 +256,19 @@ color: var(--asc-color-base-default); background-color: var(--asc-color-base-background); } + +.navigationOverlay { + position: absolute; + top: 0; + bottom: 0; + width: 50%; + z-index: 10; +} + +.leftOverlay { + left: 0; +} + +.rightOverlay { + right: 0; +} diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx index f00b81f0e..b53649369 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx @@ -18,7 +18,6 @@ import { motion, PanInfo, useAnimationControls } from 'framer-motion'; import useCommunityMembersCollection from '~/v4/social/hooks/collections/useCommunityMembersCollection'; import { PageTypes, useNavigation } from '~/v4/core/providers/NavigationProvider'; - import useSDK from '~/v4/core/hooks/useSDK'; import useImage from '~/v4/core/hooks/useImage'; import useUser from '~/v4/core/hooks/objects/useUser'; @@ -27,10 +26,11 @@ import clsx from 'clsx'; import rendererStyles from './Renderers.module.css'; import useCommunityStoriesSubscription from '~/v4/social/hooks/useCommunityStoriesSubscription'; -import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; + import { LIKE_REACTION_KEY } from '~/v4/social/constants/reactions'; import { checkStoryPermission, formatTimeAgo, isAdmin } from '~/v4/social/utils'; import { isModerator } from '~/v4/utils/permissions'; +import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; export const renderer: CustomRenderer = ({ story, action, config, messageHandler }) => { const { AmityStoryViewPageBehavior } = usePageBehavior(); diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index 2d1956613..1d4ff4754 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -6,7 +6,6 @@ import { readFileAsync } from '~/helpers'; import styles from './DraftsPage.module.css'; import { SubmitHandler } from 'react-hook-form'; -import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; import { AspectRatioButton, BackButton, @@ -24,6 +23,7 @@ import { useNavigation } from '~/social/providers/NavigationProvider'; import { PageTypes } from '~/social/constants'; import { BaseVideoPreview } from '../../internal-components/VideoPreview'; import { useCommunityInfo } from '~/social/components/CommunityInfo/hooks'; +import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; type AmityStoryMediaType = { type: 'image'; url: string } | { type: 'video'; url: string }; diff --git a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx index 6e719365e..bb6213379 100644 --- a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx @@ -221,8 +221,6 @@ export const CommunityFeedStory = ({ communityId }: CommunityFeedStoryProps) => setCurrentIndex(currentIndex + 1); }; - console.log(currentIndex); - useEffect(() => { if (stories[stories.length - 1]?.syncState === 'syncing') { setCurrentIndex(stories.length - 1); diff --git a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx index 8e98aadc9..ec099da86 100644 --- a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx @@ -1,36 +1,32 @@ import React, { useEffect, useRef, useState } from 'react'; -import useSDK from '~/core/hooks/useSDK'; -import { useMedia } from 'react-use'; import { useIntl } from 'react-intl'; import { FinalColor } from 'extract-colors/lib/types/Color'; import { StoryRepository } from '@amityco/ts-sdk'; -import { CreateStoryButton } from '../../elements'; -import { Trash2Icon } from '~/icons'; +import { CreateStoryButton } from '~/v4/social/elements'; + import { isNonNullable } from '~/v4/helpers/utils'; import { extractColors } from 'extract-colors'; -import { - HiddenInput, - StoryArrowLeftButton, - StoryArrowRightButton, - StoryWrapper, - ViewStoryContainer, - ViewStoryContent, - ViewStoryOverlay, -} from '../../internal-components/StoryViewer/styles'; - import Stories from 'react-insta-stories'; -import { renderers } from '../../internal-components/StoryViewer/Renderers'; -import { AmityDraftStoryPage } from '..'; -import { checkStoryPermission } from '~/utils'; -import { useStoryContext } from '../../providers/StoryProvider'; +import { renderers } from '~/v4/social/internal-components/StoryViewer/Renderers'; +import { AmityDraftStoryPage } from '~/v4/social/pages/DraftsPage'; + +import { useStoryContext } from '~/v4/social/providers/StoryProvider'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; import { useNotifications } from '~/v4/core/providers/NotificationProvider'; import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; import { PageTypes, useNavigation } from '~/v4/core/providers/NavigationProvider'; -import { useGetActiveStoriesByTarget } from '../../hooks/useGetActiveStories'; +import { useGetActiveStoriesByTarget } from '~/v4/social/hooks/useGetActiveStories'; +import clsx from 'clsx'; +import useSDK from '~/v4/core/hooks/useSDK'; + +import styles from './StoryPage.module.css'; +import { checkStoryPermission } from '~/v4/social/utils'; +import Trash from '~/v4/social/icons/trash'; +import { ArrowLeftButton } from '~/v4/social/elements/ArrowLeftButton'; +import { ArrowRightButton } from '~/v4/social/elements/ArrowRightButton'; const DURATION = 5000; @@ -49,8 +45,8 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = () => { targetId: page.type === PageTypes.ViewStoryPage && page.context.storyType === 'globalFeed' && - page.context.targetId - ? page.context.targetId + page.context?.targetId + ? page.context?.targetId : '', options: { orderBy: 'asc', @@ -79,7 +75,6 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = () => { const { client, currentUserId } = useSDK(); const { formatMessage } = useIntl(); - const isMobile = useMedia('(max-width: 768px)'); const [currentIndex, setCurrentIndex] = useState(0); const { file, setFile } = useStoryContext(); @@ -96,7 +91,7 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = () => { okText: formatMessage({ id: 'delete' }), onOk: async () => { previousStory(); - // if (isLastStory) onChangePage(PageTypes.SocialHomePage); + if (isLastStory) onChangePage(PageTypes.SocialHomePage); await StoryRepository.softDeleteStory(storyId); notification.success({ content: formatMessage({ id: 'storyViewer.notification.deleted' }), @@ -182,7 +177,7 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = () => { name: 'delete', action: () => deleteStory(story?.storyId as string), icon: ( - <Trash2Icon + <Trash fill={getComputedStyle(document.documentElement).getPropertyValue( '--asc-color-base-default', )} @@ -202,8 +197,8 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = () => { const nextStory = () => { if ( page.type === PageTypes.ViewStoryPage && - page.context.targetIds && - page.context.targetId && + page.context?.targetIds && + page.context?.targetId && currentIndex === formattedStories?.length - 1 ) { const currentTargetIndex = page.context.targetIds.indexOf(page.context.targetId); @@ -213,7 +208,7 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = () => { const nextTargetId = page.context.targetIds[nextTargetIndex]; onClickStory(nextTargetId, 'globalFeed', page.context.targetIds); } else { - onChangePage(PageTypes.NewsFeed); + onChangePage(PageTypes.SocialHomePage); } setCurrentIndex(0); return; @@ -235,7 +230,7 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = () => { const previousTargetId = page.context.targetIds[previousTargetIndex]; onClickStory(previousTargetId, 'globalFeed', page.context.targetIds); } else { - onChangePage(PageTypes.NewsFeed); + onChangePage(PageTypes.SocialHomePage); } setCurrentIndex(0); return; @@ -304,26 +299,27 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = () => { } return ( - <StoryWrapper data-qa-anchor="story_page"> - {!isMobile && ( - <StoryArrowLeftButton data-qa-anchor="arrow_left_button" onClick={previousStory} /> - )} - <ViewStoryContainer id={targetRootId}> - <HiddenInput + <div className={clsx(styles.storyWrapper)} data-qa-anchor="story_page"> + <ArrowLeftButton onClick={previousStory} /> + <div id={targetRootId} className={clsx(styles.viewStoryContainer)}> + <input + className={clsx(styles.hiddenInput)} ref={fileInputRef} type="file" accept="image/*,video/*" onChange={handleFileChange} /> - <ViewStoryContent> - <ViewStoryOverlay /> + <div className={clsx(styles.viewStoryContent)}> + <div className={clsx(styles.overlayLeft)} onClick={previousStory} /> + <div className={clsx(styles.overlayRight)} onClick={nextStory} /> + <div className={clsx(styles.viewStoryOverlay)} /> {formattedStories?.length > 0 ? ( // NOTE: Do not use isPaused prop, it will cause the first video story skipped <Stories width="100%" height="100%" storyStyles={storyStyles} - preventDefault={!isMobile} + preventDefault currentIndex={currentIndex} stories={formattedStories} // TO FIX: need to override custom type of renderers from react-insta-stories library @@ -337,11 +333,9 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = () => { onAllStoriesEnd={nextStory} /> ) : null} - </ViewStoryContent> - </ViewStoryContainer> - {!isMobile && ( - <StoryArrowRightButton data-qa-anchor="arrow_right_button" onClick={nextStory} /> - )} - </StoryWrapper> + </div> + </div> + <ArrowRightButton onClick={nextStory} /> + </div> ); }; diff --git a/src/v4/social/pages/StoryPage/StoryPage.module.css b/src/v4/social/pages/StoryPage/StoryPage.module.css new file mode 100644 index 000000000..ecfbe6c51 --- /dev/null +++ b/src/v4/social/pages/StoryPage/StoryPage.module.css @@ -0,0 +1,56 @@ +.overlayLeft { + position: absolute; + top: 0; + left: 0; + width: 50%; + height: 100%; + z-index: 9999; +} + +.overlayRight { + position: absolute; + top: 0; + right: 0; + width: 50%; + height: 100%; + z-index: 9999; +} + +.viewStoryContent { + position: relative; + width: 23.438rem; + height: 40.875rem; + display: flex; + flex-direction: column; + overflow: hidden; + z-index: 2; +} + +.viewStoryOverlay { + position: absolute; + width: 100%; + height: 100%; + background: linear-gradient(180deg, rgb(0 0 0 / 16%) 55.05%, rgb(255 255 255 / 0%) 96.52%); + z-index: 3; +} + +.hiddenInput { + display: none; +} + +.viewStoryContainer { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.storyWrapper { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + gap: 1rem; + overflow: hidden; +} From 0d343fc61cf062ad73271d1226fcf3b3709fb029 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Mon, 17 Jun 2024 12:28:47 +0700 Subject: [PATCH 130/300] fix: ASC-22720 - view story full width and height (#410) * fix: story view page onClose * fix: to v4 * fix: overlay bug * fix: remove console.log * fix: remove styled component * fix: add story arrow left right to customization provider * fix: view story full width and height --- .../internal-components/StoryViewer/Renderers/Image.tsx | 4 ++-- .../internal-components/StoryViewer/Renderers/Video.tsx | 3 ++- src/v4/social/pages/StoryPage/StoryPage.module.css | 6 ++++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index c091f3f10..0790120c3 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -29,8 +29,8 @@ import useUser from '~/v4/core/hooks/objects/useUser'; import useCommunityStoriesSubscription from '~/v4/social/hooks/useCommunityStoriesSubscription'; import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; import { LIKE_REACTION_KEY } from '~/v4/social/constants/reactions'; -import { isAdmin, isModerator } from '~/v4/utils/permissions'; -import { checkStoryPermission, formatTimeAgo } from '~/v4/social/utils'; + +import { checkStoryPermission, formatTimeAgo, isAdmin, isModerator } from '~/v4/social/utils'; export const renderer: CustomRenderer = ({ story, action, config }) => { const { formatMessage } = useIntl(); diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx index b53649369..fbea22f44 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx @@ -29,8 +29,9 @@ import useCommunityStoriesSubscription from '~/v4/social/hooks/useCommunityStori import { LIKE_REACTION_KEY } from '~/v4/social/constants/reactions'; import { checkStoryPermission, formatTimeAgo, isAdmin } from '~/v4/social/utils'; -import { isModerator } from '~/v4/utils/permissions'; + import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; +import { isModerator } from '~/v4/utils/permissions'; export const renderer: CustomRenderer = ({ story, action, config, messageHandler }) => { const { AmityStoryViewPageBehavior } = usePageBehavior(); diff --git a/src/v4/social/pages/StoryPage/StoryPage.module.css b/src/v4/social/pages/StoryPage/StoryPage.module.css index ecfbe6c51..490050738 100644 --- a/src/v4/social/pages/StoryPage/StoryPage.module.css +++ b/src/v4/social/pages/StoryPage/StoryPage.module.css @@ -18,8 +18,8 @@ .viewStoryContent { position: relative; - width: 23.438rem; - height: 40.875rem; + width: 100%; + height: 100%; display: flex; flex-direction: column; overflow: hidden; @@ -43,6 +43,8 @@ flex-direction: column; align-items: center; justify-content: center; + width: 100%; + height: 100%; } .storyWrapper { From 21205a97b7d7db0136045e9ae758231c417cbabe Mon Sep 17 00:00:00 2001 From: Kiattirat Sujjapongse <frankent@gmail.com> Date: Mon, 17 Jun 2024 13:18:36 +0700 Subject: [PATCH 131/300] feat(message): handle optimistic on message creation (#377) --- .../MessageReactionPreview/index.tsx | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/v4/chat/components/MessageReactionPreview/index.tsx b/src/v4/chat/components/MessageReactionPreview/index.tsx index fc5d22f88..e48d25642 100644 --- a/src/v4/chat/components/MessageReactionPreview/index.tsx +++ b/src/v4/chat/components/MessageReactionPreview/index.tsx @@ -13,23 +13,25 @@ export const MessageReactionPreview = ({ }) => { const { config: reactionConfig } = useCustomReaction(); // find the top 3 reactions - const topReactions = useMemo( - () => - Object.entries(message.reactions) - .sort((a, b) => b[1] - a[1]) // sort by value in descending order - // remove reaction that has zero value - .filter((reaction) => reaction[1] > 0) - .slice(0, 3) - .sort((a, b) => a[1] - b[1]), - [message.reactions], - ); + const topReactions = message.reactions + ? useMemo( + () => + Object.entries(message.reactions) + .sort((a, b) => b[1] - a[1]) // sort by value in descending order + // remove reaction that has zero value + .filter((reaction) => reaction[1] > 0) + .slice(0, 3) + .sort((a, b) => a[1] - b[1]), + [message?.reactions], + ) + : []; - if (!message.reactionsCount) return null; + if (!message?.reactionsCount) return null; return ( <div className={styles.reactionPreviewContainer} - data-myreaction={message.myReactions && !!message.myReactions.length} + data-myreaction={message?.myReactions && !!message?.myReactions.length} onClick={onClick} > <div className={styles.reactionIconContainer}> From 250a066425d88d85122d83ed26924eb1202fabfd Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Mon, 17 Jun 2024 14:23:17 +0700 Subject: [PATCH 132/300] fix: ASC-22081 - notification v4 (#411) * fix: story view page onClose * fix: to v4 * fix: overlay bug * fix: remove console.log * fix: remove styled component * fix: add story arrow left right to customization provider * fix: view story full width and height * fix: notification v4 * fix: file name --- .../Notification/Notification.module.css | 63 +++++++++++++++++++ src/v4/core/components/Notification/index.tsx | 4 +- .../components/Notification/styles.module.css | 44 ------------- .../providers/NotificationProvider.module.css | 41 +----------- .../StoryViewer/Renderers/Video.tsx | 9 +-- 5 files changed, 70 insertions(+), 91 deletions(-) create mode 100644 src/v4/core/components/Notification/Notification.module.css delete mode 100644 src/v4/core/components/Notification/styles.module.css diff --git a/src/v4/core/components/Notification/Notification.module.css b/src/v4/core/components/Notification/Notification.module.css new file mode 100644 index 000000000..fb9cde5ab --- /dev/null +++ b/src/v4/core/components/Notification/Notification.module.css @@ -0,0 +1,63 @@ +.icon__container { + display: flex; + padding: var(--asc-spacing-m1) var(--asc-spacing-s2); + align-items: center; + gap: var(--asc-spacing-s1); + align-self: stretch; +} + +.icon { + width: var(--asc-spacing-m2); + height: var(--asc-spacing-m2); +} + +.notifications { + position: fixed; + top: 0; + left: 0; + right: 0; + display: flex; + flex-direction: column; + align-items: center; + z-index: 99999; + pointer-events: none; + padding: var(--asc-spacing-m1); +} + +.notificationContainer { + width: 100%; + display: flex; + justify-content: flex-start; + align-items: center; + color: var(--asc-color-white); + border-radius: var(--asc-border-radius-md); + margin-bottom: var(--asc-spacing-m1); + animation-duration: 0.3s; + animation-name: appear; + pointer-events: auto; + background-color: var(--asc-color-secondary-default); + box-shadow: var(--asc-box-shadow-02); +} + +@keyframes appear { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +@media (width <= 768px) { + .notifications { + top: auto; + bottom: 0; + padding-bottom: var(--asc-spacing-l1); + } + + .notificationContainer { + width: 100%; + margin-bottom: var(--asc-spacing-none); + } +} diff --git a/src/v4/core/components/Notification/index.tsx b/src/v4/core/components/Notification/index.tsx index 748b487b5..b6913e9a9 100644 --- a/src/v4/core/components/Notification/index.tsx +++ b/src/v4/core/components/Notification/index.tsx @@ -1,6 +1,6 @@ import React, { ReactNode } from 'react'; import clsx from 'clsx'; -import styles from './styles.module.css'; +import styles from './Notification.module.css'; import { useNotificationData } from '~/v4/core/providers/NotificationProvider'; interface NotificationProps { @@ -11,7 +11,7 @@ interface NotificationProps { const Notification = ({ className, content, icon }: NotificationProps) => ( <div className={clsx(styles.notificationContainer, className)}> - {icon} {content} + <div className={clsx(styles.icon__container)}>{icon}</div> {content} </div> ); diff --git a/src/v4/core/components/Notification/styles.module.css b/src/v4/core/components/Notification/styles.module.css deleted file mode 100644 index 36dd28e5c..000000000 --- a/src/v4/core/components/Notification/styles.module.css +++ /dev/null @@ -1,44 +0,0 @@ -/* styles.module.css */ -.icon { - width: 1.125rem; - height: 1.125rem; - margin-right: 8px; -} - -.notifications { - position: fixed; - padding-top: 50px; - top: 0; - left: 0; - right: 0; - display: flex; - flex-direction: column; - align-items: center; - z-index: 99999; - pointer-events: none; -} - -.notificationContainer { - width: 480px; - padding: 8px 30px; - display: flex; - justify-content: center; - align-items: center; - color: var(--asc-color-white); - border-radius: 4px; - margin-bottom: 10px; - animation-duration: 0.3s; - animation-name: appear; - pointer-events: auto; - background-color: var(--asc-color-base-default); -} - -@keyframes appear { - from { - opacity: 0; - } - - to { - opacity: 1; - } -} diff --git a/src/v4/core/providers/NotificationProvider.module.css b/src/v4/core/providers/NotificationProvider.module.css index 0f8bf6de4..182cab8c1 100644 --- a/src/v4/core/providers/NotificationProvider.module.css +++ b/src/v4/core/providers/NotificationProvider.module.css @@ -1,44 +1,7 @@ .icon { width: 1.125rem; height: 1.125rem; - margin-right: 8px; + margin-right: var(--asc-spacing-s1); + margin-left: var(--asc-spacing-s1); fill: var(--asc-color-white); } - -.notifications { - position: fixed; - padding-top: 50px; - top: 0; - left: 0; - right: 0; - display: flex; - flex-direction: column; - align-items: center; - z-index: 99999; - pointer-events: none; -} - -.notificationContainer { - width: 480px; - padding: 8px 30px; - display: flex; - justify-content: center; - align-items: center; - color: white; - border-radius: 4px; - margin-bottom: 10px; - animation-duration: 0.3s; - animation-name: appear; - pointer-events: auto; - background-color: var(--asc-color-base-shade4); -} - -@keyframes appear { - from { - opacity: 0; - } - - to { - opacity: 1; - } -} diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx index fbea22f44..203a9bac2 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx @@ -18,21 +18,18 @@ import { motion, PanInfo, useAnimationControls } from 'framer-motion'; import useCommunityMembersCollection from '~/v4/social/hooks/collections/useCommunityMembersCollection'; import { PageTypes, useNavigation } from '~/v4/core/providers/NavigationProvider'; +import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; import useSDK from '~/v4/core/hooks/useSDK'; import useImage from '~/v4/core/hooks/useImage'; import useUser from '~/v4/core/hooks/objects/useUser'; import clsx from 'clsx'; +import { LIKE_REACTION_KEY } from '~/v4/social/constants/reactions'; +import { checkStoryPermission, formatTimeAgo, isAdmin, isModerator } from '~/v4/social/utils'; import rendererStyles from './Renderers.module.css'; import useCommunityStoriesSubscription from '~/v4/social/hooks/useCommunityStoriesSubscription'; -import { LIKE_REACTION_KEY } from '~/v4/social/constants/reactions'; -import { checkStoryPermission, formatTimeAgo, isAdmin } from '~/v4/social/utils'; - -import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; -import { isModerator } from '~/v4/utils/permissions'; - export const renderer: CustomRenderer = ({ story, action, config, messageHandler }) => { const { AmityStoryViewPageBehavior } = usePageBehavior(); const { formatMessage } = useIntl(); From e57374c15ad29f0fececfc6519e5029575d872b5 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Mon, 17 Jun 2024 14:32:13 +0700 Subject: [PATCH 133/300] fix: ASC-20521 - delete first multiple story go to next story (#412) * fix: story view page onClose * fix: to v4 * fix: overlay bug * fix: remove console.log * fix: remove styled component * fix: add story arrow left right to customization provider * fix: view story full width and height * fix: notification v4 * fix: delete first multiple stories go to next story --- src/v4/social/pages/StoryPage/CommunityFeedStory.tsx | 9 ++++++--- src/v4/social/pages/StoryPage/GlobalFeedStory.tsx | 5 +++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx index bb6213379..fbedc7291 100644 --- a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx @@ -85,18 +85,21 @@ export const CommunityFeedStory = ({ communityId }: CommunityFeedStoryProps) => const isModerator = checkStoryPermission(client, communityId); const confirmDeleteStory = (storyId: string) => { - const isLastStory = currentIndex === 0; + const isLastStory = currentIndex === stories.length - 1; confirm({ title: formatMessage({ id: 'storyViewer.action.confirmModal.title' }), content: formatMessage({ id: 'storyViewer.action.confirmModal.content' }), okText: formatMessage({ id: 'delete' }), onOk: async () => { - previousStory(); - if (isLastStory) onBack(); await StoryRepository.softDeleteStory(storyId); notification.success({ content: formatMessage({ id: 'storyViewer.notification.deleted' }), }); + if (isLastStory && stories.length > 1) { + setCurrentIndex(currentIndex - 1); + } else if (stories.length === 1) { + onBack(); + } }, }); }; diff --git a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx index ec099da86..1e1f1096a 100644 --- a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx @@ -96,6 +96,11 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = () => { notification.success({ content: formatMessage({ id: 'storyViewer.notification.deleted' }), }); + if (isLastStory && stories.length > 1) { + setCurrentIndex(currentIndex - 1); + } else if (stories.length === 1) { + onChangePage(PageTypes.SocialHomePage); + } }, }); }; From d7ee437f7d210716be679fb974d59d1cb1851fa3 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Mon, 17 Jun 2024 19:13:50 +0700 Subject: [PATCH 134/300] fix: ASC-23136 - fix social v3 issues (#388) * fix: bring back reply i18n i18n * fix: icon size * fix: comments do not load * chore: story in v3 * fix: isFlaggedByMe rate limit issue * fix: fix PageBehavior * fix: ViewStoryPage navigation * fix: remove duplicate * fix: import * fix: draft page prop * fix: draft story page storyType prop --------- Co-authored-by: Chaiwat Trisuwan <chaiwattsw@gmail.com> --- src/core/components/Dropdown/index.tsx | 50 ++- src/core/components/Uploaders/File/styles.tsx | 2 +- .../components/Uploaders/Image/styles.tsx | 4 +- .../components/Uploaders/Video/styles.tsx | 4 +- src/core/providers/UiKitProvider/index.tsx | 66 ++-- src/i18n/en.json | 2 +- .../components/Comment/StyledComment.tsx | 216 +++++++---- src/social/components/Comment/index.tsx | 29 -- src/social/components/Comment/styles.tsx | 13 + src/social/components/CommentList/index.tsx | 2 +- .../CommunityInfo/UICommunityInfo.tsx | 2 +- .../EngagementBar/UIEngagementBar.tsx | 4 +- src/social/components/StoryTab/StoryTab.tsx | 65 ++++ src/social/components/StoryTab/index.tsx | 1 + .../post/Post/DefaultPostRenderer.tsx | 217 ++++++++--- src/social/components/post/Post/index.tsx | 5 - src/social/components/post/Post/styles.tsx | 12 + src/social/constants.ts | 1 + src/social/pages/Application/index.tsx | 16 + src/social/pages/CommunityFeed/index.tsx | 22 +- src/social/pages/DraftPage.tsx | 22 ++ src/social/pages/NewsFeed/index.tsx | 2 +- src/social/pages/ViewStoryPage.tsx | 49 +++ src/social/providers/NavigationProvider.tsx | 40 +- src/v4/core/providers/NavigationProvider.tsx | 115 +++--- .../core/providers/PageBehaviorProvider.tsx | 11 +- .../components/PostContent/PostContent.tsx | 2 +- .../social/components/StoryTab/StoryTab.tsx | 57 ++- .../components/StoryTab/StoryTabCommunity.tsx | 25 +- .../StoryTab/StoryTabGlobalFeed.tsx | 28 +- .../components/StoryTab/StoryTabItem.tsx | 14 +- .../ViewStoryPage/ViewStoryPage.module.css | 355 ------------------ .../social/components/ViewStoryPage/index.tsx | 323 ---------------- .../StoryRing}/StoryRing.module.css | 0 .../StoryRing}/StoryRing.tsx | 76 ++-- src/v4/social/elements/StoryRing/index.tsx | 1 + .../collections/useGlobalStoryTargets.tsx | 1 + .../StoryViewer/Renderers/AutoPlayContent.tsx | 4 +- .../StoryViewer/Renderers/Default.tsx | 4 +- .../StoryViewer/Renderers/Image.tsx | 28 +- .../StoryViewer/Renderers/Video.tsx | 28 +- .../StoryViewer/Renderers/index.tsx | 8 +- .../StoryViewer/Renderers/types.ts | 53 ++- src/v4/social/pages/Application/index.tsx | 4 +- .../pages/DraftsPage/DraftsPage.module.css | 8 +- src/v4/social/pages/DraftsPage/DraftsPage.tsx | 74 ++-- .../pages/StoryPage/CommunityFeedStory.tsx | 38 +- .../pages/StoryPage/GlobalFeedStory.tsx | 130 ++++--- .../pages/StoryPage/ViewGlobalFeedStory.tsx | 46 +++ .../social/pages/StoryPage/ViewStoryPage.tsx | 47 ++- 50 files changed, 1158 insertions(+), 1168 deletions(-) create mode 100644 src/social/components/StoryTab/StoryTab.tsx create mode 100644 src/social/components/StoryTab/index.tsx create mode 100644 src/social/pages/DraftPage.tsx create mode 100644 src/social/pages/ViewStoryPage.tsx delete mode 100644 src/v4/social/components/ViewStoryPage/ViewStoryPage.module.css delete mode 100644 src/v4/social/components/ViewStoryPage/index.tsx rename src/v4/social/{components/StoryTab => elements/StoryRing}/StoryRing.module.css (100%) rename src/v4/social/{components/StoryTab => elements/StoryRing}/StoryRing.tsx (76%) create mode 100644 src/v4/social/elements/StoryRing/index.tsx create mode 100644 src/v4/social/pages/StoryPage/ViewGlobalFeedStory.tsx diff --git a/src/core/components/Dropdown/index.tsx b/src/core/components/Dropdown/index.tsx index 8cf1ce5d2..3ec5a16dc 100644 --- a/src/core/components/Dropdown/index.tsx +++ b/src/core/components/Dropdown/index.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useState, useEffect, useCallback, MutableRefObject } from 'react'; import Button from '~/core/components/Button'; import { ChevronDown } from '~/icons'; @@ -10,6 +10,54 @@ import { DropdownContainer, Frame, FrameContainer, ButtonContainer } from './sty const SCROLLABLE_HEIGHT = 200; +export const useDropdown = ({ + parentContainer, + dropdownRef, + align = POSITION_LEFT, + scrollableHeight = SCROLLABLE_HEIGHT, + position = POSITION_BOTTOM, + buttonContainerHeight, +}: { + scrollableHeight?: number; + position?: string; + align?: string; + dropdownRef: MutableRefObject<HTMLElement | null>; + parentContainer?: Element | null; + buttonContainerHeight: number; +}) => { + const [currentPosition, setCurrentPosition] = useState(position); + const entry = useObserver(dropdownRef.current, { + root: parentContainer, + rootMargin: parentContainer + ? `0px 0px -${Math.ceil( + (scrollableHeight * 100) / + (parentContainer.getBoundingClientRect().height - buttonContainerHeight), + )}% 0px` + : undefined, + }); + + // handling reposition for dropdown list + useEffect(() => { + if (entry?.isIntersecting === false) { + // handling vertical re-position + if (entry.boundingClientRect?.top < 0) { + setCurrentPosition(POSITION_BOTTOM); + } else { + setCurrentPosition(POSITION_TOP); + } + } else { + // reset to default + setCurrentPosition(position); + } + }, [entry, position]); + + return { + align, + currentPosition, + scrollableHeight, + }; +}; + const triggerRenderer: DropdownProps['renderTrigger'] = (props) => { return ( <Button {...props}> diff --git a/src/core/components/Uploaders/File/styles.tsx b/src/core/components/Uploaders/File/styles.tsx index 5b0638613..ce145d4d6 100644 --- a/src/core/components/Uploaders/File/styles.tsx +++ b/src/core/components/Uploaders/File/styles.tsx @@ -82,7 +82,7 @@ export const RemoveIcon = styled(Remove)` export const RemoveButton = (props: ButtonProps) => ( <Button {...props} variant="secondary"> - <RemoveIcon /> + <RemoveIcon width={14} height={14} /> </Button> ); diff --git a/src/core/components/Uploaders/Image/styles.tsx b/src/core/components/Uploaders/Image/styles.tsx index 8eec5666e..3c9847cac 100644 --- a/src/core/components/Uploaders/Image/styles.tsx +++ b/src/core/components/Uploaders/Image/styles.tsx @@ -71,12 +71,14 @@ export const ImageSkeleton = () => ( </SizeMe> ); +const StyledRemoveIcon = styled(RemoveIcon).attrs<{ icon?: ReactNode }>({width: 24, height: 24})``; + export const RemoveButton = styled(Button).attrs<{ variant?: string; children?: ReactNode; }>({ variant: 'secondary', - children: <RemoveIcon />, + children: <StyledRemoveIcon />, })` position: absolute; top: 0.5em; diff --git a/src/core/components/Uploaders/Video/styles.tsx b/src/core/components/Uploaders/Video/styles.tsx index d911c2f12..2c5ff7faa 100644 --- a/src/core/components/Uploaders/Video/styles.tsx +++ b/src/core/components/Uploaders/Video/styles.tsx @@ -83,12 +83,14 @@ export const VideoSkeleton = () => ( </SizeMe> ); +const StyledRemoveIcon = styled(Remove).attrs<{ icon?: ReactNode }>({width: 24, height: 24})``; + export const RemoveButton = styled(Button).attrs<{ variant?: string; children?: ReactNode; }>({ variant: 'secondary', - children: Remove, + children: <StyledRemoveIcon />, })` position: absolute; top: 0.5em; diff --git a/src/core/providers/UiKitProvider/index.tsx b/src/core/providers/UiKitProvider/index.tsx index 3dad3d09c..88c9d573c 100644 --- a/src/core/providers/UiKitProvider/index.tsx +++ b/src/core/providers/UiKitProvider/index.tsx @@ -1,27 +1,33 @@ import './inter.css'; -import './index.css'; import React, { useState, useEffect, useMemo, useRef } from 'react'; import { Client as ASCClient } from '@amityco/ts-sdk'; import { ThemeProvider } from 'styled-components'; import { NotificationsContainer } from '~/core/components/Notification'; import { ConfirmComponent } from '~/core/components/Confirm'; +import { NotificationsContainer as NotificationsContainerV4 } from '~/v4/core/components/Notification'; +import { ConfirmComponent as ConfirmComponentV4 } from '~/v4/core/components/ConfirmModal'; import ConfigProvider from '~/social/providers/ConfigProvider'; import Localization from './Localization'; import buildGlobalTheme from './theme'; import { UIStyles } from './styles'; import { SDKContext } from '../SDKProvider'; +import { SDKContext as SDKContextV4 } from '~/v4/core/providers/SDKProvider'; import useUser from '~/core/hooks/useUser'; import NavigationProvider from '~/social/providers/NavigationProvider'; -import SDKConnectorProvider from '../SDKConnectorProvider'; +import SDKConnectorProvider from '~/core/providers/SDKConnectorProvider'; +import SDKConnectorProviderV4 from '~/v4/core/providers/SDKConnectorProvider'; import CustomComponentsProvider, { CustomComponentType } from '../CustomComponentsProvider'; import PostRendererProvider, { PostRendererConfigType, } from '~/social/providers/PostRendererProvider'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { ConfirmProvider } from '../ConfirmProvider'; +import { ConfirmProvider as ConfirmProviderV4 } from '~/v4/core/providers/ConfirmProvider'; +import { NotificationProvider as NotificationProviderV4 } from '~/v4/core/providers/NotificationProvider'; +import { ConfirmProvider } from '~/core/providers/ConfirmProvider'; import { NotificationProvider } from '~/core/providers/NotificationProvider'; +import { CustomizationProvider } from '~/v4/core/providers/CustomizationProvider'; interface UiKitProviderProps { apiKey: string; @@ -150,26 +156,40 @@ const UiKitProvider = ({ <ThemeProvider theme={buildGlobalTheme(theme)}> <UIStyles> <SDKContext.Provider value={sdkContextValue}> - <SDKConnectorProvider> - <ConfirmProvider> - <NotificationProvider> - <CustomComponentsProvider config={customComponents}> - <ConfigProvider - config={{ - socialCommunityCreationButtonVisible: - socialCommunityCreationButtonVisible || true, - }} - > - <PostRendererProvider config={postRendererConfig}> - <NavigationProvider {...actionHandlers}>{children}</NavigationProvider> - </PostRendererProvider> - </ConfigProvider> - <NotificationsContainer /> - <ConfirmComponent /> - </CustomComponentsProvider> - </NotificationProvider> - </ConfirmProvider> - </SDKConnectorProvider> + <SDKContextV4.Provider value={sdkContextValue}> + <SDKConnectorProvider> + <SDKConnectorProviderV4> + <ConfirmProvider> + <ConfirmProviderV4> + <NotificationProvider> + <NotificationProviderV4> + <CustomizationProvider initialConfig={{}}> + <CustomComponentsProvider config={customComponents}> + <ConfigProvider + config={{ + socialCommunityCreationButtonVisible: + socialCommunityCreationButtonVisible || true, + }} + > + <PostRendererProvider config={postRendererConfig}> + <NavigationProvider {...actionHandlers}> + {children} + </NavigationProvider> + </PostRendererProvider> + </ConfigProvider> + <NotificationsContainer /> + <NotificationsContainerV4 /> + <ConfirmComponent /> + <ConfirmComponentV4 /> + </CustomComponentsProvider> + </CustomizationProvider> + </NotificationProviderV4> + </NotificationProvider> + </ConfirmProviderV4> + </ConfirmProvider> + </SDKConnectorProviderV4> + </SDKConnectorProvider> + </SDKContextV4.Provider> </SDKContext.Provider> </UIStyles> </ThemeProvider> diff --git a/src/i18n/en.json b/src/i18n/en.json index 0781af9e1..7e84367f0 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -52,7 +52,7 @@ "collapsible.viewAll": "View all", "collapsible.viewAllFiles": "View all files", "collapsible.viewMoreComments": "View more comments", - "collapsible.viewMoreReplies": "View {count, plural, one {# reply} other {# replies}}", + "collapsible.viewMoreReplies": "View more replies", "community.createSuccess": "Your community was successfully created", "community.updateSuccess": "Your community was successfully updated", diff --git a/src/social/components/Comment/StyledComment.tsx b/src/social/components/Comment/StyledComment.tsx index 5ebfc4d15..2498b13ff 100644 --- a/src/social/components/Comment/StyledComment.tsx +++ b/src/social/components/Comment/StyledComment.tsx @@ -1,9 +1,7 @@ -/* eslint-disable import/no-cycle */ -import React from 'react'; +import React, { forwardRef, MutableRefObject, useRef, useState } from 'react'; import Truncate from 'react-truncate-markup'; import { FormattedMessage, useIntl } from 'react-intl'; -import { POSITION_LEFT } from '~/helpers/getCssPosition'; import Button, { PrimaryButton } from '~/core/components/Button'; import CommentLikeButton from '~/social/components/CommentLikeButton'; import CommentText from './CommentText'; @@ -20,14 +18,121 @@ import { InteractionBar, ReplyIcon, ReplyButton, - OptionMenu, CommentEditContainer, CommentEditTextarea, ButtonContainer, EditedMark, + OptionMenuContainer, + OptionButtonContainer, } from './styles'; import { Mentioned, Metadata, isNonNullable } from '~/helpers/utils'; import { QueryMentioneesFnType } from '~/social/hooks/useSocialMention'; +import { Option, OptionsButton, OptionsIcon } from '~/core/components/OptionMenu/styles'; +import useCommentFlaggedByMe from '~/social/hooks/useCommentFlaggedByMe'; +import { useNotifications } from '~/core/providers/NotificationProvider'; +import { useDropdown } from '~/core/components/Dropdown/index'; +import useElementSize from '~/core/hooks/useElementSize'; +import { Frame, FrameContainer } from '~/core/components/Dropdown/styles'; + +type OptionMenuProps = StyledCommentProps & { + onEditCommentClick: () => void; + onClose: () => void; + buttonContainerHeight: number; +}; + +const OptionMenu = ({ + commentId, + canDelete = false, + canEdit = false, + canReport = true, + handleReportComment, + startEditing, + handleDelete, + isReplyComment, + onClose, + buttonContainerHeight, +}: OptionMenuProps) => { + const { formatMessage } = useIntl(); + const notification = useNotifications(); + const dropdownRef = useRef<HTMLDivElement>(null); + + const { currentPosition, align, scrollableHeight } = useDropdown({ + dropdownRef, + buttonContainerHeight, + }); + + const { isFlaggedByMe: isReported, toggleFlagComment } = useCommentFlaggedByMe(commentId); + + const onReportClick = async () => { + try { + await toggleFlagComment(); + handleReportComment?.(); + if (isReported) { + notification.success({ + content: formatMessage({ id: 'report.unreportSent' }), + }); + } else { + notification.success({ + content: formatMessage({ id: 'report.reportSent' }), + }); + } + } catch (err) { + if (err instanceof Error) { + notification.error({ + content: err.message, + }); + } + } + }; + + const options = [ + canEdit + ? { + name: isReplyComment + ? formatMessage({ id: 'reply.edit' }) + : formatMessage({ id: 'comment.edit' }), + action: startEditing, + } + : null, + canReport + ? { + name: isReported + ? formatMessage({ id: 'report.undoReport' }) + : formatMessage({ id: 'report.doReport' }), + action: onReportClick, + } + : null, + canDelete + ? { + name: isReplyComment + ? formatMessage({ id: 'reply.delete' }) + : formatMessage({ id: 'comment.delete' }), + action: handleDelete, + } + : null, + ].filter(isNonNullable); + + return ( + <OptionMenuContainer ref={dropdownRef}> + <FrameContainer> + <Frame position={currentPosition} align={align} scrollableHeight={scrollableHeight}> + {options.map(({ name, action }) => ( + <Option + key={name} + data-qa-anchor={`post-options-button-${name}`} + onClick={() => { + action?.(); + onClose(); + }} + > + {name} + </Option> + ))} + </Frame> + </FrameContainer> + </OptionMenuContainer> + ); +}; interface StyledCommentProps { commentId?: string; @@ -67,61 +172,33 @@ interface StyledCommentProps { metadata?: Metadata; } -const StyledComment = ({ - commentId, - authorName, - authorAvatar, - canDelete = false, - canEdit = false, - canLike = true, - canReply = false, - canReport = true, - createdAt, - editedAt, - text, - markup, - onClickReply, - handleReportComment, - handleEdit, - startEditing, - cancelEditing, - handleDelete, - isEditing, - onChange, - queryMentionees, - isReported, - isReplyComment, - isBanned, - mentionees, - metadata, -}: StyledCommentProps) => { +const StyledComment = (props: StyledCommentProps) => { + const { + commentId, + authorName, + authorAvatar, + canLike = true, + canReply = false, + createdAt, + editedAt, + text, + markup, + onClickReply, + handleEdit, + startEditing, + cancelEditing, + isEditing, + onChange, + queryMentionees, + isBanned, + mentionees, + } = props; const { formatMessage } = useIntl(); - const options = [ - canEdit - ? { - name: isReplyComment - ? formatMessage({ id: 'reply.edit' }) - : formatMessage({ id: 'comment.edit' }), - action: startEditing, - } - : null, - canReport - ? { - name: isReported - ? formatMessage({ id: 'report.undoReport' }) - : formatMessage({ id: 'report.doReport' }), - action: handleReportComment, - } - : null, - canDelete - ? { - name: isReplyComment - ? formatMessage({ id: 'reply.delete' }) - : formatMessage({ id: 'comment.delete' }), - action: handleDelete, - } - : null, - ].filter(isNonNullable); + + const [isMenuOpen, setIsMenuOpen] = useState(false); + const [buttonContainerRef, buttonContainerHeight] = useElementSize(); + + const toggle = () => setIsMenuOpen((prev) => !prev); return ( <> @@ -189,7 +266,7 @@ const StyledComment = ({ <CommentText text={text} mentionees={mentionees} /> )} - {!isEditing && (canLike || canReply || options.length > 0) && ( + {!isEditing && (canLike || canReply) && ( <InteractionBar> {canLike && <CommentLikeButton commentId={commentId} />} @@ -199,12 +276,21 @@ const StyledComment = ({ </ReplyButton> )} - <OptionMenu - data-qa-anchor="comment-options-button" - options={options} - pullRight={false} - align={POSITION_LEFT} - /> + <OptionButtonContainer> + <div ref={buttonContainerRef}> + <OptionsButton onClick={toggle}> + <OptionsIcon /> + </OptionsButton> + </div> + {isMenuOpen && ( + <OptionMenu + {...props} + onClose={toggle} + onEditCommentClick={() => startEditing()} + buttonContainerHeight={buttonContainerHeight} + /> + )} + </OptionButtonContainer> </InteractionBar> )} </Content> diff --git a/src/social/components/Comment/index.tsx b/src/social/components/Comment/index.tsx index a60124f48..f032b7290 100644 --- a/src/social/components/Comment/index.tsx +++ b/src/social/components/Comment/index.tsx @@ -32,7 +32,6 @@ import useUser from '~/core/hooks/useUser'; import useFile from '~/core/hooks/useFile'; import { CommentRepository } from '@amityco/ts-sdk'; import { useCustomComponent } from '~/core/providers/CustomComponentsProvider'; -import useCommentFlaggedByMe from '~/social/hooks/useCommentFlaggedByMe'; import useCommentPermission from '~/social/hooks/useCommentPermission'; import useCommentSubscription from '~/social/hooks/useCommentSubscription'; @@ -96,7 +95,6 @@ const Comment = ({ commentId, readonly }: CommentProps) => { const commentAuthor = useUser(comment?.userId); const commentAuthorAvatar = useFile(commentAuthor?.avatarFileId); const { userRoles } = useSDK(); - const { toggleFlagComment, isFlaggedByMe } = useCommentFlaggedByMe(commentId); const [isReplying, setIsReplying] = useState(false); const [isEditing, setIsEditing] = useState(false); @@ -131,10 +129,6 @@ const Comment = ({ commentId, readonly }: CommentProps) => { if (post == null && comment == null) return <LoadingIndicator />; - const handleReportComment = async () => { - return toggleFlagComment(); - }; - const handleReplyToComment = async ( replyCommentText: string, mentionees: Mentionees, @@ -180,27 +174,6 @@ const Comment = ({ commentId, readonly }: CommentProps) => { const handleDeleteComment = async () => commentId && CommentRepository.deleteComment(commentId); - const onReportClick = async () => { - try { - await handleReportComment(); - if (isFlaggedByMe) { - notification.success({ - content: formatMessage({ id: 'report.unreportSent' }), - }); - } else { - notification.success({ - content: formatMessage({ id: 'report.reportSent' }), - }); - } - } catch (err) { - if (err instanceof Error) { - notification.error({ - content: err.message, - }); - } - } - }; - const onClickReply = () => { setIsReplying((preValue) => !preValue); }; @@ -268,14 +241,12 @@ const Comment = ({ commentId, readonly }: CommentProps) => { metadata={comment?.metadata} text={text} markup={markup} - handleReportComment={onReportClick} startEditing={startEditing} cancelEditing={cancelEditing} handleEdit={handleEdit} handleDelete={deleteComment} isEditing={isEditing} queryMentionees={queryMentionees} - isReported={isFlaggedByMe} isReplyComment={isReplyComment} onClickReply={onClickReply} onChange={onChange} diff --git a/src/social/components/Comment/styles.tsx b/src/social/components/Comment/styles.tsx index c432d9b23..ef860fc01 100644 --- a/src/social/components/Comment/styles.tsx +++ b/src/social/components/Comment/styles.tsx @@ -9,6 +9,19 @@ import { MinusCircle, Reply } from '~/icons'; import UIAvatar from '~/core/components/Avatar'; +export const OptionButtonContainer = styled.div` + display: flex; + align-items: center; + + position: relative; +`; + +export const OptionMenuContainer = styled.div` + position: absolute; + + left: 100%; +`; + export const Avatar = styled(UIAvatar)` margin-right: 8px; `; diff --git a/src/social/components/CommentList/index.tsx b/src/social/components/CommentList/index.tsx index 5699d3225..2e27d5bdc 100644 --- a/src/social/components/CommentList/index.tsx +++ b/src/social/components/CommentList/index.tsx @@ -46,7 +46,7 @@ const CommentList = ({ }); const loadMoreText = isReplyComment - ? formatMessage({ id: 'collapsible.viewMoreReplies' }, { count: comments.length }) + ? formatMessage({ id: 'collapsible.viewMoreReplies' }) : formatMessage({ id: 'collapsible.viewMoreComments' }); const prependIcon = isReplyComment ? ( diff --git a/src/social/components/CommunityInfo/UICommunityInfo.tsx b/src/social/components/CommunityInfo/UICommunityInfo.tsx index 9e62095c9..126ae36d6 100644 --- a/src/social/components/CommunityInfo/UICommunityInfo.tsx +++ b/src/social/components/CommunityInfo/UICommunityInfo.tsx @@ -26,7 +26,7 @@ import { import { useCustomComponent } from '~/core/providers/CustomComponentsProvider'; import millify from 'millify'; import { isNonNullable } from '~/helpers/utils'; -import { StoryTab } from '~/v4/social/components'; +import { StoryTab } from '~/social/components/StoryTab'; interface UICommunityInfoProps { communityId: string; diff --git a/src/social/components/EngagementBar/UIEngagementBar.tsx b/src/social/components/EngagementBar/UIEngagementBar.tsx index 328a3c158..7abef7342 100644 --- a/src/social/components/EngagementBar/UIEngagementBar.tsx +++ b/src/social/components/EngagementBar/UIEngagementBar.tsx @@ -73,7 +73,7 @@ const UIEngagementBar = ({ <CommentIcon /> <FormattedMessage id="comment" /> </SecondaryButton> </InteractionBar> - {latestComments?.length > 0 ? ( + {post.commentsCount > 0 ? ( <CommentList referenceId={postId} referenceType={'post'} limit={COMMENTS_PER_PAGE} /> ) : null} @@ -91,7 +91,7 @@ const UIEngagementBar = ({ <NoInteractionMessage> <FormattedMessage id="community.cannotInteract" /> </NoInteractionMessage> - {latestComments?.length > 0 ? ( + {post.commentsCount > 0 ? ( <CommentList referenceId={postId} referenceType={'post'} diff --git a/src/social/components/StoryTab/StoryTab.tsx b/src/social/components/StoryTab/StoryTab.tsx new file mode 100644 index 000000000..a9c0133c8 --- /dev/null +++ b/src/social/components/StoryTab/StoryTab.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { useNavigation } from '~/social/providers/NavigationProvider'; + +import { StoryTabCommunityFeed } from '~/v4/social/components/StoryTab/StoryTabCommunity'; +import { StoryTabGlobalFeed } from '~/v4/social/components/StoryTab/StoryTabGlobalFeed'; +import { useStoryContext } from '~/v4/social/providers/StoryProvider'; + +type StoryTabType = 'communityFeed' | 'globalFeed'; + +type StoryTabProps<T extends StoryTabType> = { + type: T; + communityId?: T extends 'communityFeed' ? string : never; +}; + +export const StoryTab = <T extends StoryTabType>({ type, communityId }: StoryTabProps<T>) => { + const pageId = '*'; + const componentId = 'story_tab_component'; + const { onClickStory, goToDraftStoryPage } = useNavigation(); + + const { setFile } = useStoryContext(); + + const renderStoryTab = () => { + switch (type) { + case 'communityFeed': + return ( + <StoryTabCommunityFeed + pageId={pageId} + componentId={componentId} + communityId={communityId || ''} + onFileChange={(file) => { + setFile(file); + if (file) { + goToDraftStoryPage( + communityId || '', + 'community', + file.type.includes('image') + ? { type: 'image', url: URL.createObjectURL(file) } + : { type: 'video', url: URL.createObjectURL(file) }, + ); + } + }} + onStoryClick={() => onClickStory(communityId || '', 'communityFeed')} + /> + ); + case 'globalFeed': + return ( + <StoryTabGlobalFeed + pageId={pageId} + componentId={componentId} + goToViewStoryPage={({ storyTarget, storyTargets }) => { + onClickStory( + storyTarget.targetId, + 'globalFeed', + storyTargets.map((s) => s.targetId), + ); + }} + /> + ); + default: + return null; + } + }; + + return renderStoryTab(); +}; diff --git a/src/social/components/StoryTab/index.tsx b/src/social/components/StoryTab/index.tsx new file mode 100644 index 000000000..79f9f2f95 --- /dev/null +++ b/src/social/components/StoryTab/index.tsx @@ -0,0 +1 @@ +export { StoryTab } from './StoryTab'; diff --git a/src/social/components/post/Post/DefaultPostRenderer.tsx b/src/social/components/post/Post/DefaultPostRenderer.tsx index 0da4efad6..c4b08f66f 100644 --- a/src/social/components/post/Post/DefaultPostRenderer.tsx +++ b/src/social/components/post/Post/DefaultPostRenderer.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useRef, useState } from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; import Button, { PrimaryButton } from '~/core/components/Button'; import Modal from '~/core/components/Modal'; @@ -10,7 +10,8 @@ import PostHeader from '~/social/components/post/Header'; import Content from '~/social/components/post/Post/Content'; import { ContentSkeleton, - OptionMenu, + OptionButtonContainer, + OptionMenuContainer, PostContainer, PostHeadContainer, ReviewButtonsContainer, @@ -23,6 +24,12 @@ import usePostSubscription from '~/social/hooks/usePostSubscription'; import { SubscriptionLevels } from '@amityco/ts-sdk'; import { useConfirmContext } from '~/core/providers/ConfirmProvider'; import { useNotifications } from '~/core/providers/NotificationProvider'; +import usePostFlaggedByMe from '~/social/hooks/usePostFlaggedByMe'; +import { Option, OptionsButton, OptionsIcon } from '~/core/components/OptionMenu/styles'; +import { useDropdown } from '~/core/components/Dropdown/index'; +import useElementSize from '~/core/hooks/useElementSize'; +import { Frame, FrameContainer } from '~/core/components/Dropdown/styles'; +import { POSITION_BOTTOM, POSITION_RIGHT } from '~/helpers/getCssPosition'; // Number of lines to show in a text post before truncating. const MAX_TEXT_LINES_DEFAULT = 8; @@ -30,55 +37,45 @@ const MAX_TEXT_LINES_WITH_CHILDREN = 3; const ERROR_POST_HAS_BEEN_REVIEWED = 'Post has been reviewed'; -type DefaultPostRendererProps = PostRendererProps; +type OptionMenuProps = DefaultPostRendererProps & { + onApprove: () => void; + onDecline: () => void; + onEditPostClick: () => void; + onClose: () => void; + buttonContainerHeight: number; +}; -const DefaultPostRenderer = ({ +const OptionMenu = ({ childrenPosts = [], - className, handleDeletePost, handleReportPost, handleUnreportPost, - handleApprovePost, - handleDeclinePost, handleClosePoll, isPollClosed, - hidePostTarget, - isFlaggedByMe, - readonly, post, - loading, -}: DefaultPostRendererProps) => { + onEditPostClick, + onClose, + buttonContainerHeight, +}: OptionMenuProps) => { const { formatMessage } = useIntl(); - const [isEditing, setIsEditing] = useState(false); - const openEditingPostModal = () => setIsEditing(true); - const closeEditingPostModal = () => setIsEditing(false); + const { info, confirm } = useConfirmContext(); const notification = useNotifications(); - function showHasBeenReviewedMessageIfNeeded(error: unknown) { - if (error instanceof Error) { - if (error.message.includes(ERROR_POST_HAS_BEEN_REVIEWED)) { - info({ - title: <FormattedMessage id="post.error.cannotReview.title" />, - content: <FormattedMessage id="post.error.cannotReview.description" />, - }); - } else { - throw error; - } - } - } + const { isFlaggedByMe, toggleFlagPost } = usePostFlaggedByMe(post); const communityId = post?.targetId; const community = useCommunity(communityId); const { currentUserId } = useSDK(); + const dropdownRef = useRef<HTMLDivElement>(null); - usePostSubscription({ - postId: post?.postId, - level: SubscriptionLevels.POST, + const { currentPosition, align, scrollableHeight } = useDropdown({ + dropdownRef, + buttonContainerHeight, + position: POSITION_BOTTOM, + align: POSITION_RIGHT, }); - const [approving, setApproving] = useState(false); - const [declining, setDeclining] = useState(false); const { canEdit, canReview, canDelete, canReport, isPostUnderReview } = useCommunityPostPermission({ community, @@ -87,40 +84,20 @@ const DefaultPostRenderer = ({ userId: currentUserId || undefined, }); + const pollPost = childrenPosts.find((childPost) => childPost.dataType === 'poll'); + const onReportClick = async () => { + toggleFlagPost(); await handleReportPost?.(); notification.success({ content: <FormattedMessage id="report.reportSent" /> }); }; const onUnreportClick = async () => { + toggleFlagPost(); await handleUnreportPost?.(); notification.success({ content: <FormattedMessage id="report.unreportSent" /> }); }; - const onApprove = async () => { - try { - setApproving(true); - await handleApprovePost?.(); - notification.success({ content: <FormattedMessage id="post.success.approved" /> }); - } catch (error) { - showHasBeenReviewedMessageIfNeeded(error); - } finally { - setApproving(false); - } - }; - - const onDecline = async () => { - try { - setDeclining(true); - await handleDeclinePost?.(); - notification.success({ content: <FormattedMessage id="post.success.declined" /> }); - } catch (error) { - showHasBeenReviewedMessageIfNeeded(error); - } finally { - setDeclining(false); - } - }; - const confirmDeletePost = () => { confirm({ title: formatMessage({ id: 'post.deletePost' }), @@ -132,13 +109,11 @@ const DefaultPostRenderer = ({ }); }; - const pollPost = childrenPosts.find((childPost) => childPost.dataType === 'poll'); - - const allOptions = [ + const options = [ canEdit ? { name: formatMessage({ id: 'post.editPost' }), - action: openEditingPostModal, + action: () => onEditPostClick(), } : null, canDelete @@ -163,6 +138,108 @@ const DefaultPostRenderer = ({ : null, ].filter(isNonNullable); + return ( + <OptionMenuContainer ref={dropdownRef}> + <FrameContainer> + <Frame position={currentPosition} align={align} scrollableHeight={scrollableHeight}> + {options.map(({ name, action }) => ( + <Option + key={name} + data-qa-anchor={`post-options-button-${name}`} + onClick={() => { + action?.(); + onClose(); + }} + > + {name} + </Option> + ))} + </Frame> + </FrameContainer> + </OptionMenuContainer> + ); +}; + +type DefaultPostRendererProps = PostRendererProps; + +const DefaultPostRenderer = (props: DefaultPostRendererProps) => { + const { + childrenPosts = [], + className, + handleApprovePost, + handleDeclinePost, + hidePostTarget, + readonly, + post, + loading, + } = props; + const { formatMessage } = useIntl(); + const [isEditing, setIsEditing] = useState(false); + const openEditingPostModal = () => setIsEditing(true); + const closeEditingPostModal = () => setIsEditing(false); + const { info, confirm } = useConfirmContext(); + const notification = useNotifications(); + const [isMenuOpen, setIsMenuOpen] = useState(false); + const [buttonContainerRef, buttonContainerHeight] = useElementSize(); + + const [approving, setApproving] = useState(false); + const [declining, setDeclining] = useState(false); + + const toggle = () => setIsMenuOpen((prev) => !prev); + + function showHasBeenReviewedMessageIfNeeded(error: unknown) { + if (error instanceof Error) { + if (error.message.includes(ERROR_POST_HAS_BEEN_REVIEWED)) { + info({ + title: <FormattedMessage id="post.error.cannotReview.title" />, + content: <FormattedMessage id="post.error.cannotReview.description" />, + }); + } else { + throw error; + } + } + } + + const communityId = post?.targetId; + const community = useCommunity(communityId); + const { currentUserId } = useSDK(); + + usePostSubscription({ + postId: post?.postId, + level: SubscriptionLevels.POST, + }); + + const { canReview, isPostUnderReview } = useCommunityPostPermission({ + community, + post, + childrenPosts, + userId: currentUserId || undefined, + }); + + const onApprove = async () => { + try { + setApproving(true); + await handleApprovePost?.(); + notification.success({ content: <FormattedMessage id="post.success.approved" /> }); + } catch (error) { + showHasBeenReviewedMessageIfNeeded(error); + } finally { + setApproving(false); + } + }; + + const onDecline = async () => { + try { + setDeclining(true); + await handleDeclinePost?.(); + notification.success({ content: <FormattedMessage id="post.success.declined" /> }); + } catch (error) { + showHasBeenReviewedMessageIfNeeded(error); + } finally { + setDeclining(false); + } + }; + const hasChildrenPosts = childrenPosts.length > 0; const postMaxLines = hasChildrenPosts ? MAX_TEXT_LINES_WITH_CHILDREN : MAX_TEXT_LINES_DEFAULT; @@ -175,7 +252,25 @@ const DefaultPostRenderer = ({ <PostContainer data-qa-anchor="post" className={className}> <PostHeadContainer> <PostHeader postId={post?.postId} hidePostTarget={hidePostTarget} loading={loading} /> - {!loading && <OptionMenu options={allOptions} data-qa-anchor="post-options-button" />} + {!loading && ( + <OptionButtonContainer> + <div ref={buttonContainerRef}> + <OptionsButton onClick={toggle} className={className}> + <OptionsIcon /> + </OptionsButton> + </div> + {isMenuOpen && ( + <OptionMenu + {...props} + onApprove={onApprove} + onDecline={onDecline} + onClose={toggle} + onEditPostClick={() => openEditingPostModal()} + buttonContainerHeight={buttonContainerHeight} + /> + )} + </OptionButtonContainer> + )} </PostHeadContainer> {loading ? ( diff --git a/src/social/components/post/Post/index.tsx b/src/social/components/post/Post/index.tsx index 0930f5f34..9b474e2a3 100644 --- a/src/social/components/post/Post/index.tsx +++ b/src/social/components/post/Post/index.tsx @@ -9,7 +9,6 @@ import { PollRepository, PostRepository, SubscriptionLevels } from '@amityco/ts- import usePostByIds from '~/social/hooks/usePostByIds'; import { useCustomComponent } from '~/core/providers/CustomComponentsProvider'; import useImage from '~/core/hooks/useImage'; -import usePostFlaggedByMe from '~/social/hooks/usePostFlaggedByMe'; import { usePostRenderer } from '~/social/providers/PostRendererProvider'; import usePostSubscription from '~/social/hooks/usePostSubscription'; import useReactionSubscription from '~/social/hooks/useReactionSubscription'; @@ -28,7 +27,6 @@ const Post = ({ postId, className, hidePostTarget, readonly, onDeleted }: PostPr const avatarFileUrl = useImage({ fileId: postedUser?.avatarFileId, imageSize: 'small' }); const childrenPosts = usePostByIds(post?.children); const { userRoles } = useSDK(); - const { isFlaggedByMe, toggleFlagPost } = usePostFlaggedByMe(post); const postRenderFn = usePostRenderer(post?.dataType); const { currentUserId } = useSDK(); @@ -87,9 +85,6 @@ const Post = ({ postId, className, hidePostTarget, readonly, onDeleted }: PostPr post, userRoles, readonly, - isFlaggedByMe, - handleReportPost: toggleFlagPost, - handleUnreportPost: toggleFlagPost, handleApprovePost, handleDeclinePost, handleDeletePost, diff --git a/src/social/components/post/Post/styles.tsx b/src/social/components/post/Post/styles.tsx index a84aa7cfc..f56d302eb 100644 --- a/src/social/components/post/Post/styles.tsx +++ b/src/social/components/post/Post/styles.tsx @@ -4,6 +4,17 @@ import styled from 'styled-components'; import UIOptionMenu from '~/core/components/OptionMenu'; import Skeleton from '~/core/components/Skeleton'; +export const OptionButtonContainer = styled.div` + display: flex; + align-items: center; + + position: relative; +`; + +export const OptionMenuContainer = styled.div` + position: absolute; +`; + export const OptionMenu = styled(UIOptionMenu)<{ icon?: ReactNode }>` margin-left: auto; `; @@ -27,6 +38,7 @@ export const PostContainer = styled(PlainPostContainer)` export const PostHeadContainer = styled.div` display: flex; align-items: center; + justify-content: space-between; margin-bottom: 14px; `; diff --git a/src/social/constants.ts b/src/social/constants.ts index 4b96820e2..037a8097f 100644 --- a/src/social/constants.ts +++ b/src/social/constants.ts @@ -7,6 +7,7 @@ export const enum PageTypes { CommunityEdit = 'communityedit', UserEdit = 'useredit', ViewStory = 'viewstory', + DraftPage = 'draftpage', } export const MemberRoles = Object.freeze({ diff --git a/src/social/pages/Application/index.tsx b/src/social/pages/Application/index.tsx index c06c3b3ba..b5f93a5e6 100644 --- a/src/social/pages/Application/index.tsx +++ b/src/social/pages/Application/index.tsx @@ -19,6 +19,8 @@ import useSDK from '~/core/hooks/useSDK'; import { StoryProvider } from '~/v4/social/providers/StoryProvider'; import CommunityFeed from '../CommunityFeed'; +import ViewStoryPage from '../ViewStoryPage'; +import AmityDraftStoryPage from '../DraftPage'; const ApplicationContainer = styled.div` height: 100%; @@ -87,6 +89,20 @@ const Community = () => { /> )} + {page.type === PageTypes.ViewStory && ( + <Wrapper> + <ViewStoryPage targetId={page.targetId} type={page.storyType} /> + </Wrapper> + )} + + {page.type === PageTypes.DraftPage && ( + <AmityDraftStoryPage + mediaType={page.mediaType} + targetId={page.targetId} + targetType={page.targetType} + /> + )} + {page.type === PageTypes.CommunityEdit && ( <CommunityEditPage communityId={page.communityId} tab={page.tab} /> )} diff --git a/src/social/pages/CommunityFeed/index.tsx b/src/social/pages/CommunityFeed/index.tsx index 337a34920..b0ca3f9bf 100644 --- a/src/social/pages/CommunityFeed/index.tsx +++ b/src/social/pages/CommunityFeed/index.tsx @@ -32,7 +32,7 @@ import useStories from '~/social/hooks/useStories'; import { BarsIcon } from '~/icons'; import { useStoryContext } from '~/v4/social/providers/StoryProvider'; -import { AmityDraftStoryPage } from '~/v4/social/pages'; +import { useNavigation } from '~/social/providers/NavigationProvider'; interface CommunityFeedProps { communityId: string; @@ -42,7 +42,7 @@ interface CommunityFeedProps { } const CommunityFeed = ({ communityId, isNewCommunity, isOpen, toggleOpen }: CommunityFeedProps) => { - const { file } = useStoryContext(); + const { goToDraftStoryPage } = useNavigation(); const { stories } = useStories({ targetId: communityId, targetType: 'community', @@ -54,8 +54,6 @@ const CommunityFeed = ({ communityId, isNewCommunity, isOpen, toggleOpen }: Comm const community = useCommunity(communityId); - const communityAvatar = useFile(community?.avatarFileId || ''); - const { canReview } = useCommunityPermission({ community }); const { posts } = usePostsCollection({ @@ -88,22 +86,6 @@ const CommunityFeed = ({ communityId, isNewCommunity, isOpen, toggleOpen }: Comm } }, [activeTab, tabs]); - if (file) { - return ( - <Wrapper> - <AmityDraftStoryPage - targetId={communityId} - targetType="community" - mediaType={ - file.type.includes('image') - ? { type: 'image', url: URL.createObjectURL(file) } - : { type: 'video', url: URL.createObjectURL(file) } - } - /> - </Wrapper> - ); - } - return ( <Wrapper> <CommunitySideMenuOverlay isOpen={isOpen} onClick={toggleOpen} /> diff --git a/src/social/pages/DraftPage.tsx b/src/social/pages/DraftPage.tsx new file mode 100644 index 000000000..58cf3983b --- /dev/null +++ b/src/social/pages/DraftPage.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { + AmityDraftStoryPageProps, + PlainDraftStoryPage, +} from '~/v4/social/pages/DraftsPage/DraftsPage'; +import { PageTypes } from '../constants'; +import { useNavigation } from '../providers/NavigationProvider'; + +export const AmityDraftStoryPage = (props: AmityDraftStoryPageProps) => { + const { onChangePage, onClickCommunity } = useNavigation(); + + return ( + <PlainDraftStoryPage + {...props} + onDiscardCreateStory={() => onChangePage(PageTypes.NewsFeed)} + goToCommunityPage={(communityId) => onClickCommunity(communityId)} + goToGlobalFeedPage={() => onChangePage(PageTypes.NewsFeed)} + /> + ); +}; + +export default AmityDraftStoryPage; diff --git a/src/social/pages/NewsFeed/index.tsx b/src/social/pages/NewsFeed/index.tsx index ac43e15e1..7caa3bd99 100644 --- a/src/social/pages/NewsFeed/index.tsx +++ b/src/social/pages/NewsFeed/index.tsx @@ -14,7 +14,7 @@ import { } from './styles'; import { BarsIcon } from '~/icons'; import { useIntl } from 'react-intl'; -import { StoryTab } from '~/v4/social/components'; +import { StoryTab } from '~/social/components/StoryTab'; interface NewsFeedProps { isOpen: boolean; diff --git a/src/social/pages/ViewStoryPage.tsx b/src/social/pages/ViewStoryPage.tsx new file mode 100644 index 000000000..3999025d1 --- /dev/null +++ b/src/social/pages/ViewStoryPage.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { CommunityFeedStory } from '~/v4/social/pages/StoryPage/CommunityFeedStory'; +import { useNavigation } from '~/social/providers/NavigationProvider'; +import { ViewGlobalFeedStoryPage } from '~/v4/social/pages/StoryPage/ViewGlobalFeedStory'; +import { PageTypes } from '../constants'; + +type ViewStoryPageType = 'communityFeed' | 'globalFeed'; + +interface AmityViewStoryPageProps { + type: ViewStoryPageType; + targetId: string; +} + +const ViewStoryPage: React.FC<AmityViewStoryPageProps> = ({ type, targetId }) => { + const { onBack, goToDraftStoryPage, onClickCommunity, onChangePage, onClickStory } = + useNavigation(); + + if (type === 'communityFeed') + return ( + <CommunityFeedStory + communityId={targetId} + onBack={onBack} + onClose={(communityId) => onClickCommunity(communityId)} + onSwipeDown={(communityId) => onClickCommunity(communityId)} + onClickCommunity={(communityId) => onClickCommunity(communityId)} + /> + ); + if (type === 'globalFeed') + return ( + <ViewGlobalFeedStoryPage + targetId={targetId} + onChangePage={() => onChangePage(PageTypes.NewsFeed)} + onClose={() => { + console.log('hello'); + onChangePage(PageTypes.NewsFeed); + }} + onSwipeDown={() => onChangePage(PageTypes.NewsFeed)} + onClickStory={(targetId) => onClickStory(targetId, 'globalFeed')} + goToDraftStoryPage={({ targetId, targetType, mediaType }) => + goToDraftStoryPage(targetId, targetType, mediaType) + } + onClickCommunity={(targetId) => onClickCommunity(targetId)} + /> + ); + + return null; +}; + +export default ViewStoryPage; diff --git a/src/social/providers/NavigationProvider.tsx b/src/social/providers/NavigationProvider.tsx index e2bf1d385..5cd327eb6 100644 --- a/src/social/providers/NavigationProvider.tsx +++ b/src/social/providers/NavigationProvider.tsx @@ -3,6 +3,7 @@ import { FormattedMessage } from 'react-intl'; import { useConfirmContext } from '~/core/providers/ConfirmProvider'; import { PageTypes } from '~/social/constants'; +import { AmityStoryMediaType } from '~/v4/social/pages/DraftsPage/DraftsPage'; type Page = | { @@ -32,10 +33,17 @@ type Page = | { type: PageTypes.ViewStory; storyId: string; - targetId?: string; + targetId: string; communityId?: string; - targetIds?: string[]; - storyType?: 'communityFeed' | 'globalFeed'; + targetIds: string[]; + storyType: 'communityFeed' | 'globalFeed'; + } + | { + type: PageTypes.DraftPage; + communityId?: string; + mediaType: AmityStoryMediaType; + targetId: string; + targetType: Amity.StoryTargetType; }; type ContextValue = { @@ -54,6 +62,11 @@ type ContextValue = { onEditUser: (userId: string) => void; onMessageUser: (userId: string) => void; onBack: () => void; + goToDraftStoryPage: ( + targetId: string, + targetType: string, + mediaType: AmityStoryMediaType, + ) => void; setNavigationBlocker?: ( params: | { @@ -83,6 +96,7 @@ let defaultValue: ContextValue = { onMessageUser: (userId: string) => {}, setNavigationBlocker: () => {}, onBack: () => {}, + goToDraftStoryPage: (targetId: string) => {}, }; export const defaultNavigationBlocker = { @@ -109,6 +123,8 @@ if (process.env.NODE_ENV !== 'production') { onEditUser: (userId) => console.log(`NavigationContext onEditUser(${userId})`), onMessageUser: (userId) => console.log(`NavigationContext onMessageUser(${userId})`), onBack: () => console.log('NavigationContext onBack()'), + goToDraftStoryPage: (targetId, targetType, mediaType) => + console.log(`NavigationContext goToDraftStoryPage(${targetId})`), }; } @@ -139,6 +155,7 @@ interface NavigationProviderProps { onEditUser?: (userId: string) => void; onMessageUser?: (userId: string) => void; onBack?: () => void; + goToDraftStoryPage?: (targetId: string) => void; } export default function NavigationProvider({ @@ -352,6 +369,22 @@ export default function NavigationProvider({ [onChangePage, pushPage], ); + const goToDraftStoryPage = useCallback( + (targetId, targetType, mediaType) => { + const next = { + type: PageTypes.DraftPage, + targetId, + targetType, + mediaType, + }; + + if (onChangePage) return onChangePage(next); + + pushPage(next); + }, + [onChangePage, pushPage], + ); + return ( <NavigationContext.Provider value={{ @@ -366,6 +399,7 @@ export default function NavigationProvider({ onEditUser: handleEditUser, onMessageUser: handleMessageUser, onBack: handleBack, + goToDraftStoryPage, setNavigationBlocker, }} > diff --git a/src/v4/core/providers/NavigationProvider.tsx b/src/v4/core/providers/NavigationProvider.tsx index cf6a03957..70c253dde 100644 --- a/src/v4/core/providers/NavigationProvider.tsx +++ b/src/v4/core/providers/NavigationProvider.tsx @@ -1,4 +1,5 @@ import React, { createContext, useCallback, useContext, useState, useMemo, ReactNode } from 'react'; +import { AmityStoryMediaType } from '~/v4/social/pages/DraftsPage/DraftsPage'; export enum PageTypes { Explore = 'explore', @@ -15,6 +16,7 @@ export enum PageTypes { UserProfilePage = 'UserProfilePage', SocialGlobalSearchPage = 'SocialGlobalSearchPage', SelectPostTargetPage = 'SelectPostTargetPage', + DraftPage = 'DraftPage', } type Page = @@ -56,7 +58,6 @@ type Page = targetId: string; targetType: Amity.StoryTargetType; storyType: 'communityFeed' | 'globalFeed'; - targetIds?: string[]; }; } | { @@ -70,7 +71,14 @@ type Page = | { type: PageTypes.UserProfilePage; context: { userId: string; communityId?: string } } | { type: PageTypes.SocialHomePage; context: { communityId?: string } } | { type: PageTypes.SocialGlobalSearchPage; context: { tab?: string } } - | { type: PageTypes.SelectPostTargetPage }; + | { type: PageTypes.SelectPostTargetPage } + | { + type: PageTypes.DraftPage; + communityId?: string; + mediaType: AmityStoryMediaType; + targetId: string; + targetType: Amity.StoryTargetType; + }; type ContextValue = { page: Page; @@ -78,11 +86,6 @@ type ContextValue = { onClickCategory: (categoryId: string) => void; onClickCommunity: (communityId: string) => void; onClickUser: (userId: string, pageType?: string) => void; - onClickStory: ( - storyId: string, - storyType: 'communityFeed' | 'globalFeed', - targetId?: string[], - ) => void; onCommunityCreated: (communityId: string) => void; onEditCommunity: (communityId: string, tab?: string) => void; onEditUser: (userId: string) => void; @@ -92,13 +95,17 @@ type ContextValue = { goToPostDetailPage: (postId: string) => void; goToCommunityProfilePage: (communityId: string) => void; goToSocialGlobalSearchPage: (tab?: string) => void; - goToViewStoryPage: ( - targetId: string, - targetType: string, - storyType: 'communityFeed' | 'globalFeed', - targetIds?: string[], - ) => void; goToSelectPostTargetPage: () => void; + goToDraftStoryPage: (context: { + targetId: string; + targetType: string; + mediaType: AmityStoryMediaType; + }) => void; + goToViewStoryPage: (context: { + targetId: string; + targetType: string; + storyType: 'communityFeed' | 'globalFeed'; + }) => void; setNavigationBlocker?: ( params: | { @@ -117,22 +124,22 @@ let defaultValue: ContextValue = { onClickCategory: (categoryId: string) => {}, onClickCommunity: (communityId: string) => {}, onClickUser: (userId: string) => {}, - onClickStory: ( - storyId: string, - storyType: 'communityFeed' | 'globalFeed', - targetId?: string[], - ) => {}, onCommunityCreated: (communityId: string) => {}, onEditCommunity: (communityId: string) => {}, onEditUser: (userId: string) => {}, onMessageUser: (userId: string) => {}, goToUserProfilePage: (userId: string) => {}, goToPostDetailPage: (postId: string) => {}, - goToViewStoryPage: ( - targetId: string, - targetType: string, - storyType: 'communityFeed' | 'globalFeed', - ) => {}, + goToViewStoryPage: (context: { + targetId: string; + targetType: string; + storyType: 'communityFeed' | 'globalFeed'; + }) => {}, + goToDraftStoryPage: (context: { + targetId: string; + targetType: string; + mediaType: AmityStoryMediaType; + }) => {}, goToCommunityProfilePage: (communityId: string) => {}, goToSocialGlobalSearchPage: (tab?: string) => {}, goToSelectPostTargetPage: () => {}, @@ -149,8 +156,8 @@ if (process.env.NODE_ENV !== 'production') { onClickCommunity: (communityId) => console.log(`NavigationContext onClickCommunity(${communityId})`), onClickUser: (userId) => console.log(`NavigationContext onClickUser(${userId})`), - onClickStory: (storyId, storyType, targetIds) => - console.log(`NavigationContext onClickStory(${storyId}, ${storyType}, ${targetIds})`), + goToViewStoryPage: ({ targetId, storyType }) => + console.log(`NavigationContext goToViewStoryPage(${targetId}, ${storyType})`), onCommunityCreated: (communityId) => console.log(`NavigationContext onCommunityCreated(${communityId})`), onEditCommunity: (communityId) => @@ -161,13 +168,13 @@ if (process.env.NODE_ENV !== 'production') { goToUserProfilePage: (userId) => console.log(`NavigationContext goToUserProfilePage(${userId})`), goToPostDetailPage: (postId) => console.log(`NavigationContext goToPostDetailPage(${postId})`), - goToViewStoryPage: (targetId, targetType, storyType) => - console.log(`NavigationContext goToViewStoryPage(${targetId}) ${targetType} ${storyType}`), goToCommunityProfilePage: (communityId) => console.log(`NavigationContext goToCommunityProfilePage(${communityId})`), goToSocialGlobalSearchPage: (tab) => console.log(`NavigationContext goToSocialGlobalSearchPage(${tab})`), goToSelectPostTargetPage: () => console.log('NavigationContext goToTargetPage()'), + goToDraftStoryPage: ({ targetId, targetType, mediaType }) => + console.log(`NavigationContext goToDraftStoryPage(${targetId}, ${targetType}, ${mediaType})`), }; } @@ -188,11 +195,16 @@ interface NavigationProviderProps { onClickCategory?: (categoryId: string) => void; onClickCommunity?: (communityId: string) => void; onClickUser?: (userId: string) => void; - onClickStory?: ( - storyId: string, - storyType: 'communityFeed' | 'globalFeed', - targetId?: string[], - ) => void; + goToViewStoryPage?: (context: { + storyId: string; + storyType: 'communityFeed' | 'globalFeed'; + targetId?: string[]; + }) => void; + goToDraftStoryPage?: (context: { + targetId: string; + targetType: string; + mediaType: AmityStoryMediaType; + }) => void; onCommunityCreated?: (communityId: string) => void; onEditCommunity?: (communityId: string, options?: { tab?: string }) => void; onEditUser?: (userId: string) => void; @@ -362,8 +374,8 @@ export default function NavigationProvider({ popPage(); }, [onChangePage, onBack, popPage]); - const handleClickStory = useCallback( - (targetId, storyType, targetIds) => { + const goToViewStoryPage = useCallback( + ({ targetId, storyType, targetIds }) => { const next = { type: PageTypes.ViewStoryPage, targetId, @@ -406,23 +418,6 @@ export default function NavigationProvider({ [onChangePage, pushPage], ); - const goToViewStoryPage = useCallback( - (targetId, targetType, storyType, targetIds) => { - const next = { - type: PageTypes.ViewStoryPage, - context: { - targetId, - targetType, - storyType, - targetIds, - }, - }; - - pushPage(next); - }, - [onChangePage, pushPage], - ); - const goToCommunityProfilePage = useCallback( (communityId) => { const next = { @@ -456,6 +451,22 @@ export default function NavigationProvider({ pushPage(next); }, [onChangePage, pushPage]); + const goToDraftStoryPage = useCallback( + ({ targetId, targetType, mediaType }) => { + const next = { + type: PageTypes.DraftPage, + targetId, + targetType, + mediaType, + }; + + if (onChangePage) return onChangePage(next); + + pushPage(next); + }, + [onChangePage, pushPage], + ); + return ( <NavigationContext.Provider value={{ @@ -464,7 +475,6 @@ export default function NavigationProvider({ onClickCategory: handleClickCategory, onClickCommunity: handleClickCommunity, onClickUser: handleClickUser, - onClickStory: handleClickStory, onCommunityCreated: handleCommunityCreated, onEditCommunity: handleEditCommunity, onEditUser: handleEditUser, @@ -476,6 +486,7 @@ export default function NavigationProvider({ goToCommunityProfilePage, goToViewStoryPage, goToSelectPostTargetPage, + goToDraftStoryPage, setNavigationBlocker, }} > diff --git a/src/v4/core/providers/PageBehaviorProvider.tsx b/src/v4/core/providers/PageBehaviorProvider.tsx index 23ecf9f64..f5782587c 100644 --- a/src/v4/core/providers/PageBehaviorProvider.tsx +++ b/src/v4/core/providers/PageBehaviorProvider.tsx @@ -94,12 +94,11 @@ export const PageBehaviorProvider: React.FC<PageBehaviorProviderProps> = ({ if (pageBehavior?.AmityGlobalFeedComponentBehavior?.goToViewStoryPage) { return pageBehavior?.AmityGlobalFeedComponentBehavior.goToViewStoryPage(context); } - goToViewStoryPage( - context.targetId, - context.targetType, - context.storyType, - context.targetIds, - ); + goToViewStoryPage({ + targetId: context.targetId, + targetType: context.targetType, + storyType: context.storyType, + }); }, }, AmityPostDetailPageBehavior: {}, diff --git a/src/v4/social/components/PostContent/PostContent.tsx b/src/v4/social/components/PostContent/PostContent.tsx index 5ca1e8da1..38e712054 100644 --- a/src/v4/social/components/PostContent/PostContent.tsx +++ b/src/v4/social/components/PostContent/PostContent.tsx @@ -7,7 +7,7 @@ import { MenuButton } from '~/v4/social/elements/MenuButton'; import { ShareButton } from '~/v4/social/elements/ShareButton'; import useCommunity from '~/v4/core/hooks/collections/useCommunity'; import useUser from '~/v4/core/hooks/objects/useUser'; -import { BottomSheet, Typography } from '~/v4/core/components'; +import { Typography } from '~/v4/core/components'; import AngleRight from '~/v4/icons/AngleRight'; import { UserAvatar } from '~/v4/social/internal-components/UserAvatar'; import { CommentButton } from '~/v4/social/elements/CommentButton'; diff --git a/src/v4/social/components/StoryTab/StoryTab.tsx b/src/v4/social/components/StoryTab/StoryTab.tsx index 7004368ed..64c1edcdf 100644 --- a/src/v4/social/components/StoryTab/StoryTab.tsx +++ b/src/v4/social/components/StoryTab/StoryTab.tsx @@ -1,4 +1,7 @@ import React from 'react'; +import { useNavigation } from '~/v4/core/providers/NavigationProvider'; +import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; +import { useStoryContext } from '~/v4/social/providers/StoryProvider'; import { StoryTabCommunityFeed } from './StoryTabCommunity'; import { StoryTabGlobalFeed } from './StoryTabGlobalFeed'; @@ -6,17 +9,65 @@ import { StoryTabGlobalFeed } from './StoryTabGlobalFeed'; type StoryTabType = 'communityFeed' | 'globalFeed'; type StoryTabProps<T extends StoryTabType> = { + pageId?: string; type: T; communityId?: T extends 'communityFeed' ? string : never; }; -export const StoryTab = <T extends StoryTabType>({ type, communityId }: StoryTabProps<T>) => { +export const StoryTab = <T extends StoryTabType>({ + pageId = '*', + type, + communityId, +}: StoryTabProps<T>) => { + const componentId = 'story_tab_component'; + const { AmityGlobalFeedComponentBehavior } = usePageBehavior(); + const { goToViewStoryPage, goToDraftStoryPage } = useNavigation(); + const { setFile } = useStoryContext(); + const renderStoryTab = () => { switch (type) { case 'communityFeed': - return <StoryTabCommunityFeed communityId={communityId || ''} />; + return ( + <StoryTabCommunityFeed + pageId={pageId} + componentId={componentId} + communityId={communityId || ''} + onFileChange={(file) => { + setFile(file); + if (file) { + goToDraftStoryPage({ + targetId: communityId || '', + targetType: 'community', + mediaType: file.type.includes('image') + ? { type: 'image', url: URL.createObjectURL(file) } + : { type: 'video', url: URL.createObjectURL(file) }, + }); + } + }} + onStoryClick={() => + goToViewStoryPage({ + targetId: communityId || '', + targetType: 'community', + storyType: 'communityFeed', + }) + } + /> + ); case 'globalFeed': - return <StoryTabGlobalFeed />; + return ( + <StoryTabGlobalFeed + pageId={pageId} + componentId={componentId} + goToViewStoryPage={({ storyTarget, storyTargets }) => { + AmityGlobalFeedComponentBehavior.goToViewStoryPage({ + targetId: storyTarget.targetId, + targetType: storyTarget.targetType, + storyType: 'globalFeed', + targetIds: storyTargets.map((s) => s.targetId), + }); + }} + /> + ); default: return null; } diff --git a/src/v4/social/components/StoryTab/StoryTabCommunity.tsx b/src/v4/social/components/StoryTab/StoryTabCommunity.tsx index 0e23dccfb..f209f5624 100644 --- a/src/v4/social/components/StoryTab/StoryTabCommunity.tsx +++ b/src/v4/social/components/StoryTab/StoryTabCommunity.tsx @@ -2,11 +2,9 @@ import React, { useRef } from 'react'; import Truncate from 'react-truncate-markup'; import { backgroundImage as CommunityImage } from '~/icons/Community'; -import StoryRing from './StoryRing'; import useStories from '~/social/hooks/useStories'; import useSDK from '~/core/hooks/useSDK'; import { checkStoryPermission } from '~/utils'; -import { useStoryContext } from '~/v4/social/providers/StoryProvider'; import { useCommunityInfo } from '~/social/components/CommunityInfo/hooks'; import { useNavigation } from '~/social/providers/NavigationProvider'; @@ -22,20 +20,29 @@ import { import { isAdmin, isModerator } from '~/helpers/permissions'; import { FormattedMessage } from 'react-intl'; import useUser from '~/core/hooks/useUser'; +import { StoryRing } from '~/v4/social/elements/StoryRing/StoryRing'; interface StoryTabCommunityFeedProps { + pageId: string; + componentId: string; communityId: string; + onStoryClick: () => void; + onFileChange: (file: File | null) => void; } -export const StoryTabCommunityFeed: React.FC<StoryTabCommunityFeedProps> = ({ communityId }) => { - const { onClickStory } = useNavigation(); +export const StoryTabCommunityFeed: React.FC<StoryTabCommunityFeedProps> = ({ + pageId, + componentId, + communityId, + onFileChange, + onStoryClick, +}) => { const { stories } = useStories({ targetId: communityId, targetType: 'community', options: { orderBy: 'asc', sortBy: 'createdAt' }, }); const { avatarFileUrl } = useCommunityInfo(communityId); - const { setFile } = useStoryContext(); const fileInputRef = useRef<HTMLInputElement>(null); const handleAddIconClick = () => { @@ -47,13 +54,13 @@ export const StoryTabCommunityFeed: React.FC<StoryTabCommunityFeedProps> = ({ co const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => { const selectedFile = event.target.files?.[0]; if (selectedFile) { - setFile(selectedFile); + onFileChange(selectedFile); } }; const handleOnClick = () => { if (Array.isArray(stories) && stories.length === 0) return; - onClickStory(communityId, 'communityFeed'); + onStoryClick(); }; const { currentUserId, client } = useSDK(); @@ -73,8 +80,8 @@ export const StoryTabCommunityFeed: React.FC<StoryTabCommunityFeedProps> = ({ co <StoryWrapper> {hasStoryRing && ( <StoryRing - pageId="*" - componentId="story_tab_component" + pageId={pageId} + componentId={componentId} hasUnseen={hasUnSeen} uploading={uploading} isErrored={isErrored} diff --git a/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx b/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx index d36ad5436..d6b313c87 100644 --- a/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx +++ b/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx @@ -2,16 +2,28 @@ import React, { useRef, useEffect } from 'react'; import styles from './StoryTabGlobalFeed.module.css'; import { StoryTabItem } from './StoryTabItem'; import { useGlobalStoryTargets } from '~/v4/social/hooks/collections/useGlobalStoryTargets'; -import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; const STORIES_PER_PAGE = 10; -export const StoryTabGlobalFeed: React.FC = () => { +interface StoryTabGlobalFeedProps { + pageId: string; + componentId: string; + goToViewStoryPage: (data: { + storyTarget: Amity.StoryTarget; + storyTargets: Amity.StoryTarget[]; + }) => void; +} + +export const StoryTabGlobalFeed = ({ + pageId, + componentId, + goToViewStoryPage, +}: StoryTabGlobalFeedProps) => { const { stories, isLoading, hasMore, loadMoreStories } = useGlobalStoryTargets({ seenState: 'smart' as Amity.StorySeenQuery, limit: STORIES_PER_PAGE, }); - const { AmityGlobalFeedComponentBehavior } = usePageBehavior(); + const containerRef = useRef<HTMLDivElement>(null); const observerRef = useRef<IntersectionObserver | null>(null); @@ -63,15 +75,15 @@ export const StoryTabGlobalFeed: React.FC = () => { {stories.map((story) => { return ( <StoryTabItem + pageId={pageId} + componentId={componentId} key={story.targetId} targetId={story.targetId} hasUnseen={story.hasUnseen} onClick={() => - AmityGlobalFeedComponentBehavior.goToViewStoryPage({ - targetId: story.targetId, - targetType: story.targetType, - storyType: 'globalFeed', - targetIds: stories.map((story) => story.targetId), + goToViewStoryPage({ + storyTargets: stories, + storyTarget: story, }) } size={64} diff --git a/src/v4/social/components/StoryTab/StoryTabItem.tsx b/src/v4/social/components/StoryTab/StoryTabItem.tsx index 8273d46f9..a0a2980fd 100644 --- a/src/v4/social/components/StoryTab/StoryTabItem.tsx +++ b/src/v4/social/components/StoryTab/StoryTabItem.tsx @@ -1,7 +1,7 @@ import React from 'react'; import useImage from '~/core/hooks/useImage'; import useCommunity from '~/social/hooks/useCommunity'; -import StoryRing from './StoryRing'; +import { StoryRing } from '~/v4/social/elements/StoryRing'; import { PrivateIcon } from '~/social/components/community/Name/styles'; import styles from './StoryTabItem.module.css'; @@ -9,20 +9,28 @@ import { Typography } from '~/v4/core/components'; import Verified from '~/v4/icons/Verified'; interface StoryTabProps { + pageId: string; + componentId: string; targetId: string; hasUnseen: boolean; onClick: () => void; size: number; } -export const StoryTabItem: React.FC<StoryTabProps> = ({ targetId, hasUnseen, onClick }) => { +export const StoryTabItem: React.FC<StoryTabProps> = ({ + pageId, + componentId, + targetId, + hasUnseen, + onClick, +}) => { const community = useCommunity(targetId); const communityAvatar = useImage({ fileId: community?.avatarFileId, imageSize: 'small' }); return ( <div className={styles.container} onClick={onClick}> <div className={styles.avatarContainer}> - <StoryRing pageId="*" componentId="story_tab_component" hasUnseen={hasUnseen} /> + <StoryRing pageId={pageId} componentId={componentId} hasUnseen={hasUnseen} /> {community?.isOfficial && <Verified className={styles.verifiedIcon} />} <div className={styles.avatarBackground}> {communityAvatar && ( diff --git a/src/v4/social/components/ViewStoryPage/ViewStoryPage.module.css b/src/v4/social/components/ViewStoryPage/ViewStoryPage.module.css deleted file mode 100644 index 785aa6b0a..000000000 --- a/src/v4/social/components/ViewStoryPage/ViewStoryPage.module.css +++ /dev/null @@ -1,355 +0,0 @@ -.storyWrapper { - display: flex; - justify-content: center; - align-items: center; - width: 100%; - height: 100%; - margin: 2rem; - gap: 1rem; - background-color: var(--color-black); -} - -.mobileSheet { - margin: 0 auto; - width: 100%; -} - -.mobileSheet .reactModalSheetContent { - position: relative; - padding: 0 1rem; -} - -.replyingBlock { - display: flex; - justify-content: space-between; - align-items: center; - gap: 0.5rem; - padding: 0.75rem 1rem; - background-color: var(--color-gray-light); -} - -.storyDisabledCommentComposerBarContainer { - display: flex; - justify-content: center; - align-items: center; - gap: 0.5rem; - padding: 0.625rem 1rem; - border-top: 1px solid var(--color-gray-lighter); - color: var(--color-base-shade2); - font-family: var(--typography-body-font-family); - font-size: var(--typography-body-font-size); - font-weight: var(--typography-body-font-weight); - line-height: var(--typography-body-line-height); -} - -.storyCommentComposerBar { - display: flex; - justify-content: space-between; - align-items: center; - padding: 0.75rem; - background-color: var(--color-black); - color: var(--color-white); -} - -.mobileSheetContent { - position: relative; - padding: 0 1rem; -} - -.mobileSheetScroller { - height: calc(100% - 4.5rem); -} - -.mobileSheetComposeBarContainer { - position: absolute; - bottom: 0; - width: 100%; - z-index: 99999; -} - -.mobileActionSheetContent { - padding: 1rem; -} - -.closeButton { - color: var(--color-white); - cursor: pointer; -} - -.verifiedBadge { - color: var(--color-white); -} - -.dotsButton { - cursor: pointer; - color: var(--color-white); -} - -.viewCountIcon { - color: var(--color-gray-shade3); -} - -.storyContainer { - position: relative; - display: flex; - flex-direction: column; - align-items: center; -} - -.viewStoryInfoContainer { - display: flex; - flex-direction: column; - justify-content: flex-start; - width: 100%; -} - -.storyTabBarContainer { - display: flex; - flex-direction: column; - align-items: center; - padding: 0.5rem; - height: 5rem; -} - -.viewStoryCompostBarContainer { - width: 100%; - display: flex; - position: absolute; - justify-content: space-between; - align-items: center; - height: 3.5rem; - padding: 0.75rem; - background-color: var(--color-black); - bottom: 0; -} - -.viewStoryCompostBarViewIconContainer { - display: flex; - align-items: center; - justify-content: space-between; - color: var(--color-white); - gap: 0.25rem; -} - -.viewStoryCompostBarEngagementContainer { - display: flex; - align-items: center; - justify-content: space-between; - color: var(--color-white); - gap: 0.75rem; -} - -.viewStoryCompostBarEngagementIconContainer { - display: flex; - align-items: center; - justify-content: space-between; - color: var(--color-white); - gap: 0.25rem; - border-radius: 50%; - padding: 0.5rem 0.625rem; - background-color: var(--color-base-default); -} - -.storyContentContainer { - display: flex; - flex-direction: column; - align-items: center; - padding: 0.5rem; - height: 5rem; -} - -.viewStoryContainer { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; -} - -.viewStoryContent { - position: relative; - width: 23.438rem; - height: 40.875rem; - display: flex; - flex-direction: column; - overflow: hidden; - z-index: 2; -} - -.viewStoryOverlay { - position: absolute; - width: 100%; - height: 100%; - background: linear-gradient(180deg, rgb(0 0 0 / 16%) 55.05%, rgb(255 255 255 / 0%) 96.52%); - z-index: 3; -} - -.viewStoryHeaderContainer { - z-index: 2; - position: absolute; - width: 100%; - display: flex; - justify-content: space-between; - align-items: center; - flex-direction: column; - padding: 1.5rem 1rem 0.625rem; - gap: 0.5rem; -} - -.avatarContainer { - position: relative; - display: flex; - justify-content: center; - align-items: center; - width: 2.5rem; - height: 2.5rem; - overflow: hidden; -} - -.avatar { - width: 2.5rem; - height: 2.5rem; - border-radius: 50%; -} - -.viewStoryHeaderListActionsContainer { - display: flex; - gap: 1.25rem; - justify-content: flex-end; - align-items: center; -} - -.viewStoryHeadingInfoContainer { - display: flex; - justify-content: space-between; - width: 100%; - gap: 0.75rem; - align-items: center; -} - -.viewStoryHeading { - display: flex; - gap: 0.25rem; - color: var(--color-white); - font-size: 0.938rem; - font-style: normal; - font-weight: 600; - line-height: 1.25rem; - letter-spacing: -0.24px; - margin-right: 0.25rem; - align-items: center; -} - -.viewStorySubheading { - display: inline-flex; - gap: 0.25rem; - margin-bottom: 0.25rem; - color: var(--color-white); - font-size: 0.813rem; - font-style: normal; - font-weight: 400; - line-height: 1.25rem; - letter-spacing: -0.1px; -} - -.viewStoryImageContainer { - width: 100%; - height: 100%; - display: flex; - justify-content: center; - align-items: center; -} - -.viewStoryImageContent { - flex: 1; - width: 100%; - height: 100%; - object-fit: cover; -} - -.viewStoryVideoContent { - flex: 1; - width: 100%; - height: 100%; -} - -.viewStoryContentContainer { - flex: 1; - width: 100%; - height: 100%; -} - -.tabBarContainer { - display: flex; - justify-content: space-between; - align-items: center; - padding: 1rem; - height: 5rem; -} - -.flexContainer { - display: flex; - flex-direction: column; - min-height: 100%; -} - -.actionList { - list-style: none; -} - -.actionItem { - margin-bottom: 0.75rem; -} - -.actionButton { - border: none; - width: 100%; - display: flex; - align-items: center; - gap: 0.5rem; - background-color: transparent; - cursor: pointer; - justify-content: flex-start; -} - -.deleteIcon { - width: 1.5rem; - height: 1.5rem; - color: var(--color-base-default); -} - -.mobileSheetHeader { - text-align: center; - border-bottom: 1px solid var(--color-gray-lighter); - padding-bottom: 0.5rem; - color: var(--color-base-default); - font-family: var(--typography-title-font-family); - font-size: var(--typography-title-font-size); - font-weight: var(--typography-title-font-weight); - line-height: var(--typography-title-line-height); -} - -.storyActionItem { - display: flex; - justify-content: flex-start; - align-items: center; - gap: 0.5rem; - width: 100%; -} - -.storyActionItemText { - color: var(--color-base-main); - font-family: var(--typography-body-bold-font-family); - font-size: var(--typography-body-bold-font-size); - font-weight: var(--typography-body-bold-font-weight); - line-height: var(--typography-body-bold-line-height); -} - -.storyArrowButton { - cursor: pointer; - border: none; - background-color: transparent; -} - -.hiddenInput { - display: none; -} diff --git a/src/v4/social/components/ViewStoryPage/index.tsx b/src/v4/social/components/ViewStoryPage/index.tsx deleted file mode 100644 index 3049611e3..000000000 --- a/src/v4/social/components/ViewStoryPage/index.tsx +++ /dev/null @@ -1,323 +0,0 @@ -import React, { useEffect, useRef, useState } from 'react'; -import Stories from 'react-insta-stories'; -import { StoryRepository } from '@amityco/ts-sdk'; -import { extractColors } from 'extract-colors'; -import { FinalColor } from 'extract-colors/lib/types/Color'; -import useImage from '~/core/hooks/useImage'; -import { useIntl } from 'react-intl'; - -import { useMedia } from 'react-use'; -import useStories from '~/social/hooks/useStories'; -import useSDK from '~/core/hooks/useSDK'; - -import { isNonNullable } from '~/v4/helpers/utils'; -import { ArrowLeftCircle, ArrowRightCircle, Trash2Icon } from '~/icons'; - -import styles from './ViewStoryPage.module.css'; -import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; -import { CreateStoryButton } from '../../elements'; - -import { renderers } from '../../internal-components/StoryViewer/Renderers'; - -import { AmityDraftStoryPage } from '../../pages'; -import { useStoryContext } from '../../providers/StoryProvider'; -import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; -import { useNotifications } from '~/v4/core/providers/NotificationProvider'; - -interface StoryViewerProps { - pageId: 'story_page'; - targetId: string; - duration?: number; - onClose: () => void; -} - -const StoryViewer = ({ pageId, targetId, duration = 5000, onClose }: StoryViewerProps) => { - const { getConfig, isExcluded } = useCustomization(); - const pageConfig = getConfig(`${pageId}/*/*`); - const isPageExcluded = isExcluded(`${pageId}/*/*`); - const { confirm } = useConfirmContext(); - const notification = useNotifications(); - - if (isPageExcluded) return null; - - const progressBarElementConfig = getConfig(`${pageId}/*/progress_bar`); - - const { stories } = useStories({ - targetId, - targetType: 'community', - options: { - orderBy: 'asc', - sortBy: 'createdAt', - }, - }); - - const fileInputRef = useRef<HTMLInputElement>(null); - - const handleAddIconClick = (e: React.MouseEvent) => { - e.stopPropagation(); - e.preventDefault(); - - if (fileInputRef.current) { - fileInputRef.current.click(); - } - }; - - const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => { - const selectedFile = event.target.files?.[0]; - if (selectedFile) { - onChange(selectedFile as File); - } - }; - - const { currentUserId } = useSDK(); - - const { formatMessage } = useIntl(); - const isMobile = useMedia('(max-width: 768px)'); - - const [currentIndex, setCurrentIndex] = useState(0); - const { file, setFile } = useStoryContext(); - const [colors, setColors] = useState<FinalColor[]>([]); - - const isStoryCreator = stories[currentIndex]?.creator?.userId === currentUserId; - - const confirmDeleteStory = (storyId: string) => { - const isLastStory = currentIndex === 0; - confirm({ - title: formatMessage({ id: 'storyViewer.action.confirmModal.title' }), - content: formatMessage({ id: 'storyViewer.action.confirmModal.content' }), - okText: formatMessage({ id: 'delete' }), - onOk: async () => { - previousStory(); - if (isLastStory) { - onClose(); - } - await StoryRepository.softDeleteStory(storyId); - notification.success({ - content: formatMessage({ id: 'storyViewer.notification.deleted' }), - }); - }, - }); - }; - - const onChange = (file: File) => { - setFile(file); - }; - - const deleteStory = async (storyId: string) => { - confirmDeleteStory(storyId); - }; - - const onCreateStory = async ( - file: File, - imageMode: 'fit' | 'fill', - metadata?: Amity.Metadata, - items?: Amity.StoryItem[], - ) => { - onClose(); - const formData = new FormData(); - formData.append('files', file); - setFile(null); - if (file?.type.includes('image')) { - const { data: imageData } = await StoryRepository.createImageStory( - 'community', - targetId, - formData, - metadata, - imageMode, - items, - ); - if (imageData) { - notification.success({ - content: formatMessage({ id: 'storyViewer.notification.success' }), - }); - } - } else { - const { data: videoData } = await StoryRepository.createVideoStory( - 'community', - targetId, - formData, - metadata, - items, - ); - if (videoData) { - notification.success({ - content: formatMessage({ id: 'storyViewer.notification.success' }), - }); - } - } - }; - - const discardStory = () => { - setFile(null); - }; - - const addStoryButton = ( - <CreateStoryButton pageId="story_page" componentId="*" onClick={handleAddIconClick} /> - ); - - const formattedStories = stories?.map((story) => { - const isImage = story?.dataType === 'image'; - const url = isImage ? story?.imageData?.fileUrl : story?.videoData?.videoUrl?.['720p']; - - return { - ...story, - url, - type: isImage ? 'image' : 'video', - actions: [ - isStoryCreator - ? { - name: 'delete', - action: () => deleteStory(story?.storyId as string), - icon: ( - <Trash2Icon - style={{ - fill: 'var(--asc-color-black)', - }} - /> - ), - } - : null, - ].filter(isNonNullable), - handleAddIconClick, - onCreateStory, - discardStory, - addStoryButton, - fileInputRef, - }; - }); - - const avatarUrl = useImage({ - fileId: stories[currentIndex]?.community?.avatarFileId, - imageSize: 'small', - }); - - const nextStory = () => { - if (currentIndex === stories.length - 1) { - onClose(); - return; - } - setCurrentIndex(currentIndex + 1); - }; - - const previousStory = () => { - if (currentIndex === 0) return; - setCurrentIndex(currentIndex - 1); - }; - - const targetRootId = 'asc-uikit-stories-viewer'; - - const storyStyles = { - width: '100%', - height: '100%', - objectFit: - stories[currentIndex]?.dataType === 'image' && - stories[currentIndex]?.data?.imageDisplayMode === 'fill' - ? 'cover' - : 'contain', - background: `linear-gradient( - 180deg, - ${colors?.length > 0 ? colors[0].hex : '#000'} 0%, - ${colors?.length > 0 ? colors[colors?.length - 1].hex : '#000'} 100% - )`, - }; - - const increaseIndex = () => { - setCurrentIndex(currentIndex + 1); - }; - - useEffect(() => { - if (stories[stories.length - 1]?.syncState === 'syncing') { - setCurrentIndex(stories.length - 1); - } - if (stories[currentIndex]) { - stories[currentIndex]?.analytics.markAsSeen(); - } - }, [currentIndex, stories]); - - useEffect(() => { - const extractColorsFromImage = async (url: string) => { - const colorsFromImage = await extractColors(url, { - crossOrigin: 'anonymous', - }); - - setColors(colorsFromImage); - }; - - if (file?.type.includes('image') || stories[currentIndex]?.dataType === 'image') { - extractColorsFromImage((stories[currentIndex]?.imageData?.fileUrl as string) ?? file); - } else { - setColors([]); - } - }, [stories, file, currentIndex]); - - if (file) { - return ( - <AmityDraftStoryPage - mediaType={ - file.type.includes('image') - ? { type: 'image', url: URL.createObjectURL(file) } - : { type: 'video', url: URL.createObjectURL(file) } - } - targetId={targetId} - targetType="community" - /> - ); - } - - return ( - <div className={styles.storyWrapper} data-qa-anchor="story_page"> - {!isMobile && ( - <button className={styles.storyArrowButton} onClick={previousStory}> - <ArrowLeftCircle /> - </button> - )} - <div className={styles.viewStoryContainer} id={targetRootId}> - <input - className={styles.hiddenInput} - ref={fileInputRef} - type="file" - accept="image/*,video/*" - onChange={handleFileChange} - /> - <div className={styles.viewStoryContent}> - <div className={styles.viewStoryOverlay} /> - {formattedStories?.length > 0 ? ( - // NOTE: Do not use isPaused prop, it will cause the first video story skipped - <Stories - progressStyles={{ - backgroundColor: - progressBarElementConfig.progress_color || 'var(--asc-color-white)', - }} - progressWrapperStyles={{ - backgroundColor: - progressBarElementConfig.background_color || 'var(--asc-color-secondary-shade3)', - }} - width="100%" - height="100%" - storyStyles={storyStyles} - preventDefault={!isMobile} - currentIndex={currentIndex} - stories={formattedStories} - // TO FIX: need to override custom type of renderers from react-insta-stories library - // @ts-ignore - renderers={renderers} - defaultInterval={duration} - onStoryStart={() => stories[currentIndex]?.analytics.markAsSeen()} - onStoryEnd={increaseIndex} - onNext={nextStory} - onPrevious={previousStory} - onAllStoriesEnd={onClose} - /> - ) : null} - </div> - </div> - {!isMobile && ( - <button className={styles.storyArrowButton} onClick={nextStory}> - <ArrowRightCircle /> - </button> - )} - </div> - ); -}; - -export default StoryViewer; diff --git a/src/v4/social/components/StoryTab/StoryRing.module.css b/src/v4/social/elements/StoryRing/StoryRing.module.css similarity index 100% rename from src/v4/social/components/StoryTab/StoryRing.module.css rename to src/v4/social/elements/StoryRing/StoryRing.module.css diff --git a/src/v4/social/components/StoryTab/StoryRing.tsx b/src/v4/social/elements/StoryRing/StoryRing.tsx similarity index 76% rename from src/v4/social/components/StoryTab/StoryRing.tsx rename to src/v4/social/elements/StoryRing/StoryRing.tsx index c99a69c5d..376c8a0f5 100644 --- a/src/v4/social/components/StoryTab/StoryRing.tsx +++ b/src/v4/social/elements/StoryRing/StoryRing.tsx @@ -1,15 +1,6 @@ import React from 'react'; import styles from './StoryRing.module.css'; -import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; - -interface StoryRingProps extends React.SVGProps<SVGSVGElement> { - pageId?: '*'; - componentId?: 'story_tab_component'; - hasUnseen?: boolean; - uploading?: boolean; - isErrored?: boolean; - size?: number; -} +import { useAmityElement } from '~/v4/core/hooks/uikit/index'; const EmptyStateRingSvg = ({ pageId, @@ -22,8 +13,14 @@ const EmptyStateRingSvg = ({ elementId: string; size: number; }) => { - const { getConfig } = useCustomization(); - const elementConfig = getConfig(`${pageId}/${componentId}/${elementId}`); + const { config } = useAmityElement({ + pageId, + componentId, + elementId, + }); + + const progressColors = config?.progress_color as string[]; + return ( <svg xmlns="http://www.w3.org/2000/svg" @@ -41,8 +38,8 @@ const EmptyStateRingSvg = ({ y2="45.75" gradientUnits="userSpaceOnUse" > - <stop stopColor={elementConfig?.progress_color?.[0]} /> - <stop offset={1} stopColor={elementConfig?.progress_color?.[1]} /> + <stop stopColor={progressColors[0]} /> + <stop offset={1} stopColor={progressColors[1]} /> </linearGradient> </defs> <circle cx={size / 2} cy={size / 2} r={size / 2 - 1} className={styles.emptyStateRing} /> @@ -61,8 +58,14 @@ const HasSeenRingSvg = ({ elementId: string; size: number; }) => { - const { getConfig } = useCustomization(); - const elementConfig = getConfig(`${pageId}/${componentId}/${elementId}`); + const { config } = useAmityElement({ + pageId, + componentId, + elementId, + }); + + const progressColors = config?.progress_color as string[]; + return ( <svg xmlns="http://www.w3.org/2000/svg" @@ -80,8 +83,8 @@ const HasSeenRingSvg = ({ y2="45.75" gradientUnits="userSpaceOnUse" > - <stop stopColor={elementConfig?.progress_color?.[0]} /> - <stop offset={1} stopColor={elementConfig?.progress_color?.[1]} /> + <stop stopColor={progressColors[0]} /> + <stop offset={1} stopColor={progressColors[1]} /> </linearGradient> </defs> <circle @@ -110,8 +113,13 @@ const UploadingRingSvg = ({ elementId: string; size: number; }) => { - const { getConfig } = useCustomization(); - const elementConfig = getConfig(`${pageId}/${componentId}/${elementId}`); + const { config } = useAmityElement({ + pageId, + componentId, + elementId, + }); + + const progressColors = config?.progress_color as string[]; return ( <svg @@ -130,8 +138,8 @@ const UploadingRingSvg = ({ y2="45.75" gradientUnits="userSpaceOnUse" > - <stop stopColor={elementConfig?.progress_color?.[0]} /> - <stop offset={1} stopColor={elementConfig?.progress_color?.[1]} /> + <stop stopColor={progressColors[0]} /> + <stop offset={1} stopColor={progressColors[1]} /> </linearGradient> </defs> <circle @@ -152,9 +160,18 @@ const UploadingRingSvg = ({ ); }; -const StoryRing = ({ +interface StoryRingProps extends React.SVGProps<SVGSVGElement> { + pageId?: string; + componentId?: string; + hasUnseen?: boolean; + uploading?: boolean; + isErrored?: boolean; + size?: number; +} + +export const StoryRing = ({ pageId = '*', - componentId = 'story_tab_component', + componentId = '*', hasUnseen = false, uploading = false, isErrored = false, @@ -162,10 +179,13 @@ const StoryRing = ({ ...props }: StoryRingProps) => { const elementId = 'story_ring'; - const { isExcluded } = useCustomization(); - const isElementExcluded = isExcluded(`${pageId}/${componentId}/${elementId}`); + const { isExcluded, config } = useAmityElement({ + pageId, + componentId, + elementId, + }); - if (isElementExcluded) return null; + if (isExcluded) return null; if (isErrored) { return ( <svg @@ -213,5 +233,3 @@ const StoryRing = ({ /> ); }; - -export default StoryRing; diff --git a/src/v4/social/elements/StoryRing/index.tsx b/src/v4/social/elements/StoryRing/index.tsx new file mode 100644 index 000000000..ef21c319c --- /dev/null +++ b/src/v4/social/elements/StoryRing/index.tsx @@ -0,0 +1 @@ +export { StoryRing } from './StoryRing'; diff --git a/src/v4/social/hooks/collections/useGlobalStoryTargets.tsx b/src/v4/social/hooks/collections/useGlobalStoryTargets.tsx index 571a7b122..959cd327b 100644 --- a/src/v4/social/hooks/collections/useGlobalStoryTargets.tsx +++ b/src/v4/social/hooks/collections/useGlobalStoryTargets.tsx @@ -16,6 +16,7 @@ export const useGlobalStoryTargets = ( } }; + return { stories: items, hasMore, diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/AutoPlayContent.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/AutoPlayContent.tsx index 6fa161af7..f4067eab6 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/AutoPlayContent.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/AutoPlayContent.tsx @@ -1,7 +1,7 @@ import React, { useEffect } from 'react'; -import { Renderer, Tester } from 'react-insta-stories/dist/interfaces'; +import { CustomRendererProps, RendererProps, Tester } from './types'; -export const renderer: Renderer = (props) => { +export const renderer = (props: CustomRendererProps) => { useEffect(() => { props.action('play'); }, [props.story]); diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Default.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Default.tsx index 381b968a8..98971f825 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Default.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Default.tsx @@ -1,7 +1,7 @@ import React, { useEffect } from 'react'; -import { Renderer, Tester } from 'react-insta-stories/dist/interfaces'; +import { CustomRenderer, Tester } from './types'; -export const renderer: Renderer = ({ story, action }) => { +export const renderer: CustomRenderer = ({ story, action }) => { useEffect(() => { action('play'); }, [story]); diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index 0790120c3..4c4565b1d 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -20,22 +20,25 @@ import { Typography } from '~/v4/core/components'; import { Button } from '~/v4/core/components/Button'; import useCommunityMembersCollection from '~/v4/social/hooks/collections/useCommunityMembersCollection'; -import { PageTypes, useNavigation } from '~/v4/core/providers/NavigationProvider'; import useSDK from '~/v4/core/hooks/useSDK'; import useImage from '~/v4/core/hooks/useImage'; import useUser from '~/v4/core/hooks/objects/useUser'; import useCommunityStoriesSubscription from '~/v4/social/hooks/useCommunityStoriesSubscription'; -import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; import { LIKE_REACTION_KEY } from '~/v4/social/constants/reactions'; import { checkStoryPermission, formatTimeAgo, isAdmin, isModerator } from '~/v4/social/utils'; -export const renderer: CustomRenderer = ({ story, action, config }) => { +export const renderer: CustomRenderer = ({ + story, + action, + config, + onClose, + onSwipeDown, + onClickCommunity, +}) => { const { formatMessage } = useIntl(); - const { AmityStoryViewPageBehavior } = usePageBehavior(); - const { page, onChangePage, onClickCommunity } = useNavigation(); const [loaded, setLoaded] = useState(false); const [isOpenBottomSheet, setIsOpenBottomSheet] = useState(false); const [isOpenCommentSheet, setIsOpenCommentSheet] = useState(false); @@ -122,11 +125,7 @@ export const renderer: CustomRenderer = ({ story, action, config }) => { transition: { duration: 0.3, ease: 'easeOut' }, }) .then(() => { - if (page.type === PageTypes.ViewStoryPage && page.context.storyType === 'globalFeed') { - onChangePage(PageTypes.SocialHomePage); - } else { - onClickCommunity(community?.communityId as string); - } + onSwipeDown?.(); }); }; @@ -145,11 +144,8 @@ export const renderer: CustomRenderer = ({ story, action, config }) => { }; const handleOnClose = () => { - if (page.type === PageTypes.ViewStoryPage && page.context.storyType === 'globalFeed') { - AmityStoryViewPageBehavior.onCloseAction(); - return; - } - onClickCommunity(community?.communityId as string); + console.log('handleOnClose'); + onClose(); }; useEffect(() => { @@ -210,7 +206,7 @@ export const renderer: CustomRenderer = ({ story, action, config }) => { onPause={pause} onAction={openBottomSheet} onAddStory={handleAddIconClick} - onClickCommunity={() => onClickCommunity(community?.communityId as string)} + onClickCommunity={() => onClickCommunity?.()} onClose={handleOnClose} addStoryButton={addStoryButton} /> diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx index 203a9bac2..6a30dd927 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx @@ -17,8 +17,6 @@ import Footer from '~/v4/social/internal-components/StoryViewer/Renderers/Wrappe import { motion, PanInfo, useAnimationControls } from 'framer-motion'; import useCommunityMembersCollection from '~/v4/social/hooks/collections/useCommunityMembersCollection'; -import { PageTypes, useNavigation } from '~/v4/core/providers/NavigationProvider'; -import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; import useSDK from '~/v4/core/hooks/useSDK'; import useImage from '~/v4/core/hooks/useImage'; import useUser from '~/v4/core/hooks/objects/useUser'; @@ -30,10 +28,16 @@ import { checkStoryPermission, formatTimeAgo, isAdmin, isModerator } from '~/v4/ import rendererStyles from './Renderers.module.css'; import useCommunityStoriesSubscription from '~/v4/social/hooks/useCommunityStoriesSubscription'; -export const renderer: CustomRenderer = ({ story, action, config, messageHandler }) => { - const { AmityStoryViewPageBehavior } = usePageBehavior(); +export const renderer: CustomRenderer = ({ + story, + action, + config, + messageHandler, + onSwipeDown, + onClose, + onClickCommunity, +}) => { const { formatMessage } = useIntl(); - const { page, onClickCommunity } = useNavigation(); const [loaded, setLoaded] = useState(false); const [muted, setMuted] = useState(false); const [isPaused, setIsPaused] = useState(false); @@ -138,11 +142,7 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler transition: { duration: 0.3, ease: 'easeOut' }, }) .then(() => { - if (page.type === PageTypes.ViewStoryPage && page.context.storyType === 'globalFeed') { - AmityStoryViewPageBehavior.onCloseAction(); - } else { - onClickCommunity(community?.communityId as string); - } + onSwipeDown?.(); }); }; @@ -163,11 +163,7 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler }; const handleOnClose = () => { - if (page.type === PageTypes.ViewStoryPage && page.context.storyType === 'globalFeed') { - AmityStoryViewPageBehavior.onCloseAction(); - return; - } - onClickCommunity(community?.communityId as string); + onClose(); }; useEffect(() => { @@ -240,7 +236,7 @@ export const renderer: CustomRenderer = ({ story, action, config, messageHandler onUnmute={unmute} onAction={openBottomSheet} onAddStory={handleAddIconClick} - onClickCommunity={() => onClickCommunity(community?.communityId as string)} + onClickCommunity={() => onClickCommunity?.()} onClose={handleOnClose} addStoryButton={addStoryButton} /> diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/index.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/index.tsx index 9d188b8dd..6c09caccd 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/index.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/index.tsx @@ -2,5 +2,11 @@ import image from './Image'; import video from './Video'; import defaultRenderer from './Default'; import autoplayContent from './AutoPlayContent'; +import { CustomRenderer, Tester } from './types'; -export const renderers = [image, video, autoplayContent, defaultRenderer]; +export const renderers: { renderer: CustomRenderer; tester: Tester }[] = [ + image, + video, + autoplayContent, + defaultRenderer, +]; diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/types.ts b/src/v4/social/internal-components/StoryViewer/Renderers/types.ts index 08c67236a..0a14924a8 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/types.ts +++ b/src/v4/social/internal-components/StoryViewer/Renderers/types.ts @@ -1,19 +1,33 @@ -import { Action, Story } from 'react-insta-stories/dist/interfaces'; - -export type CustomRenderer = React.FC<{ - story: Amity.Story & - Story & { - actions: Array<{ - name: string; - action: () => void; - icon: JSX.Element; - }>; - onChange: (file: File) => void; - handleAddIconClick: (e: React.MouseEvent<Element, MouseEvent>) => void; - addStoryButton: JSX.Element; - fileInputRef: React.RefObject<HTMLInputElement>; - }; - action: Action; +import Stories from 'react-insta-stories'; + +import React from 'react'; + +type StoriesProps = React.ComponentProps<typeof Stories>; + +export type RendererObject = NonNullable<StoriesProps['renderers']>[number]; + +export type RendererProps = React.ComponentProps<RendererObject['renderer']>; +export type Renderer = RendererObject['renderer']; +export type Tester = RendererObject['tester']; + +type Action = RendererProps['action']; +type Story = RendererProps['story']; + +export type CustomStory = Story & + Amity.Story & { + actions: Array<{ + name: string; + action: () => void; + icon: JSX.Element; + }>; + onChange: (file: File) => void; + handleAddIconClick: (e: React.MouseEvent<Element, MouseEvent>) => void; + addStoryButton: JSX.Element; + fileInputRef: React.RefObject<HTMLInputElement>; + }; + +export type CustomRendererProps = RendererProps & { + story: CustomStory; config: { width?: number | string; height?: number | string; @@ -21,10 +35,15 @@ export type CustomRenderer = React.FC<{ header?: () => JSX.Element; storyStyles?: object; }; + onClose: () => void; + onSwipeDown?: () => void; + onClickCommunity?: () => void; messageHandler: ( type: string, data: any, ) => { ack: 'OK' | 'ERROR'; }; -}>; +}; + +export type CustomRenderer = React.FC<CustomRendererProps>; diff --git a/src/v4/social/pages/Application/index.tsx b/src/v4/social/pages/Application/index.tsx index 3f98d21c4..7d1eca54f 100644 --- a/src/v4/social/pages/Application/index.tsx +++ b/src/v4/social/pages/Application/index.tsx @@ -17,7 +17,9 @@ const Application = () => { {page.type === PageTypes.SocialHomePage && <SocialHomePage />} {page.type === PageTypes.SocialGlobalSearchPage && <SocialGlobalSearchPage />} {page.type === PageTypes.PostDetailPage && <PostDetailPage id={page.context.postId} />} - {page.type === PageTypes.ViewStoryPage && <ViewStoryPage type="globalFeed" />} + {page.type === PageTypes.ViewStoryPage && ( + <ViewStoryPage type="globalFeed" targetId={page.context.targetId} /> + )} {/* {page.type === PageTypes.SelectPostTargetPage && <SelectPostTargetPage />} */} </div> </StoryProvider> diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.module.css b/src/v4/social/pages/DraftsPage/DraftsPage.module.css index 08caaefd6..83edd6f7c 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.module.css +++ b/src/v4/social/pages/DraftsPage/DraftsPage.module.css @@ -27,7 +27,7 @@ align-items: center; padding: var(--asc-spacing-s1); background-color: transparent; - color: var(--color-white); + color: var(--asc-color-white); } .topRightButtons { @@ -47,8 +47,8 @@ border-top-right-radius: var(--asc-border-radius-lg); background: linear-gradient( 180deg, - var(--draft-image-container-color-0, var(--asc-color-black)) 0%, - var(--draft-image-container-color-last, var(--asc-color-black)) 100% + var(--asc-draft-image-container-color-0, var(--asc-color-black)) 0%, + var(--asc-draft-image-container-color-last, var(--asc-color-black)) 100% ); position: relative; width: 100%; @@ -107,7 +107,7 @@ .progressFill { height: 100%; - background-color: var(--color-white); + background-color: var(--asc-color-white); transition: width 0.1s linear; } diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index 1d4ff4754..9da15bca6 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -19,30 +19,40 @@ import { StoryRepository } from '@amityco/ts-sdk'; import { HyperLinkConfig } from '~/v4/social/components'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; import { useNotifications } from '~/v4/core/providers/NotificationProvider'; -import { useNavigation } from '~/social/providers/NavigationProvider'; -import { PageTypes } from '~/social/constants'; -import { BaseVideoPreview } from '../../internal-components/VideoPreview'; +import { BaseVideoPreview } from '~/v4/social/internal-components/VideoPreview'; import { useCommunityInfo } from '~/social/components/CommunityInfo/hooks'; import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; +import { useNavigation } from '~/v4/core/providers/NavigationProvider'; -type AmityStoryMediaType = { type: 'image'; url: string } | { type: 'video'; url: string }; +export type AmityStoryMediaType = { type: 'image'; url: string } | { type: 'video'; url: string }; -type AmityDraftStoryPageProps = { - targetId?: string; +export type AmityDraftStoryPageProps = { + targetId: string; targetType: Amity.StoryTargetType; mediaType?: AmityStoryMediaType; }; -type HyperLinkFormInputs = { +export type HyperLinkFormInputs = { url: string; customText?: string; }; -const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStoryPageProps) => { +export const PlainDraftStoryPage = ({ + targetId, + targetType, + mediaType, + goToCommunityPage, + goToGlobalFeedPage, + onDiscardCreateStory, + storyType, +}: AmityDraftStoryPageProps & { + goToCommunityPage: (communityId: string) => void; + goToGlobalFeedPage: () => void; + onDiscardCreateStory: () => void; + storyType: 'communityFeed' | 'globalFeed'; +}) => { const pageId = 'create_story_page'; - const { page, onChangePage, onClickCommunity } = useNavigation(); const { file, setFile } = useStoryContext(); - const { AmityDraftStoryPageBehavior } = usePageBehavior(); const [isHyperLinkBottomSheetOpen, setIsHyperLinkBottomSheetOpen] = useState(false); const { confirm } = useConfirmContext(); const notification = useNotifications(); @@ -91,12 +101,10 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor const formData = new FormData(); formData.append('files', file); setFile(null); - if (page.type === PageTypes.ViewStory && page.storyType === 'globalFeed') { - onChangePage(PageTypes.NewsFeed); + if (storyType === 'globalFeed') { + goToGlobalFeedPage(); } else { - if (page.communityId) { - onClickCommunity(page.communityId); - } + goToCommunityPage(targetId); } if (mediaType?.type === 'image' && targetId) { const { data: imageData } = await StoryRepository.createImageStory( @@ -141,7 +149,7 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor okText: formatMessage({ id: 'delete' }), onOk: () => { setFile(null); - AmityDraftStoryPageBehavior.onCloseAction(); + onDiscardCreateStory(); }, }); }; @@ -217,20 +225,12 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor <div id="asc-uikit-create-story" className={styles.draftPage}> <div className={styles.headerContainer}> <div className={styles.header}> - <BackButton pageId="create_story_page" componentId="*" onClick={discardCreateStory} /> + <BackButton pageId={pageId} onClick={discardCreateStory} /> <div className={styles.topRightButtons}> {mediaType?.type === 'image' && ( - <AspectRatioButton - pageId="create_story_page" - componentId="*" - onClick={onClickImageMode} - /> + <AspectRatioButton pageId={pageId} onClick={onClickImageMode} /> )} - <HyperLinkButton - pageId="create_story_page" - componentId="*" - onClick={handleOnClickHyperLinkActionButton} - /> + <HyperLinkButton pageId={pageId} onClick={handleOnClickHyperLinkActionButton} /> </div> </div> </div> @@ -276,7 +276,7 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor )} <HyperLinkConfig - pageId="*" + pageId={pageId} isOpen={isHyperLinkBottomSheetOpen} onClose={handleHyperLinkBottomSheetClose} onSubmit={onSubmitHyperLink} @@ -286,8 +286,7 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor <div className={styles.footer}> <ShareStoryButton - pageId="*" - componentId="*" + pageId={pageId} avatar={community.avatarFileUrl} onClick={() => onCreateStory(file, imageMode, {}, hyperLink[0]?.data?.url ? hyperLink : []) @@ -299,4 +298,19 @@ const AmityDraftStoryPage = ({ targetId, targetType, mediaType }: AmityDraftStor ); }; +export const AmityDraftStoryPage = (props: AmityDraftStoryPageProps) => { + const { page } = useNavigation(); + const { AmityDraftStoryPageBehavior } = usePageBehavior(); + + return ( + <PlainDraftStoryPage + {...props} + onDiscardCreateStory={() => AmityDraftStoryPageBehavior.onCloseAction()} + goToCommunityPage={(communityId) => AmityDraftStoryPageBehavior.onCloseAction()} + goToGlobalFeedPage={() => AmityDraftStoryPageBehavior.onCloseAction()} + storyType={page.type === 'communityFeed' ? 'communityFeed' : 'globalFeed'} + /> + ); +}; + export default AmityDraftStoryPage; diff --git a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx index fbedc7291..c9050f734 100644 --- a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx @@ -14,8 +14,6 @@ import { Trash2Icon } from '~/icons'; import { isNonNullable } from '~/v4/helpers/utils'; import { extractColors } from 'extract-colors'; -import { useNavigation } from '~/social/providers/NavigationProvider'; - import { HiddenInput, StoryArrowLeftButton, @@ -33,15 +31,28 @@ import { checkStoryPermission } from '~/utils'; import { useStoryContext } from '../../providers/StoryProvider'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; import { useNotifications } from '~/v4/core/providers/NotificationProvider'; +import { + RendererObject, + CustomRendererProps, +} from '../../internal-components/StoryViewer/Renderers/types'; interface CommunityFeedStoryProps { communityId: string; + onBack: () => void; + onClose: (communityId: string) => void; + onSwipeDown: (communityId: string) => void; + onClickCommunity: (communityId: string) => void; } const DURATION = 5000; -export const CommunityFeedStory = ({ communityId }: CommunityFeedStoryProps) => { - const { onBack } = useNavigation(); +export const CommunityFeedStory = ({ + communityId, + onBack, + onClose, + onSwipeDown, + onClickCommunity, +}: CommunityFeedStoryProps) => { const { confirm } = useConfirmContext(); const notification = useNotifications(); @@ -54,6 +65,21 @@ export const CommunityFeedStory = ({ communityId }: CommunityFeedStoryProps) => }, }); + const communityFeedRenderers = renderers.map(({ renderer, tester }) => { + const newRenderer = (props: CustomRendererProps) => + renderer({ + ...props, + onClose: () => onClose(communityId), + onSwipeDown: () => onSwipeDown(communityId), + onClickCommunity: () => onClickCommunity(communityId), + }); + + return { + renderer: newRenderer, + tester, + }; + }); + const fileInputRef = useRef<HTMLInputElement>(null); const handleAddIconClick = (e: React.MouseEvent) => { @@ -286,9 +312,7 @@ export const CommunityFeedStory = ({ communityId }: CommunityFeedStoryProps) => preventDefault={!isMobile} currentIndex={currentIndex} stories={formattedStories} - // TO FIX: need to override custom type of renderers from react-insta-stories library - // @ts-ignore - renderers={renderers} + renderers={communityFeedRenderers as RendererObject[]} defaultInterval={DURATION} onStoryStart={() => stories[currentIndex]?.analytics.markAsSeen()} onStoryEnd={increaseIndex} diff --git a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx index 1e1f1096a..a7701904d 100644 --- a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx @@ -4,56 +4,86 @@ import { useIntl } from 'react-intl'; import { FinalColor } from 'extract-colors/lib/types/Color'; import { StoryRepository } from '@amityco/ts-sdk'; import { CreateStoryButton } from '~/v4/social/elements'; - import { isNonNullable } from '~/v4/helpers/utils'; import { extractColors } from 'extract-colors'; import Stories from 'react-insta-stories'; import { renderers } from '~/v4/social/internal-components/StoryViewer/Renderers'; -import { AmityDraftStoryPage } from '~/v4/social/pages/DraftsPage'; - +import { checkStoryPermission } from '~/utils'; import { useStoryContext } from '~/v4/social/providers/StoryProvider'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; import { useNotifications } from '~/v4/core/providers/NotificationProvider'; -import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; -import { PageTypes, useNavigation } from '~/v4/core/providers/NavigationProvider'; import { useGetActiveStoriesByTarget } from '~/v4/social/hooks/useGetActiveStories'; +import { ArrowLeftButton } from '~/v4/social/elements/ArrowLeftButton/ArrowLeftButton'; import clsx from 'clsx'; -import useSDK from '~/v4/core/hooks/useSDK'; +import { ArrowRightButton } from '~/v4/social/elements/ArrowRightButton/ArrowRightButton'; import styles from './StoryPage.module.css'; -import { checkStoryPermission } from '~/v4/social/utils'; -import Trash from '~/v4/social/icons/trash'; -import { ArrowLeftButton } from '~/v4/social/elements/ArrowLeftButton'; -import { ArrowRightButton } from '~/v4/social/elements/ArrowRightButton'; +import { Trash2Icon } from '~/icons'; +import useSDK from '~/v4/core/hooks/useSDK'; +import { + CustomRendererProps, + RendererObject, +} from '~/v4/social/internal-components/StoryViewer/Renderers/types'; const DURATION = 5000; interface GlobalFeedStoryProps { targetId: string; + targetIds: string[]; + onChangePage: () => void; + onClickStory: (targetId: string) => void; + goToDraftStoryPage: (data: { + mediaType: { type: 'image' | 'video'; url: string }; + targetId: string; + targetType: string; + }) => void; + onClose: (targetId: string) => void; + onSwipeDown: (targetId: string) => void; + onClickCommunity: (targetId: string) => void; } -export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = () => { - const { page, onChangePage, onClickStory } = useNavigation(); - const { AmityStoryViewPageBehavior } = usePageBehavior(); +export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ + targetId, + targetIds, + onChangePage, + onClickStory, + goToDraftStoryPage, + onClose, + onSwipeDown, + onClickCommunity, +}) => { const { confirm } = useConfirmContext(); const notification = useNotifications(); const { stories } = useGetActiveStoriesByTarget({ targetType: 'community', - targetId: - page.type === PageTypes.ViewStoryPage && - page.context.storyType === 'globalFeed' && - page.context?.targetId - ? page.context?.targetId - : '', + targetId, options: { orderBy: 'asc', sortBy: 'createdAt', }, }); + const globalFeedRenderers = renderers.map(({ renderer, tester }) => { + const newRenderer = (props: CustomRendererProps) => + renderer({ + ...props, + onClose: () => { + console.log('onClose'); + onClose(targetId); + }, + onSwipeDown: () => onSwipeDown(targetId), + onClickCommunity: () => onClickCommunity(targetId), + }); + + return { + renderer: newRenderer, + tester, + }; + }); + const fileInputRef = useRef<HTMLInputElement>(null); const handleAddIconClick = (e: React.MouseEvent) => { @@ -91,7 +121,7 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = () => { okText: formatMessage({ id: 'delete' }), onOk: async () => { previousStory(); - if (isLastStory) onChangePage(PageTypes.SocialHomePage); + if (isLastStory) onChangePage(); await StoryRepository.softDeleteStory(storyId); notification.success({ content: formatMessage({ id: 'storyViewer.notification.deleted' }), @@ -99,7 +129,7 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = () => { if (isLastStory && stories.length > 1) { setCurrentIndex(currentIndex - 1); } else if (stories.length === 1) { - onChangePage(PageTypes.SocialHomePage); + onChangePage(); } }, }); @@ -182,7 +212,7 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = () => { name: 'delete', action: () => deleteStory(story?.storyId as string), icon: ( - <Trash + <Trash2Icon fill={getComputedStyle(document.documentElement).getPropertyValue( '--asc-color-base-default', )} @@ -200,20 +230,15 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = () => { }); const nextStory = () => { - if ( - page.type === PageTypes.ViewStoryPage && - page.context?.targetIds && - page.context?.targetId && - currentIndex === formattedStories?.length - 1 - ) { - const currentTargetIndex = page.context.targetIds.indexOf(page.context.targetId); + if (currentIndex === formattedStories?.length - 1) { + const currentTargetIndex = targetIds.indexOf(targetId); const nextTargetIndex = currentTargetIndex + 1; - if (nextTargetIndex < page.context.targetIds.length) { - const nextTargetId = page.context.targetIds[nextTargetIndex]; - onClickStory(nextTargetId, 'globalFeed', page.context.targetIds); + if (nextTargetIndex < targetIds.length) { + const nextTargetId = targetIds[nextTargetIndex]; + onClickStory(nextTargetId); } else { - onChangePage(PageTypes.SocialHomePage); + onChangePage(); } setCurrentIndex(0); return; @@ -222,20 +247,15 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = () => { }; const previousStory = () => { - if ( - page.type === PageTypes.ViewStoryPage && - page.context.targetIds && - page.context.targetId && - currentIndex === 0 - ) { - const currentTargetIndex = page.context.targetIds.indexOf(page.context.targetId); + if (currentIndex === 0) { + const currentTargetIndex = targetIds.indexOf(targetId); const previousTargetIndex = currentTargetIndex - 1; if (previousTargetIndex >= 0) { - const previousTargetId = page.context.targetIds[previousTargetIndex]; - onClickStory(previousTargetId, 'globalFeed', page.context.targetIds); + const previousTargetId = targetIds[previousTargetIndex]; + onClickStory(previousTargetId); } else { - onChangePage(PageTypes.SocialHomePage); + onChangePage(); } setCurrentIndex(0); return; @@ -289,18 +309,14 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = () => { } }, [stories, file, currentIndex]); - if (file && page.type === PageTypes.ViewStoryPage && page.context.storyType === 'globalFeed') { - return ( - <AmityDraftStoryPage - mediaType={ - file.type.includes('image') - ? { type: 'image', url: URL.createObjectURL(file) } - : { type: 'video', url: URL.createObjectURL(file) } - } - targetId={page.context.targetId} - targetType="community" - /> - ); + if (file) { + goToDraftStoryPage({ + targetId, + targetType: 'community', + mediaType: file.type.includes('image') + ? { type: 'image', url: URL.createObjectURL(file) } + : { type: 'video', url: URL.createObjectURL(file) }, + }); } return ( @@ -327,9 +343,7 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = () => { preventDefault currentIndex={currentIndex} stories={formattedStories} - // TO FIX: need to override custom type of renderers from react-insta-stories library - // @ts-ignore - renderers={renderers} + renderers={globalFeedRenderers as RendererObject[]} defaultInterval={DURATION} onStoryStart={() => stories[currentIndex]?.analytics.markAsSeen()} onStoryEnd={increaseIndex} diff --git a/src/v4/social/pages/StoryPage/ViewGlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/ViewGlobalFeedStory.tsx new file mode 100644 index 000000000..a56380c38 --- /dev/null +++ b/src/v4/social/pages/StoryPage/ViewGlobalFeedStory.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { useGlobalStoryTargets } from '../../hooks/collections/useGlobalStoryTargets'; +import { GlobalFeedStory } from './GlobalFeedStory'; + +export const ViewGlobalFeedStoryPage = ({ + targetId, + onChangePage, + onClickStory, + goToDraftStoryPage, + onClose, + onSwipeDown, + onClickCommunity, +}: { + targetId: string; + onChangePage: () => void; + onClickStory: (targetId: string) => void; + goToDraftStoryPage: ({ + targetId, + targetType, + mediaType, + }: { + targetId: string; + targetType: string; + mediaType: any; + }) => void; + onClose: (targetId: string) => void; + onSwipeDown: (targetId: string) => void; + onClickCommunity: (targetId: string) => void; +}) => { + const { stories } = useGlobalStoryTargets({ + seenState: 'smart' as Amity.StorySeenQuery, + limit: 10, + }); + return ( + <GlobalFeedStory + targetId={targetId} + targetIds={stories.map((s) => s.targetId)} + onChangePage={onChangePage} + onClickStory={onClickStory} + goToDraftStoryPage={goToDraftStoryPage} + onClose={onClose} + onSwipeDown={onSwipeDown} + onClickCommunity={onClickCommunity} + /> + ); +}; diff --git a/src/v4/social/pages/StoryPage/ViewStoryPage.tsx b/src/v4/social/pages/StoryPage/ViewStoryPage.tsx index 1b490affd..46f7c335f 100644 --- a/src/v4/social/pages/StoryPage/ViewStoryPage.tsx +++ b/src/v4/social/pages/StoryPage/ViewStoryPage.tsx @@ -1,21 +1,50 @@ import React from 'react'; -import { CommunityFeedStory } from './CommunityFeedStory'; -import { GlobalFeedStory } from './GlobalFeedStory'; -import { PageTypes, useNavigation } from '~/v4/core/providers/NavigationProvider'; +import { CommunityFeedStory } from '~/v4/social/pages/StoryPage/CommunityFeedStory'; +import { useNavigation } from '~/v4/core/providers/NavigationProvider'; +import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; +import { ViewGlobalFeedStoryPage } from './ViewGlobalFeedStory'; type ViewStoryPageType = 'communityFeed' | 'globalFeed'; interface AmityViewStoryPageProps { type: ViewStoryPageType; + targetId: string; } -const ViewStoryPage: React.FC<AmityViewStoryPageProps> = ({ type }) => { - const { page } = useNavigation(); +const ViewStoryPage: React.FC<AmityViewStoryPageProps> = ({ type, targetId }) => { + const { AmityStoryViewPageBehavior } = usePageBehavior(); + const { onBack, goToViewStoryPage, goToDraftStoryPage, onClickCommunity } = useNavigation(); - if (page.type !== PageTypes.ViewStoryPage || !page.context.targetId) return null; - - if (type === 'communityFeed') return <CommunityFeedStory communityId={page.context.targetId} />; - if (type === 'globalFeed') return <GlobalFeedStory targetId={page.context.targetId} />; + if (type === 'communityFeed') + return ( + <CommunityFeedStory + communityId={targetId} + onBack={onBack} + onClose={(communityId) => onClickCommunity(communityId)} + onSwipeDown={(communityId) => onClickCommunity(communityId)} + onClickCommunity={(communityId) => onClickCommunity(communityId)} + /> + ); + if (type === 'globalFeed') + return ( + <ViewGlobalFeedStoryPage + targetId={targetId} + onChangePage={() => AmityStoryViewPageBehavior.onCloseAction()} + onClose={() => AmityStoryViewPageBehavior.onCloseAction()} + onSwipeDown={() => AmityStoryViewPageBehavior.onCloseAction()} + onClickStory={(targetId) => + goToViewStoryPage({ + storyType: 'globalFeed', + targetId, + targetType: 'community', + }) + } + goToDraftStoryPage={({ targetId, targetType, mediaType }) => + goToDraftStoryPage({ targetId, targetType, mediaType }) + } + onClickCommunity={(targetId) => onClickCommunity(targetId)} + /> + ); return null; }; From 88bb4c33e700de723b6e3eb2a6f563012e5bdb1a Mon Sep 17 00:00:00 2001 From: Pitchaya T <33589608+ptchayap@users.noreply.github.com> Date: Tue, 18 Jun 2024 10:25:31 +0700 Subject: [PATCH 135/300] feat: ASC-00000 - tech debt livechat (#416) * fix: unconfigurable style * fix: modify avatar v4 to support defaultImage * fix: livechat avatar to use avatar component v4 * fix: user icon in message bubble to use v4 compo * fix: change defaul image for livechat * fix: use avatar v4 component on story preview * fix: to use v4 components in reply message placeholder * fix: mention item to use v4 avatar components * fix: defaultImage on share story button * fix: theme on AmityLiveChatHeader * fix: compose bar to use useAmityComponent * fix: LiveChatMessageList to use useAmityComponent * fix: remove condition variable * fix: configuration value * fix: message reaction to use useAmityElement * fix: remove unused * fix: to use same value on light and dark theme for livechat * fix: remove /index --- amity-uikit.config.json | 18 ++--- .../components/AmityLiveChatHeader/index.tsx | 16 +++-- .../AmityLiveChatHeader/styles.module.css | 15 +--- .../AmityLiveChatMessageComposeBar/index.tsx | 14 ++-- .../styles.module.css | 27 ++------ .../AmityLiveChatMessageList/index.tsx | 9 ++- .../styles.module.css | 3 +- .../HomeIndicator/styles.module.css | 2 +- .../MessageAction/styles.module.css | 40 +++-------- .../MessageBubble/styles.module.css | 41 +++-------- .../MessageBubbleContainer/index.tsx | 12 +--- .../MessageBubbleContainer/styles.module.css | 5 +- .../MessageTextWithMention/styles.module.css | 10 +-- .../LiveChatMessageContent/styles.module.css | 15 +--- .../LiveChatNotification/styles.module.css | 4 +- .../components/MessageQuickReaction/index.tsx | 15 ++-- .../MessageQuickReaction/styles.module.css | 5 +- src/v4/chat/components/UserAvatar/index.tsx | 68 ------------------- .../ChatContainer/ReplyMessagePlaceholder.tsx | 14 ++-- .../ChatContainer/styles.module.css | 24 +++---- src/v4/chat/pages/AmityLiveChatPage/index.tsx | 8 ++- .../pages/AmityLiveChatPage/styles.module.css | 2 +- src/v4/core/components/Avatar/Avatar.tsx | 18 +++-- src/v4/core/components/Avatar/index.ts | 2 +- .../components/SocialMentionItem/index.tsx | 11 +-- src/v4/icons/Chat.tsx | 26 +++++++ src/v4/icons/Community.tsx | 2 - src/v4/icons/User.tsx | 24 +++++++ .../ShareStoryButton/ShareStoryButton.tsx | 7 +- .../StoryPreview/StoryPreview.tsx | 6 +- 30 files changed, 176 insertions(+), 287 deletions(-) delete mode 100644 src/v4/chat/components/UserAvatar/index.tsx create mode 100644 src/v4/icons/Chat.tsx create mode 100644 src/v4/icons/User.tsx diff --git a/amity-uikit.config.json b/amity-uikit.config.json index ce9f67eda..3b0980017 100644 --- a/amity-uikit.config.json +++ b/amity-uikit.config.json @@ -178,15 +178,15 @@ "theme": { "light": { "primary_color": "#1054DE", - "secondary_color": "#292B32", - "base_color": "#292b32", - "base_shade1_color": "#636878", - "base_shade2_color": "#898e9e", - "base_shade3_color": "#a5a9b5", - "base_shade4_color": "#ebecef", + "secondary_color": "#ebecef", + "base_color": "#ebecef", + "base_shade1_color": "#a5a9b5", + "base_shade2_color": "#6e7487", + "base_shade3_color": "#40434e", + "base_shade4_color": "#292b32", "alert_color": "#FA4D30", - "background_color": "#FFFFFF", - "base_inverse_color": "#000000" + "background_color": "#191919", + "base_inverse_color": "#FFFFFF" }, "dark": { "primary_color": "#1054DE", @@ -208,7 +208,7 @@ "message_limit": 200, "placeholder_text": "Write a message" }, - "live_chat_page/message_list/message_quick_reaction": { + "live_chat/message_list/message_quick_reaction": { "reaction": "heart" } } diff --git a/src/v4/chat/components/AmityLiveChatHeader/index.tsx b/src/v4/chat/components/AmityLiveChatHeader/index.tsx index fa8a7f48e..93e238217 100644 --- a/src/v4/chat/components/AmityLiveChatHeader/index.tsx +++ b/src/v4/chat/components/AmityLiveChatHeader/index.tsx @@ -1,27 +1,33 @@ import React from 'react'; import millify from 'millify'; import { FormattedMessage, useIntl } from 'react-intl'; -import UserAvatar from '~/v4/chat/components/UserAvatar'; -import { backgroundImage as communityBackgroundImage } from '~/v4/icons/Community'; +import Chat from '~/v4/icons/Chat'; import UserRegular from '~/v4/icons/UserRegular'; import useConnectionStates from '~/social/hooks/useConnectionStates'; import ConnectionSpinner from '~/v4/icons/ConnectionSpinner'; import { Typography } from '~/v4/core/components'; import styles from './styles.module.css'; import useChatInfo from '~/v4/chat/hooks/useChatInfo'; +import { Avatar, AVATAR_SIZE } from '~/v4/core/components/Avatar/Avatar'; +import { useAmityComponent } from '~/v4/core/hooks/uikit/index'; interface AmityLiveChatHeaderProps { channel: Amity.Channel | null; + pageId?: string; + componentId?: string; } -export const AmityLiveChatHeader = ({ channel }: AmityLiveChatHeaderProps) => { +export const AmityLiveChatHeader = ({ channel, pageId = '*' }: AmityLiveChatHeaderProps) => { + const componentId = 'chat_header'; + const { themeStyles } = useAmityComponent({ pageId, componentId }); + const { chatName, chatAvatar } = useChatInfo({ channel }); const { formatMessage } = useIntl(); const isOnline = useConnectionStates(); return ( - <div className={styles.messageListHeader}> - <UserAvatar avatarUrl={chatAvatar} defaultImage={communityBackgroundImage} /> + <div className={styles.messageListHeader} style={themeStyles}> + <Avatar size={AVATAR_SIZE.MEDIUM} avatar={chatAvatar} defaultImage={<Chat />} /> <div> <div className={styles.displayName}> <Typography.Title>{chatName || formatMessage({ id: 'loading' })}</Typography.Title> diff --git a/src/v4/chat/components/AmityLiveChatHeader/styles.module.css b/src/v4/chat/components/AmityLiveChatHeader/styles.module.css index d1d81ab14..09580e17d 100644 --- a/src/v4/chat/components/AmityLiveChatHeader/styles.module.css +++ b/src/v4/chat/components/AmityLiveChatHeader/styles.module.css @@ -7,17 +7,11 @@ } .displayName { - color: var( - --live-chat-chat-header-asc-color-base-inverse, - var(--live-chat-asc-color-base-inverse, var(--asc-color-base-inverse)) - ); + color: var(--asc-color-base-inverse); } .memberCount { - color: var( - --live-chat-chat-header-asc-color-base-default, - var(--live-chat-asc-color-base-default, var(--asc-color-base-default)) - ); + color: var(--asc-color-base-default); display: flex; align-items: center; } @@ -26,8 +20,5 @@ width: 0.75rem; height: 0.75rem; margin-right: var(--asc-spacing-xxs2); - fill: var( - --live-chat-chat-header-asc-color-base-default, - var(--live-chat-asc-color-base-default, var(--asc-color-base-default)) - ); + fill: var(--asc-color-base-default); } diff --git a/src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx b/src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx index 18441efca..cb118f8a9 100644 --- a/src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx +++ b/src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx @@ -10,6 +10,7 @@ import useChannelPermission from '~/v4/chat/hooks/useChannelPermission'; import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; import { useLiveChatNotifications } from '~/v4/chat/providers/LiveChatNotificationProvider'; +import { useAmityComponent } from '~/v4/core/hooks/uikit'; const COMPOSEBAR_MAX_CHARACTER_LIMIT = 200; @@ -37,7 +38,7 @@ type ComposeBarMention = { }; export const AmityLiveChatMessageComposeBar = ({ - pageId = 'live_chat', + pageId = '*', channel, suggestionRef, composeAction: { replyMessage, mentionMessage, clearReplyMessage, clearMention }, @@ -46,13 +47,13 @@ export const AmityLiveChatMessageComposeBar = ({ const [mentionList, setMentionList] = useState<{ [key: ComposeBarMention['id']]: ComposeBarMention; }>({}); + const componentId = 'message_composer'; + const { themeStyles, config } = useAmityComponent({ pageId, componentId }); const { confirm } = useConfirmContext(); const notification = useLiveChatNotifications(); - const { getConfig } = useCustomization(); - const componentConfig = getConfig(`${pageId}/${componentId}/*`); const commentInputRef = useRef<HTMLInputElement | HTMLTextAreaElement | null>(null); const { queryMentionees, mentionees, onChange, markup, metadata, text } = useMention({ @@ -68,7 +69,7 @@ export const AmityLiveChatMessageComposeBar = ({ if (!channel) return; if (text?.trim().length === 0) return; - if (text.trim().length > (componentConfig?.message_limit || COMPOSEBAR_MAX_CHARACTER_LIMIT)) { + if (text.trim().length > (config?.message_limit || COMPOSEBAR_MAX_CHARACTER_LIMIT)) { confirm({ title: formatMessage({ id: 'livechat.error.tooLongMessage.title' }), content: formatMessage({ id: 'livechat.error.tooLongMessage.description' }), @@ -119,7 +120,7 @@ export const AmityLiveChatMessageComposeBar = ({ }, []); return ( - <div className={styles.composeBarContainer}> + <div className={styles.composeBarContainer} style={themeStyles}> <div className={styles.composeBar}> <div className={styles.textInputContainer}> <InputText @@ -129,8 +130,7 @@ export const AmityLiveChatMessageComposeBar = ({ multiline disabled={disabled} placeholder={ - (typeof componentConfig?.placeholder_text === 'string' && - componentConfig?.placeholder_text) || + (typeof config?.placeholder_text === 'string' && config?.placeholder_text) || formatMessage({ id: 'livechat.composebar.placeholder', }) diff --git a/src/v4/chat/components/AmityLiveChatMessageComposeBar/styles.module.css b/src/v4/chat/components/AmityLiveChatMessageComposeBar/styles.module.css index c9bc1ae1e..61c896bb0 100644 --- a/src/v4/chat/components/AmityLiveChatMessageComposeBar/styles.module.css +++ b/src/v4/chat/components/AmityLiveChatMessageComposeBar/styles.module.css @@ -2,10 +2,7 @@ border-radius: var(--asc-border-radius-xxl); width: 1rem; height: 1rem; - background-color: var( - --live-chat-message-composer-asc-color-primary-default, - var(--live-chat-asc-color-primary-default, var(--asc-color-primary-default)) - ); + background-color: var(--asc-color-primary-default); cursor: pointer; padding: var(--asc-spacing-xxs2); fill: white; @@ -37,7 +34,7 @@ gap: var(--asc-spacing-s2); padding: var(--asc-spacing-s1) var(--asc-spacing-s2); background-color: inherit; - box-shadow: 0 -1px 0 0 var(--live-chat-message-composer-asc-color-base-shade4, var(--live-chat-asc-color-base-shade4, var(--asc-color-base-shade4))); + box-shadow: 0 -1px 0 0 var(--asc-color-base-shade4); } .textInputContainer { @@ -47,10 +44,7 @@ & > div { width: 100%; border-radius: var(--asc-border-radius-xxl); - background: var( - --live-chat-message-composer-asc-color-secondary-shade4, - var(--live-chat-asc-color-secondary-shade4, var(--asc-color-secondary-shade4)) - ); + background: var(--asc-color-secondary-shade4); textarea { padding: 0.563rem 1rem; @@ -65,14 +59,8 @@ padding: var(--asc-spacing-s1) var(--asc-spacing-s2); border-radius: var(--asc-border-radius-sm); border: var(--asc-border-radius-none); - background-color: var( - --live-chat-message-composer-asc-color-secondary, - var(--live-chat-asc-color-secondary, var(--asc-color-secondary-default)) - ); - color: var( - --live-chat-message-composer-asc-color-base-inverse, - var(--live-chat-asc-color-base-inverse, var(--asc-color-base-inverse)) - ); + background-color: var(--asc-color-secondary-default); + color: var(--asc-color-base-inverse); display: flex; align-items: center; @@ -83,8 +71,5 @@ } .mentionText { - color: var( - --live-chat-message-composer-asc-color-primary-default, - var(--live-chat-asc-color-primary-default, var(--asc-color-primary-default)) - ) !important; + color: var(--asc-color-primary-default); } diff --git a/src/v4/chat/components/AmityLiveChatMessageList/index.tsx b/src/v4/chat/components/AmityLiveChatMessageList/index.tsx index ce87e4ae6..84e7a7e69 100644 --- a/src/v4/chat/components/AmityLiveChatMessageList/index.tsx +++ b/src/v4/chat/components/AmityLiveChatMessageList/index.tsx @@ -14,6 +14,7 @@ import { unFlagMessage } from '~/v4/utils/unFlagMessage'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; import { useLiveChatNotifications } from '~/v4/chat/providers/LiveChatNotificationProvider'; import { useCopyMessage } from '~/v4/core/hooks'; +import { useAmityComponent } from '~/v4/core/hooks/uikit'; interface AmityLiveChatMessageListProps { pageId?: string; @@ -22,7 +23,7 @@ interface AmityLiveChatMessageListProps { } export const AmityLiveChatMessageList = ({ - pageId, + pageId = '*', channel, replyMessage, }: AmityLiveChatMessageListProps) => { @@ -36,6 +37,8 @@ export const AmityLiveChatMessageList = ({ const notification = useLiveChatNotifications(); const copyMessage = useCopyMessage(); + const { themeStyles } = useAmityComponent({ pageId, componentId }); + const { messages: rawMessages, hasMore, @@ -88,7 +91,7 @@ export const AmityLiveChatMessageList = ({ if (error) { return ( - <div className={styles.customStatusContainer} ref={containerRef}> + <div className={styles.customStatusContainer} ref={containerRef} style={themeStyles}> <div className={styles.iconContainer}> <Redo /> </div> @@ -100,7 +103,7 @@ export const AmityLiveChatMessageList = ({ } return ( - <div className={styles.infiniteScrollContainer} ref={containerRef}> + <div className={styles.infiniteScrollContainer} ref={containerRef} style={themeStyles}> {containerRef.current && ( <InfiniteScroll className={styles.infiniteScrollInner} diff --git a/src/v4/chat/components/AmityLiveChatMessageList/styles.module.css b/src/v4/chat/components/AmityLiveChatMessageList/styles.module.css index 0a394e098..bda68abe2 100644 --- a/src/v4/chat/components/AmityLiveChatMessageList/styles.module.css +++ b/src/v4/chat/components/AmityLiveChatMessageList/styles.module.css @@ -29,8 +29,7 @@ width: 100%; height: 100%; align-items: center; - color: var(--live-chat-message-list-asc-color-secondary-shade2), - var(--live-chat-asc-color-secondary-shade2, var(--asc-color-secondary-shade2)); + color: var(--asc-color-secondary-shade2); font-weight: var(--asc-text-font-weight-light); font-size: var(--asc-text-font-size-md); } diff --git a/src/v4/chat/components/HomeIndicator/styles.module.css b/src/v4/chat/components/HomeIndicator/styles.module.css index 7cc527f79..c8fc4e832 100644 --- a/src/v4/chat/components/HomeIndicator/styles.module.css +++ b/src/v4/chat/components/HomeIndicator/styles.module.css @@ -8,5 +8,5 @@ width: 8.375rem; height: 0.3125rem; border-radius: var(--asc-border-radius-full); - background-color: var(--live-chat-asc-color-base-shade1, var(--asc-color-base-shade1)); + background-color: var(--asc-color-base-shade1); } diff --git a/src/v4/chat/components/LiveChatMessageContent/MessageAction/styles.module.css b/src/v4/chat/components/LiveChatMessageContent/MessageAction/styles.module.css index ca01af8f8..01331658a 100644 --- a/src/v4/chat/components/LiveChatMessageContent/MessageAction/styles.module.css +++ b/src/v4/chat/components/LiveChatMessageContent/MessageAction/styles.module.css @@ -3,19 +3,13 @@ :hover { cursor: pointer; - background-color: var( - --live-chat-message-list-asc-color-secondary-shade4, - var(--live-chat-asc-color-secondary-shade4, var(--asc-color-secondary-shade4)) - ); + background-color: var(--asc-color-secondary-shade4); border-radius: var(--asc-border-radius-sm); } } .optionIcon { - fill: var( - --live-chat-message-list-asc-color-secondary-shade2, - var(--live-chat-asc-color-secondary-shade2, var(--asc-color-secondary-shade2)) - ); + fill: var(--asc-color-secondary-shade2); width: auto; height: 1.25rem; } @@ -27,10 +21,7 @@ .timestamp { font-family: var(--asc-text-global-font-family); - color: var( - --live-chat-message-list-asc-color-base-shade2, - var(--live-chat-asc-color-base-shade2, var(--asc-color-base-shade2)) - ); + color: var(--asc-color-base-shade2); font-size: 0.5rem; line-height: 0.75rem; margin-bottom: var(--asc-spacing-s1); @@ -44,40 +35,25 @@ } .messageActionButtonText { - color: var( - --live-chat-message-list-asc-color-base-inverse, - var(--live-chat-asc-color-base-inverse, var(--asc-color-base-inverse)) - ); + color: var(--asc-color-base-inverse); } .messageDangerActionButtonText { - color: var( - --live-chat-message-list-asc-color-alert, - var(--live-chat-asc-color-alert, var(--asc-color-alert)) - ); + color: var(--asc-color-alert); } .copyIcon { - fill: var( - --live-chat-message-list-asc-color-base-inverse, - var(--live-chat-asc-color-base-inverse, var(--asc-color-base-inverse)) - ); + fill: var(--asc-color-base-inverse); } .replyIcon { - fill: var( - --live-chat-message-list-asc-color-base-inverse, - var(--live-chat-asc-color-base-inverse, var(--asc-color-base-inverse)) - ); + fill: var(--asc-color-base-inverse); } .binIcon { width: 1.25rem; height: 1.25rem; - fill: var( - --live-chat-message-list-asc-color-alert, - var(--live-chat-asc-color-alert, var(--asc-color-alert)) - ); + fill: var(--asc-color-alert); } .mentionIcon { diff --git a/src/v4/chat/components/LiveChatMessageContent/MessageBubble/styles.module.css b/src/v4/chat/components/LiveChatMessageContent/MessageBubble/styles.module.css index 4465b6c72..089caff55 100644 --- a/src/v4/chat/components/LiveChatMessageContent/MessageBubble/styles.module.css +++ b/src/v4/chat/components/LiveChatMessageContent/MessageBubble/styles.module.css @@ -2,14 +2,8 @@ padding: var(--asc-spacing-s1) 0.625rem; border-radius: var(--asc-border-radius-lg); border-top-left-radius: var(--asc-border-radius-none); - color: var( - --live-chat-message-list-asc-color-base-inverse, - var(--live-chat-asc-color-base-inverse, var(--asc-color-base-inverse)) - ); - background-color: var( - --live-chat-message-list-asc-color-base-shade4, - var(--live-chat-asc-color-base-shade4, var(--asc-color-base-shade4)) - ); + color: var(--asc-color-base-inverse); + background-color: var(--asc-color-base-shade4); max-width: 7.5rem; word-break: break-word; overflow-wrap: break-word; @@ -18,11 +12,7 @@ } .messageBubble[data-mentioned='true'] { - border: 1px solid - var( - --live-chat-message-list-asc-color-primary-default, - var(--live-chat-asc-color-primary-default, var(--asc-color-primary-default)) - ); + border: 1px solid var(--asc-color-primary-default); } .messageRepliedBubble { @@ -32,24 +22,15 @@ .messageParentContainer { padding: var(--asc-spacing-s2); border-radius: var(--asc-border-radius-lg) var(--asc-border-radius-lg) 0 0; - background-color: var( - --live-chat-message-list-asc-color-base-shade3, - var(--live-chat-asc-color-base-shade3, var(--asc-color-base-shade3)) - ); + background-color: var(--asc-color-base-shade3); } .messageParentDisplayName { - color: var( - --live-chat-message-list-asc-color-base-inverse, - var(--live-chat-asc-color-base-inverse, var(--asc-color-base-inverse)) - ); + color: var(--asc-color-base-inverse); } .messageParentText { - color: var( - --live-chat-message-list-asc-color-base-default, - var(--live-chat-asc-color-base-default, var(--asc-color-base-default)) - ); + color: var(--asc-color-base-default); text-overflow: ellipsis; overflow: hidden; white-space: nowrap; @@ -57,14 +38,8 @@ .messageChildContainer { border-radius: 0 0 var(--asc-border-radius-lg) var(--asc-border-radius-lg); - background-color: var( - --live-chat-message-list-asc-color-base-shade4, - var(--live-chat-asc-color-base-shade4, var(--asc-color-base-shade4)) - ); - color: var( - --live-chat-message-list-asc-color-base-inverse, - var(--live-chat-asc-color-base-inverse, var(--asc-color-base-inverse)) - ); + background-color: var(--asc-color-base-shade4); + color: var(--asc-color-base-inverse); padding: var(--asc-spacing-s1) var(--asc-spacing-s2); } diff --git a/src/v4/chat/components/LiveChatMessageContent/MessageBubbleContainer/index.tsx b/src/v4/chat/components/LiveChatMessageContent/MessageBubbleContainer/index.tsx index aeff127e8..4031d2b1f 100644 --- a/src/v4/chat/components/LiveChatMessageContent/MessageBubbleContainer/index.tsx +++ b/src/v4/chat/components/LiveChatMessageContent/MessageBubbleContainer/index.tsx @@ -1,10 +1,8 @@ import React from 'react'; import styles from './styles.module.css'; -import UserAvatar from '../../UserAvatar'; -import { backgroundImage as userBackgroundImage } from '~/icons/User'; -import Badge from '~/v4/icons/Badge'; +import User from '~/v4/icons/User'; import { Typography } from '~/v4/core/components'; -import { SIZE_ALIAS } from '~/core/hooks/useSize'; +import { Avatar, AVATAR_SIZE } from '~/v4/core/components/Avatar'; interface MessageBubbleContainerProps { avatarUrl?: string; @@ -19,11 +17,7 @@ const MessageBubbleContainer = ({ }: MessageBubbleContainerProps) => { return ( <div className={styles.messageItemContainer}> - <UserAvatar - size={SIZE_ALIAS.SMALL} - avatarUrl={avatarUrl} - defaultImage={userBackgroundImage} - /> + <Avatar size={AVATAR_SIZE.SMALL} avatar={avatarUrl} defaultImage={<User />} /> <div> <div className={styles.userDisplayName}> diff --git a/src/v4/chat/components/LiveChatMessageContent/MessageBubbleContainer/styles.module.css b/src/v4/chat/components/LiveChatMessageContent/MessageBubbleContainer/styles.module.css index 240434c91..163d6932c 100644 --- a/src/v4/chat/components/LiveChatMessageContent/MessageBubbleContainer/styles.module.css +++ b/src/v4/chat/components/LiveChatMessageContent/MessageBubbleContainer/styles.module.css @@ -8,10 +8,7 @@ display: flex; gap: var(--asc-spacing-xxs2); align-items: center; - color: var( - --live-chat-message-list-asc-color-secondary-shade1, - var(--live-chat-asc-color-secondary-shade1, var(--asc-color-secondary-shade1)) - ); + color: var(--asc-color-secondary-shade2); margin-bottom: var(--asc-spacing-xxs2); } diff --git a/src/v4/chat/components/LiveChatMessageContent/MessageTextWithMention/styles.module.css b/src/v4/chat/components/LiveChatMessageContent/MessageTextWithMention/styles.module.css index b49054e75..9e77a66cd 100644 --- a/src/v4/chat/components/LiveChatMessageContent/MessageTextWithMention/styles.module.css +++ b/src/v4/chat/components/LiveChatMessageContent/MessageTextWithMention/styles.module.css @@ -1,13 +1,7 @@ .mentionText { - color: var( - --live-chat-message-list-asc-color-primary-default, - var(--live-chat-asc-color-primary-default, var(--asc-color-primary-default)) - ); + color: var(--asc-color-primary-default); } .hyperlink { - color: var( - --live-chat-message-list-asc-color-primary-default, - var(--live-chat-asc-color-primary-default, var(--asc-color-primary-default)) - ); + color: var(--asc-color-primary-default); } diff --git a/src/v4/chat/components/LiveChatMessageContent/styles.module.css b/src/v4/chat/components/LiveChatMessageContent/styles.module.css index e43f897d8..0cf5143fd 100644 --- a/src/v4/chat/components/LiveChatMessageContent/styles.module.css +++ b/src/v4/chat/components/LiveChatMessageContent/styles.module.css @@ -31,10 +31,7 @@ background-color: var(--asc-color-base-shade4); gap: var(--asc-spacing-xxs2); border-radius: var(--asc-border-radius-lg); - color: var( - --live-chat-message-list-asc-color-base-inverse, - var(--live-chat-asc-color-base-inverse, var(--asc-color-base-inverse)) - ); + color: var(--asc-color-base-inverse); } .messageOptionsWrap { @@ -46,10 +43,7 @@ .binIcon { height: 1rem; width: 1rem; - fill: var( - --live-chat-message-list-asc-color-base-inverse, - var(--live-chat-asc-color-base-inverse, var(--asc-color-base-inverse)) - ); + fill: var(--asc-color-base-inverse); } .optionIcon { @@ -63,10 +57,7 @@ .timestamp { font-family: var(--asc-text-global-font-family); - color: var( - --live-chat-message-list-asc-color-base-shade2, - var(--live-chat-asc-color-base-shade2, var(--asc-color-base-shade2)) - ); + color: var(--asc-color-base-shade2); margin-bottom: var(--asc-spacing-s1); /* This value is not available in global css */ diff --git a/src/v4/chat/components/LiveChatNotification/styles.module.css b/src/v4/chat/components/LiveChatNotification/styles.module.css index f0e3857b5..336baa6ab 100644 --- a/src/v4/chat/components/LiveChatNotification/styles.module.css +++ b/src/v4/chat/components/LiveChatNotification/styles.module.css @@ -15,13 +15,13 @@ padding: 1.125rem var(--asc-spacing-m1); display: flex; align-items: center; - color: var(--live-chat-asc-color-base-inverse, var(--asc-color-base-inverse)); + color: var(--asc-color-base-inverse); border-radius: var(--asc-border-radius-md); animation-duration: 0.3s; animation-name: appear; animation-fill-mode: forwards; pointer-events: auto; - background-color: var(--live-chat-asc-color-base-shade4, var(--asc-color-base-shade4)); + background-color: var(--asc-color-base-shade4); } @keyframes appear { diff --git a/src/v4/chat/components/MessageQuickReaction/index.tsx b/src/v4/chat/components/MessageQuickReaction/index.tsx index 4d59a2045..7ff9939e9 100644 --- a/src/v4/chat/components/MessageQuickReaction/index.tsx +++ b/src/v4/chat/components/MessageQuickReaction/index.tsx @@ -1,5 +1,5 @@ import React, { useCallback } from 'react'; -import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; import { AmityReactionType, useCustomReaction } from '~/v4/core/providers/CustomReactionProvider'; import { QuickReactionIcon } from '~/v4/icons/QuickReactionIcon'; import { selectMessageReaction } from '~/v4/utils/selectMessageReaction'; @@ -19,25 +19,24 @@ export const MessageQuickReaction = ({ onSelectReaction, }: MessageQuickReactionProps) => { const elementId = 'message_quick_reaction'; - const { config: reactionConfig } = useCustomReaction(); - const { getConfig } = useCustomization(); - const elementConfig = getConfig(`${pageId}/${componentId}/${elementId}`); + const { config } = useAmityElement({ pageId, componentId, elementId }); + const { config: reactionConfig } = useCustomReaction(); const onClickQuickReaction = useCallback(() => { if ( reactionConfig && - elementConfig.reaction && - reactionConfig.find((config) => config.name === elementConfig.reaction) + config.reaction && + reactionConfig.find((reaction) => reaction.name === config.reaction) ) { selectMessageReaction({ - reactionName: elementConfig.reaction as AmityReactionType['name'], + reactionName: config.reaction as AmityReactionType['name'], message, }); } onSelectReaction && onSelectReaction(); - }, [reactionConfig, elementConfig, message]); + }, [reactionConfig, config, message]); return ( <div className={styles.quickReactionIconContainer}> diff --git a/src/v4/chat/components/MessageQuickReaction/styles.module.css b/src/v4/chat/components/MessageQuickReaction/styles.module.css index ce96cb230..b4d5a6f58 100644 --- a/src/v4/chat/components/MessageQuickReaction/styles.module.css +++ b/src/v4/chat/components/MessageQuickReaction/styles.module.css @@ -10,9 +10,6 @@ .quickReactionIconContainer:hover { cursor: pointer; - background-color: var( - --live-chat-message-list-asc-color-secondary-shade4, - var(--live-chat-asc-color-secondary-shade4, var(--asc-color-secondary-shade4)) - ); + background-color: var(--asc-color-secondary-shade4); border-radius: var(--asc-border-radius-sm); } diff --git a/src/v4/chat/components/UserAvatar/index.tsx b/src/v4/chat/components/UserAvatar/index.tsx deleted file mode 100644 index becd694f4..000000000 --- a/src/v4/chat/components/UserAvatar/index.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { FileRepository } from '@amityco/ts-sdk'; -import UiKitAvatar from '~/core/components/Avatar'; -import { SIZE_ALIAS } from '~/core/hocs/withSize'; - -export interface UserAvatarProps { - size?: typeof SIZE_ALIAS[keyof typeof SIZE_ALIAS] | null; - avatarUrl?: string | null; - avatarFileId?: string | null; - avatarFile?: File | null; - defaultImage?: string | null; - avatarCustomUrl?: string | null; -} - -const UserAvatar = ({ - size = SIZE_ALIAS.REGULAR, - avatarUrl, - avatarFileId, - avatarFile, - defaultImage, - avatarCustomUrl, -}: UserAvatarProps) => { - const [avatar, setAvatar] = useState<string | null>(null); - const [backgroundImage, setBackgroundImage] = useState<string | null>(null); - - useEffect(() => { - setAvatar(null); - setBackgroundImage(null); - const getAvatarProps = async () => { - if (avatarUrl) { - setAvatar(avatarUrl); - return; - } - if (avatarCustomUrl) { - setAvatar(avatarCustomUrl); - return; - } - if (avatarFile) { - const toBase64 = (file: File) => - new Promise<string>((resolve, reject) => { - const reader = new FileReader(); - reader.readAsDataURL(file); - reader.onload = () => resolve(reader.result as string); - reader.onerror = reject; - }); - - const fileBase64 = await toBase64(avatarFile); - setAvatar(fileBase64); - return; - } - if (avatarFileId) { - const avatarFileUrl = await FileRepository.fileUrlWithSize(avatarFileId, 'small'); - setAvatar(avatarFileUrl); - return; - } - - if (defaultImage) { - setBackgroundImage(defaultImage); - return; - } - }; - getAvatarProps(); - }, [avatarUrl, avatarFileId, avatarFile, avatarCustomUrl]); - - return <UiKitAvatar size={size} avatar={avatar} backgroundImage={backgroundImage} />; -}; - -export default UserAvatar; diff --git a/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ReplyMessagePlaceholder.tsx b/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ReplyMessagePlaceholder.tsx index cbac62fcc..af51ca5b6 100644 --- a/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ReplyMessagePlaceholder.tsx +++ b/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ReplyMessagePlaceholder.tsx @@ -1,11 +1,11 @@ import React from 'react'; import useUser from '~/core/hooks/useUser'; import styles from './styles.module.css'; -import UserAvatar from '~/v4/chat/components/UserAvatar'; -import { SIZE_ALIAS } from '~/core/hocs/withSize'; import { FormattedMessage } from 'react-intl'; -import { CloseIcon } from '~/icons'; -import { backgroundImage as userBackgroundImage } from '~/icons/User'; +import CloseIcon from '~/v4/icons/Close'; +import { Avatar } from '~/v4/core/components'; +import User from '~/v4/icons/User'; +import { AVATAR_SIZE } from '~/v4/core/components/Avatar/Avatar'; interface ReplyMessagePlaceholderProps { replyMessage: Amity.Message<'text'>; @@ -20,11 +20,7 @@ const ReplyMessagePlaceholder = ({ replyMessage, onDismiss }: ReplyMessagePlaceh return ( <div className={styles.replyPlaceholderContainer}> <div className={styles.replyAvatar}> - <UserAvatar - avatarUrl={profile.avatar?.fileUrl} - size={SIZE_ALIAS.SMALL} - defaultImage={userBackgroundImage} - /> + <Avatar avatar={profile.avatar?.fileUrl} size={AVATAR_SIZE.SMALL} defaultImage={<User />} /> </div> <div className={styles.replyProfile}> <div className={styles.replyProfileName}> diff --git a/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/styles.module.css b/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/styles.module.css index e5884d283..ba002e639 100644 --- a/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/styles.module.css +++ b/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/styles.module.css @@ -10,7 +10,7 @@ gap: var(--asc-spacing-s2); padding: var(--asc-spacing-s1) var(--asc-spacing-s2); background-color: inherit; - border-top: 1px solid var(--live-chat-asc-color-base-shade2, var(--asc-color-base-shade2)); + border-top: 1px solid var(--asc-color-base-shade2); } .textInputContainer { @@ -20,8 +20,8 @@ & > div { width: 100%; border-radius: var(--asc-border-radius-xxl); - border: 1px solid var(--live-chat-asc-color-base-background, var(--asc-color-base-background)); - background: var(--live-chat-asc-color-secondary-shade4, var(--asc-color-secondary-shade4)); + border: 1px solid var(--asc-color-base-background); + background: var(--asc-color-secondary-shade4); textarea { padding: 0.563rem 1rem; @@ -40,7 +40,7 @@ --live-chat-asc-color-secondary-default, var(--asc-color-secondary-default) ); - color: var(--live-chat-asc-color-base-inverse, var(--asc-color-base-inverse)); + color: var(--asc-color-base-inverse); display: flex; align-items: center; @@ -53,8 +53,8 @@ .replyPlaceholderContainer { display: flex; font-family: var(--asc-text-global-font-family); - background-color: var(--live-chat-asc-color-base-shade4, var(--asc-color-base-shade4)); - color: var(--live-chat-asc-color-base-inverse, var(--asc-color-base-inverse)); + background-color: var(--asc-color-base-shade4); + color: var(--asc-color-base-inverse); max-width: 100%; padding: var(--asc-spacing-s2); padding-left: var(--asc-spacing-m1); @@ -69,7 +69,7 @@ .replyProfile { display: flex; flex-direction: column; - color: var(--live-chat-asc-color-base-inverse, var(--asc-color-base-inverse)); + color: var(--asc-color-base-inverse); font-size: var(--asc-text-font-size-sm); line-height: var(--asc-line-height-sm); flex: 1; @@ -106,8 +106,8 @@ .mutedChannelContainer { display: flex; font-family: var(--asc-text-global-font-family); - background-color: var(--live-chat-asc-color-base-shade4, var(--asc-color-base-shade4)); - color: var(--live-chat-asc-color-base-shade1, var(--asc-color-base-shade1)); + background-color: var(--asc-color-base-shade4); + color: var(--asc-color-base-shade1); max-width: 100%; padding: var(--asc-spacing-s2); padding-left: var(--asc-spacing-m1); @@ -116,11 +116,11 @@ } .mutedIcon { - color: var(--live-chat-asc-color-base-shade3, var(--asc-color-base-shade3)); + color: var(--asc-color-base-shade3); } .commentAltIcon { - color: var(--live-chat-asc-color-base-shade2, var(--asc-color-base-shade2)); + color: var(--asc-color-base-shade2); } .banStatePanel { @@ -132,5 +132,5 @@ text-align: center; gap: var(--asc-spacing-s2); padding: var(--asc-spacing-m1); - color: var(--live-chat-asc-color-base-shade2, var(--asc-color-base-shade2)); + color: var(--asc-color-base-shade2); } diff --git a/src/v4/chat/pages/AmityLiveChatPage/index.tsx b/src/v4/chat/pages/AmityLiveChatPage/index.tsx index 18321dd20..6aac7f660 100644 --- a/src/v4/chat/pages/AmityLiveChatPage/index.tsx +++ b/src/v4/chat/pages/AmityLiveChatPage/index.tsx @@ -4,6 +4,7 @@ import AmityLiveChatHeader from '~/v4/chat/components/AmityLiveChatHeader'; import ChatContainer from './ChatContainer'; import styles from './styles.module.css'; import { LiveChatNotificationProvider } from '~/v4/chat/providers/LiveChatNotificationProvider'; +import { useAmityPage } from '~/v4/core/hooks/uikit'; interface AmityLiveChatPageProps { channelId: Amity.Channel['channelId']; @@ -11,14 +12,15 @@ interface AmityLiveChatPageProps { export const AmityLiveChatPage = ({ channelId }: AmityLiveChatPageProps) => { const channel = useChannel(channelId); + const pageId = 'live_chat'; + const { themeStyles } = useAmityPage({ pageId }); const ref = useRef<HTMLDivElement>(null); - const pageId = 'live_chat_page'; return ( <LiveChatNotificationProvider> - <div className={styles.amtiyLivechatPage} ref={ref}> + <div className={styles.amtiyLivechatPage} ref={ref} style={themeStyles}> <div className={styles.messageListHeaderWrap}> - <AmityLiveChatHeader channel={channel} /> + <AmityLiveChatHeader channel={channel} pageId={pageId} /> </div> <ChatContainer channel={channel} pageId={pageId} /> </div> diff --git a/src/v4/chat/pages/AmityLiveChatPage/styles.module.css b/src/v4/chat/pages/AmityLiveChatPage/styles.module.css index 942a220aa..0bb535a12 100644 --- a/src/v4/chat/pages/AmityLiveChatPage/styles.module.css +++ b/src/v4/chat/pages/AmityLiveChatPage/styles.module.css @@ -18,7 +18,7 @@ .amtiyLivechatPage { display: flex; flex-direction: column; - background-color: var(--live-chat-asc-color-base-background, var(--asc-color-base-background)); + background-color: var(--asc-color-base-background); height: 100%; overflow-y: hidden; } diff --git a/src/v4/core/components/Avatar/Avatar.tsx b/src/v4/core/components/Avatar/Avatar.tsx index 12a061429..8a324f3dd 100644 --- a/src/v4/core/components/Avatar/Avatar.tsx +++ b/src/v4/core/components/Avatar/Avatar.tsx @@ -2,14 +2,20 @@ import React, { useState, useCallback } from 'react'; import clsx from 'clsx'; import styles from './Avatar.module.css'; +export const enum AVATAR_SIZE { + SMALL = 'small', + MEDIUM = 'medium', + LARGE = 'large', +} + export interface AvatarProps { className?: string; avatar?: string | null; showOverlay?: boolean; onClick?: () => void; loading?: boolean; - backgroundImage?: string | null; - size?: 'small' | 'medium' | 'large'; + defaultImage?: React.ReactNode; + size?: AVATAR_SIZE; } export const Avatar = ({ @@ -18,8 +24,8 @@ export const Avatar = ({ showOverlay, onClick, loading, - size = 'medium', - backgroundImage, + size = AVATAR_SIZE.MEDIUM, + defaultImage, ...props }: AvatarProps) => { const [visible, setVisible] = useState(false); @@ -62,7 +68,9 @@ export const Avatar = ({ alt="Avatar" /> ) - ) : null} + ) : ( + defaultImage ?? null + )} </div> ); }; diff --git a/src/v4/core/components/Avatar/index.ts b/src/v4/core/components/Avatar/index.ts index d3fb6dfa7..0fb0a0baa 100644 --- a/src/v4/core/components/Avatar/index.ts +++ b/src/v4/core/components/Avatar/index.ts @@ -1 +1 @@ -export { Avatar } from './Avatar'; +export { Avatar, AVATAR_SIZE } from './Avatar'; diff --git a/src/v4/core/components/SocialMentionItem/index.tsx b/src/v4/core/components/SocialMentionItem/index.tsx index b9933079e..dc818cbc9 100644 --- a/src/v4/core/components/SocialMentionItem/index.tsx +++ b/src/v4/core/components/SocialMentionItem/index.tsx @@ -1,16 +1,15 @@ import React, { useCallback, useEffect, useRef } from 'react'; import clsx from 'clsx'; -import UserAvatar from '~/v4/chat/components/UserAvatar'; -import { backgroundImage as userBackgroundImage } from '~/icons/User'; import BanIcon from '~/icons/Ban'; import useObserver from '~/core/hooks/useObserver'; import useUser from '~/core/hooks/useUser'; import useImage from '~/core/hooks/useImage'; import styles from './styles.module.css'; import { MentionIcon } from '~/icons'; -import { SIZE_ALIAS } from '~/core/hooks/useSize'; import { FormattedMessage } from 'react-intl'; import { Typography } from '../index'; +import { Avatar, AVATAR_SIZE } from '~/v4/core/components/Avatar'; +import User from '~/v4/icons/User'; interface SocialMentionItemProps { id: string; @@ -58,11 +57,7 @@ const UserMentionItem = ({ className={clsx(styles.mentionItem, user?.isGlobalBanned && 'isBanned')} onMouseEnter={(e) => onMouseEnter(e, user?.isGlobalBanned)} > - <UserAvatar - size={SIZE_ALIAS.SMALL} - avatarUrl={avatarFileUrl} - defaultImage={userBackgroundImage} - /> + <Avatar size={AVATAR_SIZE.SMALL} avatar={avatarFileUrl} defaultImage={<User />} /> <div className={styles.userDisplayName}> <Typography.Body>{user?.displayName}</Typography.Body> </div> diff --git a/src/v4/icons/Chat.tsx b/src/v4/icons/Chat.tsx new file mode 100644 index 000000000..149eb508a --- /dev/null +++ b/src/v4/icons/Chat.tsx @@ -0,0 +1,26 @@ +import React from 'react'; + +const Svg = (props: React.SVGProps<SVGSVGElement>) => ( + <svg + width="100%" + height="100%" + viewBox="0 0 121 121" + fill="none" + xmlns="http://www.w3.org/2000/svg" + {...props} + > + <rect x="0.5" y="0.5" width="120" height="120" rx="60" fill="#D9E5FC" /> + <path + d="M53.3621 77.2578V64.4753C53.3621 61.6791 55.6698 59.3623 58.4549 59.3623H81.3727C84.2374 59.3623 86.4655 61.6791 86.4655 64.4753V77.2578C86.4655 80.1339 84.2374 82.3708 81.3727 82.3708V86.7648C81.3727 87.404 80.6565 87.7235 80.179 87.3241L73.5743 82.3708H58.4549C55.6698 82.3708 53.3621 80.1339 53.3621 77.2578Z" + fill="white" + /> + <path + d="M78.1733 64.1802V46.5515C78.1733 42.2387 74.5708 38.6888 70.2321 38.6888H40.63C36.1715 38.6888 32.6888 42.247 32.6888 46.5515V64.1802C32.6888 68.3305 35.7628 71.6152 39.8188 72.0042V77.2915C39.8188 78.8883 41.6295 79.6431 42.7981 78.7042L51.8149 72.0429H70.2321C74.5625 72.0429 78.1733 68.6113 78.1733 64.1802Z" + fill="white" + stroke="#D9E5FC" + strokeWidth="1.6224" + /> + </svg> +); + +export default Svg; diff --git a/src/v4/icons/Community.tsx b/src/v4/icons/Community.tsx index ced2fc1b7..1ee18df1d 100644 --- a/src/v4/icons/Community.tsx +++ b/src/v4/icons/Community.tsx @@ -18,5 +18,3 @@ const Community = (props: React.SVGProps<SVGSVGElement>) => ( ); export default Community; - -export const backgroundImage = `url("data:image/svg+xml,%3Csvg width='100%25' height='100%25' viewBox='0 0 40 40' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='40' height='40' rx='20' fill='%23D9E5FC'/%3E%3Cpath d='M19.8462 12C20.7625 12 21.6413 12.356 22.2893 12.9898C22.9373 13.6235 23.3013 14.4831 23.3013 15.3793C23.3013 16.2756 22.9373 17.1351 22.2893 17.7688C21.6413 18.4026 20.7625 18.7586 19.8462 18.7586C18.9298 18.7586 18.051 18.4026 17.403 17.7688C16.755 17.1351 16.391 16.2756 16.391 15.3793C16.391 14.4831 16.755 13.6235 17.403 12.9898C18.051 12.356 18.9298 12 19.8462 12ZM12.9359 14.4138C13.4887 14.4138 14.0021 14.5586 14.4463 14.8193C14.2982 16.2 14.7128 17.571 15.5618 18.6428C15.0682 19.5697 14.081 20.2069 12.9359 20.2069C12.1504 20.2069 11.3972 19.9017 10.8418 19.3585C10.2864 18.8153 9.97436 18.0786 9.97436 17.3103C9.97436 16.5421 10.2864 15.8054 10.8418 15.2622C11.3972 14.719 12.1504 14.4138 12.9359 14.4138ZM26.7564 14.4138C27.5419 14.4138 28.2951 14.719 28.8505 15.2622C29.4059 15.8054 29.7179 16.5421 29.7179 17.3103C29.7179 18.0786 29.4059 18.8153 28.8505 19.3585C28.2951 19.9017 27.5419 20.2069 26.7564 20.2069C25.6113 20.2069 24.6241 19.5697 24.1305 18.6428C24.9795 17.571 25.3941 16.2 25.246 14.8193C25.6903 14.5586 26.2036 14.4138 26.7564 14.4138ZM13.4295 24.3103C13.4295 22.3117 16.3022 20.6897 19.8462 20.6897C23.3901 20.6897 26.2628 22.3117 26.2628 24.3103V26H13.4295V24.3103ZM8 26V24.5517C8 23.2097 9.86577 22.08 12.3929 21.7517C11.8105 22.4083 11.4551 23.3159 11.4551 24.3103V26H8ZM31.6923 26H28.2372V24.3103C28.2372 23.3159 27.8818 22.4083 27.2994 21.7517C29.8265 22.08 31.6923 23.2097 31.6923 24.5517V26Z' fill='white'/%3E%3C/svg%3E%0A");`; diff --git a/src/v4/icons/User.tsx b/src/v4/icons/User.tsx new file mode 100644 index 000000000..06990514d --- /dev/null +++ b/src/v4/icons/User.tsx @@ -0,0 +1,24 @@ +import React from 'react'; + +const Svg = (props: React.SVGProps<SVGSVGElement>) => ( + <svg + width="100%" + height="100%" + viewBox="0 0 40 40" + fill="none" + xmlns="http://www.w3.org/2000/svg" + {...props} + > + <rect width="40" height="40" rx="20" fill="#D9E5FC" /> + <path + d="M23.1255 13.1312C22.385 12.4069 21.3806 12 20.3333 12C19.2861 12 18.2817 12.4069 17.5412 13.1312C16.8006 13.8555 16.3846 14.8378 16.3846 15.8621C16.3846 16.8864 16.8006 17.8687 17.5412 18.593C18.2817 19.3172 19.2861 19.7241 20.3333 19.7241C21.3806 19.7241 22.385 19.3172 23.1255 18.593C23.866 17.8687 24.2821 16.8864 24.2821 15.8621C24.2821 14.8378 23.866 13.8555 23.1255 13.1312Z" + fill="white" + /> + <path + d="M20.3333 21.931C16.2831 21.931 13 23.7848 13 26.069V28H27.6667V26.069C27.6667 23.7848 24.3836 21.931 20.3333 21.931Z" + fill="white" + /> + </svg> +); + +export default Svg; diff --git a/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx b/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx index 8813ef1c9..4da490afc 100644 --- a/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx +++ b/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx @@ -4,9 +4,10 @@ import { useIntl } from 'react-intl'; import { isValidHttpUrl } from '~/utils'; import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; import { Icon } from '~/v4/core/components/Icon'; -import { backgroundImage as communityBackgroundImage } from '~/v4/icons/Community'; +import Community from '~/v4/icons/Community'; import styles from './ShareStoryButton.module.css'; import { Avatar } from '~/v4/core/components'; +import { AVATAR_SIZE } from '~/v4/core/components/Avatar/Avatar'; interface ShareButtonProps { onClick: () => void; @@ -44,9 +45,9 @@ export const ShareStoryButton = ({ {!elementConfig?.hide_avatar && ( <Avatar data-qa-anchor="share_story_button_image_view" - size="small" + size={AVATAR_SIZE.SMALL} avatar={avatar} - backgroundImage={communityBackgroundImage} + defaultImage={<Community />} /> )} <span>{formatMessage({ id: 'storyDraft.button.shareStory' })}</span> diff --git a/src/v4/social/internal-components/StoryPreview/StoryPreview.tsx b/src/v4/social/internal-components/StoryPreview/StoryPreview.tsx index bb077752e..f7b93ad95 100644 --- a/src/v4/social/internal-components/StoryPreview/StoryPreview.tsx +++ b/src/v4/social/internal-components/StoryPreview/StoryPreview.tsx @@ -1,11 +1,11 @@ import React, { useEffect, useRef, useState } from 'react'; -import UserAvatar from '~/v4/chat/components/UserAvatar'; -import { backgroundImage as communityBackgroundImage } from '~/icons/Community'; +import Community from '~/v4/icons/Community'; import Verified from '~/v4/social/icons/verified'; import { Typography } from '~/v4/core/components'; import { HyperLink } from '~/v4/social/elements/HyperLink'; import TruncateMarkup from 'react-truncate-markup'; import styles from './StoryPreview.module.css'; +import { Avatar } from '~/v4/core/components/Avatar/'; type AmityStoryMediaType = { type: 'image'; url: string } | { type: 'video'; url: string }; @@ -109,7 +109,7 @@ export const StoryPreview: React.FC<StoryPreviewProps> = ({ <div className={styles.progressFill} style={{ width: `${progress}%` }} /> </div> <div className={styles.userInfo}> - <UserAvatar avatarUrl={avatar} defaultImage={communityBackgroundImage} /> + <Avatar avatar={avatar} defaultImage={<Community />} /> <Typography.BodyBold className={styles.storyPreviewTitle}> <span className={styles.nameContainer}> {title} {isOfficial && <Verified fill="white" />} From 5f2823ae071a552b2d7e934dc3066c529443d5b3 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Tue, 18 Jun 2024 17:51:53 +0700 Subject: [PATCH 136/300] fix: view story page context (#420) --- src/v4/core/providers/NavigationProvider.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/v4/core/providers/NavigationProvider.tsx b/src/v4/core/providers/NavigationProvider.tsx index 70c253dde..d4d6c1151 100644 --- a/src/v4/core/providers/NavigationProvider.tsx +++ b/src/v4/core/providers/NavigationProvider.tsx @@ -378,13 +378,14 @@ export default function NavigationProvider({ ({ targetId, storyType, targetIds }) => { const next = { type: PageTypes.ViewStoryPage, - targetId, - storyType, - targetIds, + context: { + targetId, + targetType: 'community', + storyType, + targetIds, + }, }; - if (onChangePage) return onChangePage(next); - pushPage(next); }, [onChangePage, pushPage], From 736182afea8dd4bd437d81e025e3c30057a028ac Mon Sep 17 00:00:00 2001 From: Pitchaya T <33589608+ptchayap@users.noreply.github.com> Date: Tue, 18 Jun 2024 20:11:39 +0700 Subject: [PATCH 137/300] fix: build include css (#421) --- package.json | 6 +++++- tsup.config.ts | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 5a87716b6..d6ef5019c 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,10 @@ "types": "./dist/index.d.ts", "import": "./dist/esm/index.js", "default": "./dist/index.js" + }, + "./dist/index.css": { + "import": "./dist/esm/index.css", + "default": "./dist/index.css" } }, "files": [ @@ -35,7 +39,7 @@ "tsc": "tsc" }, "peerDependencies": { - "@amityco/ts-sdk": "~6.16.0", + "@amityco/ts-sdk": "^6.26.3", "react": ">=17.0.2", "react-dom": ">=17.0.2" }, diff --git a/tsup.config.ts b/tsup.config.ts index 51a6a79b6..f732c5e8f 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -22,4 +22,7 @@ export default defineConfig((options) => ({ '(typeof process !== "undefined" && process.env && process.env.NODE_ENV ? (process.env.NODE_ENV !== "production") : false)', }), ], + loader: { + '.css': 'local-css', + }, })); From afb784b0da90b4802b5d3d83b09e1f1e3ec1dbe2 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Wed, 19 Jun 2024 20:34:57 +0700 Subject: [PATCH 138/300] fix: ASC-23233 - disabled button when file is uploading and fix 429 issue (#422) * fix: disabled remove button when file is uploading * fix: add handle document event when menu opened --- .../components/Uploaders/File/StyledFile.tsx | 8 ++++++- .../components/Comment/StyledComment.tsx | 21 ++++++++++++++++-- .../post/Post/DefaultPostRenderer.tsx | 22 +++++++++++++++++-- 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/core/components/Uploaders/File/StyledFile.tsx b/src/core/components/Uploaders/File/StyledFile.tsx index f9ae5f3dc..33f473150 100644 --- a/src/core/components/Uploaders/File/StyledFile.tsx +++ b/src/core/components/Uploaders/File/StyledFile.tsx @@ -61,6 +61,8 @@ const StyledFile = ({ const isImg = type?.includes('image'); + const isUploading = progress < 100; + return ( <FileContainer href={url} download data-qa-anchor={dataQaAnchor}> <Content> @@ -74,7 +76,11 @@ const StyledFile = ({ {!!isRejected && <RetryButton onClick={retryCallback} />} {!!onRemove && ( - <RemoveButton data-qa-anchor="uploaders-file-remove-button" onClick={removeCallback} /> + <RemoveButton + data-qa-anchor="uploaders-file-remove-button" + onClick={removeCallback} + disabled={isUploading} + /> )} </ButtonContainer> </Content> diff --git a/src/social/components/Comment/StyledComment.tsx b/src/social/components/Comment/StyledComment.tsx index 2498b13ff..6958cacd3 100644 --- a/src/social/components/Comment/StyledComment.tsx +++ b/src/social/components/Comment/StyledComment.tsx @@ -1,4 +1,4 @@ -import React, { forwardRef, MutableRefObject, useRef, useState } from 'react'; +import React, { forwardRef, MutableRefObject, useEffect, useRef, useState } from 'react'; import Truncate from 'react-truncate-markup'; import { FormattedMessage, useIntl } from 'react-intl'; @@ -200,6 +200,18 @@ const StyledComment = (props: StyledCommentProps) => { const toggle = () => setIsMenuOpen((prev) => !prev); + useEffect(() => { + if (isMenuOpen) { + document.addEventListener('click', toggle); + } else { + document.removeEventListener('click', toggle); + } + + return () => { + document.removeEventListener('click', toggle); + }; + }, [isMenuOpen]); + return ( <> <Avatar avatar={authorAvatar} backgroundImage={UserImage} /> @@ -278,7 +290,12 @@ const StyledComment = (props: StyledCommentProps) => { <OptionButtonContainer> <div ref={buttonContainerRef}> - <OptionsButton onClick={toggle}> + <OptionsButton + onClick={(ev) => { + ev.stopPropagation(); + toggle(); + }} + > <OptionsIcon /> </OptionsButton> </div> diff --git a/src/social/components/post/Post/DefaultPostRenderer.tsx b/src/social/components/post/Post/DefaultPostRenderer.tsx index c4b08f66f..ce553f85a 100644 --- a/src/social/components/post/Post/DefaultPostRenderer.tsx +++ b/src/social/components/post/Post/DefaultPostRenderer.tsx @@ -1,4 +1,4 @@ -import React, { useRef, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; import Button, { PrimaryButton } from '~/core/components/Button'; import Modal from '~/core/components/Modal'; @@ -187,6 +187,18 @@ const DefaultPostRenderer = (props: DefaultPostRendererProps) => { const toggle = () => setIsMenuOpen((prev) => !prev); + useEffect(() => { + if (isMenuOpen) { + document.addEventListener('click', toggle); + } else { + document.removeEventListener('click', toggle); + } + + return () => { + document.removeEventListener('click', toggle); + }; + }, [isMenuOpen]); + function showHasBeenReviewedMessageIfNeeded(error: unknown) { if (error instanceof Error) { if (error.message.includes(ERROR_POST_HAS_BEEN_REVIEWED)) { @@ -255,7 +267,13 @@ const DefaultPostRenderer = (props: DefaultPostRendererProps) => { {!loading && ( <OptionButtonContainer> <div ref={buttonContainerRef}> - <OptionsButton onClick={toggle} className={className}> + <OptionsButton + onClick={(event) => { + event.stopPropagation(); + toggle(); + }} + className={className} + > <OptionsIcon /> </OptionsButton> </div> From f3bc479d9340dfadc959f48feefbf747be087d52 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Wed, 19 Jun 2024 20:40:15 +0700 Subject: [PATCH 139/300] fix: ASC-23288 - disabled submit vote button (#424) * fix: disabled submit vote button * chore: remove console.log --- src/social/components/post/PollContent/index.tsx | 4 ++-- src/social/components/post/PollContent/styles.tsx | 13 +++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/social/components/post/PollContent/index.tsx b/src/social/components/post/PollContent/index.tsx index 758f5ed2b..bdb80189a 100644 --- a/src/social/components/post/PollContent/index.tsx +++ b/src/social/components/post/PollContent/index.tsx @@ -136,7 +136,7 @@ const PollContent = ({ pollId }: { pollId?: string }) => { } }; - const handleSubmit: React.MouseEventHandler<HTMLAnchorElement> = async (e) => { + const handleSubmit: React.MouseEventHandler<HTMLButtonElement> = async (e) => { if (!pollId) return; e.preventDefault(); @@ -173,7 +173,7 @@ const PollContent = ({ pollId }: { pollId?: string }) => { <ResultList answers={answers} /> )} {!isVoted && ( - <SubmitButton disabled={!answerIds.length} onClick={handleSubmit}> + <SubmitButton disabled={!answerIds.length} onClick={(e) => handleSubmit(e)}> <FormattedMessage id="poll.vote.submit" /> </SubmitButton> )} diff --git a/src/social/components/post/PollContent/styles.tsx b/src/social/components/post/PollContent/styles.tsx index 05de8b63b..97bd23fd5 100644 --- a/src/social/components/post/PollContent/styles.tsx +++ b/src/social/components/post/PollContent/styles.tsx @@ -45,7 +45,7 @@ export const PollInformation = styled.div` margin-bottom: 12px; `; -export const SubmitButton = styled.a.attrs<{ disabled?: boolean }>({ role: 'button' })` +export const SubmitButton = styled.button.attrs<{ disabled?: boolean }>({ role: 'button' })` display: block; width: 100%; text-align: center; @@ -53,11 +53,12 @@ export const SubmitButton = styled.a.attrs<{ disabled?: boolean }>({ role: 'butt font-weight: 600; ${({ disabled, theme }) => - disabled ? theme.palette.primary.shade2 : theme.palette.primary.main}; - - &:hover { - cursor: pointer; - } + disabled + ? { + color: theme.palette.primary.shade2, + cursor: 'not-allowed', + } + : { color: theme.palette.primary.main, cursor: 'pointer' }}; `; export const ChipContainer = styled.div` From bc5f833011c1a0d9fda5bdbc8e56f6bcfeccb637 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Thu, 20 Jun 2024 10:52:56 +0700 Subject: [PATCH 140/300] fix: formatDuration (#429) --- src/social/components/post/GalleryContent/styles.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/social/components/post/GalleryContent/styles.tsx b/src/social/components/post/GalleryContent/styles.tsx index e3f1619f5..8658807e0 100644 --- a/src/social/components/post/GalleryContent/styles.tsx +++ b/src/social/components/post/GalleryContent/styles.tsx @@ -61,7 +61,8 @@ const BaseThumbnail = ({ const { formatNumber } = useIntl(); const formatDuration = (inputDuration: number) => { - const hour = formatNumber(Math.floor(inputDuration / 60 / 60), { + const floorHour = Math.floor(inputDuration / 60 / 60); + const hour = formatNumber(floorHour, { minimumIntegerDigits: 2, maximumSignificantDigits: 2, }); @@ -70,12 +71,14 @@ const BaseThumbnail = ({ maximumSignificantDigits: 2, }); - const second = formatNumber((inputDuration % 60) % 60, { + const roundedSecond = Math.round((inputDuration % 60) % 60); + + const second = formatNumber(roundedSecond, { minimumIntegerDigits: 2, maximumSignificantDigits: 2, }); - if (hour === '00') { + if (floorHour === 0) { return `${minute}:${second}`; } return `${hour}:${minute}:${second}`; From 84d9d5722d319b65ed9c196d2e1988b4386eb0a0 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Thu, 20 Jun 2024 11:00:42 +0700 Subject: [PATCH 141/300] fix: link text color (#426) --- src/core/components/Linkify/index.tsx | 15 ++++++++++----- src/core/components/Linkify/styles.tsx | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/core/components/Linkify/index.tsx b/src/core/components/Linkify/index.tsx index 044871eb9..f2cabed8a 100644 --- a/src/core/components/Linkify/index.tsx +++ b/src/core/components/Linkify/index.tsx @@ -7,12 +7,17 @@ type UiKitLinkifyProps = Omit<React.ComponentProps<typeof Linkify>, 'componentDe const UiKitLinkify = (props: UiKitLinkifyProps) => ( <Linkify - componentDecorator={(decoratedHref?: string, decoratedText?: string, key?: string) => ( - <Link key={key} target="blank" rel="noopener noreferrer" href={decoratedHref}> - {decoratedText} - </Link> - )} {...props} + options={{ + render: ({ attributes, content }) => { + const { href, ...props } = attributes; + return ( + <Link target="blank" rel="noopener noreferrer" href={href} {...props}> + {content} + </Link> + ); + }, + }} /> ); diff --git a/src/core/components/Linkify/styles.tsx b/src/core/components/Linkify/styles.tsx index 3ec298dc7..fd245ee97 100644 --- a/src/core/components/Linkify/styles.tsx +++ b/src/core/components/Linkify/styles.tsx @@ -1,7 +1,7 @@ import styled from 'styled-components'; export const Link = styled.a` - color: ${({ theme }) => theme.palette.primary.shade1}; + color: ${({ theme }) => theme.palette.primary.main}; text-decoration: none; &:hover { From 5b63b65a8cd8a185007a716320611bcb4270339d Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Thu, 20 Jun 2024 11:15:42 +0700 Subject: [PATCH 142/300] fix: reduce comment api network call amount (#427) --- src/social/components/CommentList/index.tsx | 114 ++++++++++++++++++-- 1 file changed, 105 insertions(+), 9 deletions(-) diff --git a/src/social/components/CommentList/index.tsx b/src/social/components/CommentList/index.tsx index 2e27d5bdc..058eacc6d 100644 --- a/src/social/components/CommentList/index.tsx +++ b/src/social/components/CommentList/index.tsx @@ -1,4 +1,4 @@ -import React, { memo } from 'react'; +import React, { memo, useEffect, useState } from 'react'; import { useIntl } from 'react-intl'; import Comment from '~/social/components/Comment'; @@ -8,6 +8,8 @@ import LoadMoreWrapper from '../LoadMoreWrapper'; import usePostSubscription from '~/social/hooks/usePostSubscription'; import { SubscriptionLevels } from '@amityco/ts-sdk'; import useCommentsCollection from '~/social/hooks/collections/useCommentsCollection'; +import usePost from '~/social/hooks/usePost'; +import { LoadMoreButton, ShevronDownIcon } from '../LoadMoreWrapper/styles'; interface CommentListProps { parentId?: string; @@ -19,7 +21,7 @@ interface CommentListProps { limit?: number; } -const CommentList = ({ +const InnerCommentList = ({ parentId, referenceId, referenceType, @@ -28,10 +30,13 @@ const CommentList = ({ // filterByParentId = false, readonly = false, isExpanded = true, -}: CommentListProps) => { + callLoadMoreAgain = false, +}: CommentListProps & { + callLoadMoreAgain?: boolean; +}) => { + const [isCallAgain, setIsCallAgain] = useState(!callLoadMoreAgain); const { formatMessage } = useIntl(); const isReplyComment = !!parentId; - const { comments, hasMore, loadMore } = useCommentsCollection({ parentId, referenceId, @@ -39,11 +44,12 @@ const CommentList = ({ limit, }); - usePostSubscription({ - postId: referenceId, - level: SubscriptionLevels.COMMENT, - shouldSubscribe: () => referenceType === 'post' && !parentId, - }); + useEffect(() => { + if (isCallAgain && comments.length > 0 && hasMore) { + loadMore(); + setIsCallAgain(false); + } + }, [comments, hasMore, loadMore, isCallAgain]); const loadMoreText = isReplyComment ? formatMessage({ id: 'collapsible.viewMoreReplies' }) @@ -73,4 +79,94 @@ const CommentList = ({ ); }; +const PostCommentList = (props: CommentListProps) => { + const { + parentId, + referenceId, + referenceType, + limit = 5, + // TODO: breaking change + // filterByParentId = false, + readonly = false, + isExpanded = true, + } = props; + const { formatMessage } = useIntl(); + const [firstRender, setFirstRender] = useState(true); + const isReplyComment = !!parentId; + const post = usePost(referenceId); + + usePostSubscription({ + postId: referenceId, + level: SubscriptionLevels.COMMENT, + shouldSubscribe: () => referenceType === 'post' && !parentId, + }); + + const loadMoreText = isReplyComment + ? formatMessage({ id: 'collapsible.viewMoreReplies' }) + : formatMessage({ id: 'collapsible.viewMoreComments' }); + + const prependIcon = isReplyComment ? ( + <TabIconContainer> + <TabIcon /> + </TabIconContainer> + ) : null; + + if (firstRender) { + return ( + <LoadMoreWrapper + hasMore={(post?.comments.length || 0) > (post?.latestComments?.length || 0)} + loadMore={() => { + setFirstRender(false); + }} + text={loadMoreText} + className={isReplyComment ? 'reply-button' : 'comments-button'} + prependIcon={prependIcon} + appendIcon={null} + isExpanded={isExpanded} + contentSlot={(post?.latestComments || []).map((comment: Amity.Comment) => ( + <Comment key={comment.commentId} commentId={comment.commentId} readonly={readonly} /> + ))} + /> + ); + } + + return <InnerCommentList {...props} callLoadMoreAgain />; +}; + +const ReplayCommentList = (props: CommentListProps) => { + const [isExpanded, setExpanded] = useState(false); + const { formatMessage } = useIntl(); + + const prependIcon = ( + <TabIconContainer> + <TabIcon /> + </TabIconContainer> + ); + + if (!isExpanded) { + return ( + <LoadMoreButton className="reply-button" onClick={() => setExpanded((prev) => !prev)}> + {prependIcon} {formatMessage({ id: 'collapsible.viewMoreReplies' })} + </LoadMoreButton> + ); + } + + return <InnerCommentList {...props} isExpanded />; +}; + +const CommentList = (props: CommentListProps) => { + const { referenceType, parentId } = props; + const isReplyComment = !!parentId; + + if (isReplyComment) { + return <ReplayCommentList {...props} />; + } + + if (referenceType === 'post' && !isReplyComment) { + return <PostCommentList {...props} />; + } + + return <InnerCommentList {...props} />; +}; + export default memo(CommentList); From 814e8a8db53c366e55cf6b2ded48a4186fd248e1 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Thu, 20 Jun 2024 11:19:08 +0700 Subject: [PATCH 143/300] fix: like button color (#425) --- src/icons/ThumbsUp.tsx | 5 +---- src/social/components/CommentLikeButton/styles.tsx | 3 +++ src/social/components/post/LikeButton/styles.tsx | 3 +++ 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/icons/ThumbsUp.tsx b/src/icons/ThumbsUp.tsx index e49b18ecc..3381cf279 100644 --- a/src/icons/ThumbsUp.tsx +++ b/src/icons/ThumbsUp.tsx @@ -10,10 +10,7 @@ function Icon(props: React.SVGProps<SVGSVGElement>) { viewBox="0 0 20 20" {...props} > - <path - fill="#A5A9B5" - d="M17.793 11.025c.148.854.037 1.67-.334 2.338a3.437 3.437 0 01-.668 2.487c-.037 2.078-1.299 3.525-4.156 3.525h-.854c-3.785 0-4.935-1.41-6.605-1.447-.112.482-.594.853-1.114.853H1.688c-.667 0-1.187-.52-1.187-1.187V8.687c0-.63.52-1.187 1.188-1.187h3.636c.705-.594 1.707-2.227 2.56-3.08.52-.52.372-4.045 2.673-4.045 2.115 0 3.525 1.188 3.525 3.896 0 .706-.148 1.262-.334 1.745h1.373c1.781 0 3.191 1.521 3.191 3.154 0 .705-.185 1.299-.519 1.855zm-2.3 2.004c.816-.742.704-1.892.185-2.449.37 0 .853-.705.853-1.373-.037-.705-.63-1.41-1.41-1.41h-3.86c0-1.41 1.04-2.078 1.04-3.526 0-.89 0-2.115-1.744-2.115-.705.705-.372 2.487-1.41 3.526-1.002 1.002-2.45 3.6-3.526 3.6H5.25v6.939c1.967 0 3.71 1.373 6.346 1.373h1.41c1.299 0 2.264-.631 1.967-2.412.556-.334 1.002-1.373.52-2.153zM3.765 16.406a.903.903 0 00-.891-.89.88.88 0 00-.89.89c0 .52.37.89.89.89a.88.88 0 00.89-.89z" - ></path> + <path d="M17.793 11.025c.148.854.037 1.67-.334 2.338a3.437 3.437 0 01-.668 2.487c-.037 2.078-1.299 3.525-4.156 3.525h-.854c-3.785 0-4.935-1.41-6.605-1.447-.112.482-.594.853-1.114.853H1.688c-.667 0-1.187-.52-1.187-1.187V8.687c0-.63.52-1.187 1.188-1.187h3.636c.705-.594 1.707-2.227 2.56-3.08.52-.52.372-4.045 2.673-4.045 2.115 0 3.525 1.188 3.525 3.896 0 .706-.148 1.262-.334 1.745h1.373c1.781 0 3.191 1.521 3.191 3.154 0 .705-.185 1.299-.519 1.855zm-2.3 2.004c.816-.742.704-1.892.185-2.449.37 0 .853-.705.853-1.373-.037-.705-.63-1.41-1.41-1.41h-3.86c0-1.41 1.04-2.078 1.04-3.526 0-.89 0-2.115-1.744-2.115-.705.705-.372 2.487-1.41 3.526-1.002 1.002-2.45 3.6-3.526 3.6H5.25v6.939c1.967 0 3.71 1.373 6.346 1.373h1.41c1.299 0 2.264-.631 1.967-2.412.556-.334 1.002-1.373.52-2.153zM3.765 16.406a.903.903 0 00-.891-.89.88.88 0 00-.89.89c0 .52.37.89.89.89a.88.88 0 00.89-.89z"></path> </svg> ); } diff --git a/src/social/components/CommentLikeButton/styles.tsx b/src/social/components/CommentLikeButton/styles.tsx index e8fcc405c..205686dc2 100644 --- a/src/social/components/CommentLikeButton/styles.tsx +++ b/src/social/components/CommentLikeButton/styles.tsx @@ -5,6 +5,7 @@ import { ThumbsUp } from '~/icons'; const isLikedStyle = css` color: ${({ theme }) => theme.palette.primary.main}; + fill: ${({ theme }) => theme.palette.primary.main}; `; export const StyledLikeButton = styled(SecondaryButton)` @@ -18,6 +19,8 @@ export const StyledLikeButton = styled(SecondaryButton)` export const BaseLikeIcon = styled(ThumbsUp)<{ icon?: ReactNode }>` font-size: 16px; + color: #a5a9b5; + fill: #a5a9b5; `; export const IsLikedLikeIcon = styled(BaseLikeIcon)<{ icon?: ReactNode }>` diff --git a/src/social/components/post/LikeButton/styles.tsx b/src/social/components/post/LikeButton/styles.tsx index 54057d1a3..861460e3a 100644 --- a/src/social/components/post/LikeButton/styles.tsx +++ b/src/social/components/post/LikeButton/styles.tsx @@ -6,6 +6,7 @@ import { ThumbsUp } from '~/icons'; const isLikedStyle = css` color: ${({ theme }) => theme.palette.primary.main}; + fill: ${({ theme }) => theme.palette.primary.main}; `; export const StyledLikeButton = styled(SecondaryButton)` @@ -15,6 +16,8 @@ export const StyledLikeButton = styled(SecondaryButton)` export const BaseLikeIcon = styled(ThumbsUp)<{ icon?: ReactNode }>` font-size: 16px; margin-right: 5px; + color: #a5a9b5; + fill: #a5a9b5; `; export const IsLikedLikeIcon = styled(BaseLikeIcon)<{ icon?: ReactNode }>` From 09ba538ccf8b56525ced2412ab638c9b78744f0b Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Thu, 20 Jun 2024 11:33:29 +0700 Subject: [PATCH 144/300] fix: play icon (#428) --- src/icons/Play.tsx | 5 +---- .../components/post/GalleryContent/styles.tsx | 21 ++++++++++++++++++- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/icons/Play.tsx b/src/icons/Play.tsx index 316c11b60..bd71bb88b 100644 --- a/src/icons/Play.tsx +++ b/src/icons/Play.tsx @@ -10,10 +10,7 @@ function Icon(props: React.SVGProps<SVGSVGElement>) { viewBox="0 0 25 24" {...props} > - <path - fill="#fff" - d="M19.531 10.809a1.71 1.71 0 010 2.918L7.156 21.039c-1.125.668-2.531-.14-2.531-1.477V4.938c0-1.44 1.512-2.039 2.531-1.44l12.375 7.312z" - ></path> + <path d="M19.531 10.809a1.71 1.71 0 010 2.918L7.156 21.039c-1.125.668-2.531-.14-2.531-1.477V4.938c0-1.44 1.512-2.039 2.531-1.44l12.375 7.312z"></path> </svg> ); } diff --git a/src/social/components/post/GalleryContent/styles.tsx b/src/social/components/post/GalleryContent/styles.tsx index 8658807e0..a229f0527 100644 --- a/src/social/components/post/GalleryContent/styles.tsx +++ b/src/social/components/post/GalleryContent/styles.tsx @@ -20,13 +20,32 @@ export const RemoveButton = styled(Button).attrs<{ right: 0.5em; `; -export const PlayIcon = styled(Play)` +export const StyledPlayIcon = styled(Play)` + fill: #fff; +`; + +export const PlayIconContainer = styled.div` position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); + display: flex; + justify-content: center; + align-items: center; + + width: 2.25rem; + height: 2.25rem; + + background-color: ${({ theme }) => theme.palette.base.shade2}; + border-radius: 50%; `; +export const PlayIcon = (props: HTMLAttributes<SVGElement>) => ( + <PlayIconContainer> + <StyledPlayIcon {...props} /> + </PlayIconContainer> +); + export const Duration = styled.div` position: absolute; left: 0.5rem; From 47fad160fbbf1b43b9b33678713ff4a24002175c Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Thu, 20 Jun 2024 16:25:03 +0700 Subject: [PATCH 145/300] feat: ASC-23090 - LinkPreview (#414) * feat: LinkPreview * fix: error handling * fix: types --- .../LinkPreview/LinkPreview.module.css | 41 +++++++ .../LinkPreview/LinkPreview.stories.tsx | 17 +++ .../PostContent/LinkPreview/LinkPreview.tsx | 105 ++++++++++++++++++ .../LinkPreviewSkeleton.module.css | 49 ++++++++ .../LinkPreview/LinkPreviewSkeleton.tsx | 21 ++++ .../PostContent/LinkPreview/index.tsx | 1 + .../PostContent/TextContent/TextContent.tsx | 57 +++++----- .../CommunityPrivateBadge.tsx | 2 - .../VideoPreview/VideoPreview.tsx | 4 +- .../internal-components/VideoPreview/index.ts | 2 +- src/v4/social/pages/DraftsPage/DraftsPage.tsx | 4 +- 11 files changed, 271 insertions(+), 32 deletions(-) create mode 100644 src/v4/social/components/PostContent/LinkPreview/LinkPreview.module.css create mode 100644 src/v4/social/components/PostContent/LinkPreview/LinkPreview.stories.tsx create mode 100644 src/v4/social/components/PostContent/LinkPreview/LinkPreview.tsx create mode 100644 src/v4/social/components/PostContent/LinkPreview/LinkPreviewSkeleton.module.css create mode 100644 src/v4/social/components/PostContent/LinkPreview/LinkPreviewSkeleton.tsx create mode 100644 src/v4/social/components/PostContent/LinkPreview/index.tsx diff --git a/src/v4/social/components/PostContent/LinkPreview/LinkPreview.module.css b/src/v4/social/components/PostContent/LinkPreview/LinkPreview.module.css new file mode 100644 index 000000000..c3b8e0090 --- /dev/null +++ b/src/v4/social/components/PostContent/LinkPreview/LinkPreview.module.css @@ -0,0 +1,41 @@ +.linkPreview { + cursor: pointer; + border: 1px solid var(--asc-color-base-shade4); + border-radius: 0.5rem; + overflow: hidden; +} + +.linkPreview__top { + background-color: var(--asc-color-base-shade4); + overflow: hidden; + width: 100%; + aspect-ratio: 21 / 9; +} + +.linkPreview__bottom { + padding: 0.75rem; + display: flex; + flex-direction: column; + gap: 0.12rem; +} + +.linkPreview__object { + width: 100%; + height: 100%; + object-fit: cover; +} + +.linkPreview__unableToPreview { + display: flex; + width: 100%; + height: 100%; + justify-content: center; + align-items: center; + background-color: var(--asc-color-base-shade4); +} + +.linkPreview__unableToPreview__icon { + width: 3rem; + height: 2.25rem; + fill: var(--asc-color-base-shade3); +} diff --git a/src/v4/social/components/PostContent/LinkPreview/LinkPreview.stories.tsx b/src/v4/social/components/PostContent/LinkPreview/LinkPreview.stories.tsx new file mode 100644 index 000000000..bbc331333 --- /dev/null +++ b/src/v4/social/components/PostContent/LinkPreview/LinkPreview.stories.tsx @@ -0,0 +1,17 @@ +import React from 'react'; + +import { LinkPreview } from './LinkPreview'; + +export default { + title: 'v4-social/internal-components/LinkPreview', +}; + +export const LinkPreviewStory = { + render: () => { + return ( + <LinkPreview url="https://medium.com/better-humans/how-to-wake%20-up-at-5-a-m-every-day-ceb02e29c802" /> + ); + }, + + name: 'LinkPreview', +}; diff --git a/src/v4/social/components/PostContent/LinkPreview/LinkPreview.tsx b/src/v4/social/components/PostContent/LinkPreview/LinkPreview.tsx new file mode 100644 index 000000000..300981e52 --- /dev/null +++ b/src/v4/social/components/PostContent/LinkPreview/LinkPreview.tsx @@ -0,0 +1,105 @@ +import React, { useState } from 'react'; +import { Typography } from '~/v4/core/components/Typography'; +import { VideoPreview } from '~/v4/social/internal-components/VideoPreview'; +import { useQuery } from '@tanstack/react-query'; + +import useSDK from '~/v4/core/hooks/useSDK'; +import { LinkPreviewSkeleton } from './LinkPreviewSkeleton'; +import styles from './LinkPreview.module.css'; + +const UnableToPreviewSvg = (props: React.SVGProps<SVGSVGElement>) => ( + <svg + width="36" + height="27" + viewBox="0 0 36 27" + fill="none" + xmlns="http://www.w3.org/2000/svg" + {...props} + > + <path d="M32.625 0C34.4531 0 36 1.54688 36 3.375V23.625C36 25.5234 34.4531 27 32.625 27H3.375C1.47656 27 0 25.5234 0 23.625V3.375C0 1.54688 1.47656 0 3.375 0H32.625ZM32.2031 23.625C32.4141 23.625 32.625 23.4844 32.625 23.2031V3.79688C32.625 3.58594 32.4141 3.375 32.2031 3.375H3.79688C3.51562 3.375 3.375 3.58594 3.375 3.79688V23.2031C3.375 23.4844 3.51562 23.625 3.79688 23.625H32.2031ZM9 6.1875C10.5469 6.1875 11.8125 7.45312 11.8125 9C11.8125 10.6172 10.5469 11.8125 9 11.8125C7.38281 11.8125 6.1875 10.6172 6.1875 9C6.1875 7.45312 7.38281 6.1875 9 6.1875ZM6.75 20.25V16.875L9.49219 14.1328C9.84375 13.7812 10.3359 13.7812 10.6875 14.1328L13.5 16.875L21.8672 8.50781C22.2188 8.15625 22.7109 8.15625 23.0625 8.50781L29.25 14.625V20.25H6.75Z" /> + </svg> +); + +const UnableToPreview = () => ( + <div className={styles.linkPreview__unableToPreview}> + <UnableToPreviewSvg className={styles.linkPreview__unableToPreview__icon} /> + </div> +); + +const usePreviewLink = ({ url }: { url: string }) => { + const { client } = useSDK(); + + return useQuery({ + enabled: !!client, + queryKey: ['asc-uikit', 'previewLink'], + queryFn: async () => { + const data = await client?.http.get<{ + title: 'string'; + description: 'string'; + image: 'string'; + video: 'string'; + }>('/api/v1/link-preview', { + params: { + url, + }, + }); + + return data?.data; + }, + retry: false, + }); +}; + +interface LinkPreviewProps { + url: string; +} + +export function LinkPreview({ url }: LinkPreviewProps) { + const urlObject = new URL(url); + + const previewData = usePreviewLink({ url }); + + if (previewData.isLoading) { + return <LinkPreviewSkeleton />; + } + + const handleClick = () => { + window.open(url, '_blank'); + }; + + if (previewData.isError) { + return ( + <div onClick={handleClick} className={styles.linkPreview}> + <div className={styles.linkPreview__top}> + <UnableToPreview /> + </div> + <div className={styles.linkPreview__bottom}> + <Typography.Caption>{urlObject.hostname}</Typography.Caption> + </div> + </div> + ); + } + + return ( + <div onClick={handleClick} className={styles.linkPreview}> + <div className={styles.linkPreview__top}> + {previewData.data?.image ? ( + <object data={previewData.data.image} className={styles.linkPreview__object}> + <UnableToPreview /> + </object> + ) : ( + <UnableToPreview /> + )} + </div> + {previewData.data?.video ? ( + <VideoPreview src={previewData.data.video} /> + ) : ( + <UnableToPreview /> + )} + <div className={styles.linkPreview__bottom}> + <Typography.Caption>{urlObject.hostname}</Typography.Caption> + <Typography.BodyBold>{previewData.data?.title || ''}</Typography.BodyBold> + </div> + </div> + ); +} diff --git a/src/v4/social/components/PostContent/LinkPreview/LinkPreviewSkeleton.module.css b/src/v4/social/components/PostContent/LinkPreview/LinkPreviewSkeleton.module.css new file mode 100644 index 000000000..7f2d06fbf --- /dev/null +++ b/src/v4/social/components/PostContent/LinkPreview/LinkPreviewSkeleton.module.css @@ -0,0 +1,49 @@ +.linkPreviewSkeleton { + border: 1px solid var(--asc-color-base-shade4); + border-radius: 0.5rem; + overflow: hidden; +} + +.linkPreviewSkeleton__bottom { + padding: 0.75rem; + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.linkPreviewSkeleton__bar1 { + height: 0.7109rem; + width: 14.9458rem; + border-radius: 1.0156rem; + background-color: var(--asc-color-base-shade4); +} + +.linkPreviewSkeleton__bar2 { + width: 11.0126rem; + height: 0.8203rem; + border-radius: 1.1719rem; + background-color: var(--asc-color-base-shade4); +} + +.linkPreviewSkeleton__image { + height: 11rem; + background-color: var(--asc-color-base-shade4); +} + +.linkPreviewSkeleton__animation { + animation: skeleton-pulse 1.5s ease-in-out infinite; +} + +@keyframes skeleton-pulse { + 0% { + opacity: 0.6; + } + + 50% { + opacity: 1; + } + + 100% { + opacity: 0.6; + } +} diff --git a/src/v4/social/components/PostContent/LinkPreview/LinkPreviewSkeleton.tsx b/src/v4/social/components/PostContent/LinkPreview/LinkPreviewSkeleton.tsx new file mode 100644 index 000000000..d9c02250f --- /dev/null +++ b/src/v4/social/components/PostContent/LinkPreview/LinkPreviewSkeleton.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import clsx from 'clsx'; +import styles from './LinkPreviewSkeleton.module.css'; + +export function LinkPreviewSkeleton() { + return ( + <div className={styles.linkPreviewSkeleton}> + <div + className={clsx(styles.linkPreviewSkeleton__image, styles.linkPreviewSkeleton__animation)} + /> + <div className={styles.linkPreviewSkeleton__bottom}> + <div + className={clsx(styles.linkPreviewSkeleton__bar1, styles.linkPreviewSkeleton__animation)} + /> + <div + className={clsx(styles.linkPreviewSkeleton__bar2, styles.linkPreviewSkeleton__animation)} + /> + </div> + </div> + ); +} diff --git a/src/v4/social/components/PostContent/LinkPreview/index.tsx b/src/v4/social/components/PostContent/LinkPreview/index.tsx new file mode 100644 index 000000000..413060de0 --- /dev/null +++ b/src/v4/social/components/PostContent/LinkPreview/index.tsx @@ -0,0 +1 @@ +export { LinkPreview } from './LinkPreview'; diff --git a/src/v4/social/components/PostContent/TextContent/TextContent.tsx b/src/v4/social/components/PostContent/TextContent/TextContent.tsx index f3df4b392..d79ece110 100644 --- a/src/v4/social/components/PostContent/TextContent/TextContent.tsx +++ b/src/v4/social/components/PostContent/TextContent/TextContent.tsx @@ -1,9 +1,11 @@ -import React, { useState, useMemo, ReactNode } from 'react'; +import React, { useState, useMemo, ReactNode, useEffect } from 'react'; import { Linkify } from '~/v4/social/internal-components/Linkify'; import { Mentioned, findChunks, processChunks } from '~/v4/helpers/utils'; import { Typography } from '~/v4/core/components'; +import { LinkPreview } from '../LinkPreview/LinkPreview'; import styles from './TextContent.module.css'; +import * as linkify from 'linkifyjs'; interface MentionHighlightTagProps { children: ReactNode; @@ -39,31 +41,36 @@ export const TextContent = ({ text = '', mentionees }: TextContentProps) => { return null; } + const linksFounded = linkify.find(text).filter((link) => link.type === 'url'); + return ( - <Typography.Body className={styles.postContent}> - <> - {chunks.map((chunk) => { - const key = `${text}-${chunk.start}-${chunk.end}`; - const sub = text.substring(chunk.start, chunk.end); - if (chunk.highlight) { - const mentionee = mentionees?.find((m) => m.index === chunk.start); - if (mentionee) { - return ( - <MentionHighlightTag key={key} mentionee={mentionee}> - {sub} - </MentionHighlightTag> - ); + <> + <Typography.Body className={styles.postContent}> + <> + {chunks.map((chunk) => { + const key = `${text}-${chunk.start}-${chunk.end}`; + const sub = text.substring(chunk.start, chunk.end); + if (chunk.highlight) { + const mentionee = mentionees?.find((m) => m.index === chunk.start); + if (mentionee) { + return ( + <MentionHighlightTag key={key} mentionee={mentionee}> + {sub} + </MentionHighlightTag> + ); + } + return <span key={key}>{sub}</span>; } - return <span key={key}>{sub}</span>; - } - return <Linkify key={key}>{sub}</Linkify>; - })} - </> - {isShowReadMore ? ( - <span className={styles.postContent__readmore} onClick={() => setIsReadMoreClick(true)}> - ...Read more - </span> - ) : null} - </Typography.Body> + return <Linkify key={key}>{sub}</Linkify>; + })} + </> + {isShowReadMore ? ( + <span className={styles.postContent__readmore} onClick={() => setIsReadMoreClick(true)}> + ...Read more + </span> + ) : null} + </Typography.Body> + {linksFounded && linksFounded.length > 0 && <LinkPreview url={linksFounded[0].href} />} + </> ); }; diff --git a/src/v4/social/elements/CommunityPrivateBadge/CommunityPrivateBadge.tsx b/src/v4/social/elements/CommunityPrivateBadge/CommunityPrivateBadge.tsx index b8b0c58b7..2242b3b7c 100644 --- a/src/v4/social/elements/CommunityPrivateBadge/CommunityPrivateBadge.tsx +++ b/src/v4/social/elements/CommunityPrivateBadge/CommunityPrivateBadge.tsx @@ -2,8 +2,6 @@ import clsx from 'clsx'; import React from 'react'; import { useAmityElement } from '~/v4/core/hooks/uikit'; import { IconComponent } from '~/v4/core/IconComponent'; -import { getDefaultConfig, useCustomization } from '~/v4/core/providers/CustomizationProvider'; -import { useGenerateStylesShadeColors } from '~/v4/core/providers/ThemeProvider'; import styles from './CommunityPrivateBadge.module.css'; const PrivateIconSvg = (props: React.SVGProps<SVGSVGElement>) => ( diff --git a/src/v4/social/internal-components/VideoPreview/VideoPreview.tsx b/src/v4/social/internal-components/VideoPreview/VideoPreview.tsx index 2449f0334..2237e0d5f 100644 --- a/src/v4/social/internal-components/VideoPreview/VideoPreview.tsx +++ b/src/v4/social/internal-components/VideoPreview/VideoPreview.tsx @@ -1,12 +1,12 @@ import React from 'react'; -type BaseVideoPreviewProps = { +type VideoPreviewProps = { src: string; mimeType?: string; mediaFit?: string; } & React.VideoHTMLAttributes<HTMLVideoElement>; -export const BaseVideoPreview = React.forwardRef<HTMLVideoElement, BaseVideoPreviewProps>( +export const VideoPreview = React.forwardRef<HTMLVideoElement, VideoPreviewProps>( ({ src, mimeType, mediaFit, ...props }, ref) => ( <video controls controlsList="nodownload" {...props} ref={ref} data-qa-anchor="video-preview"> <source src={src} type={mimeType} /> diff --git a/src/v4/social/internal-components/VideoPreview/index.ts b/src/v4/social/internal-components/VideoPreview/index.ts index 442a23464..259a74c07 100644 --- a/src/v4/social/internal-components/VideoPreview/index.ts +++ b/src/v4/social/internal-components/VideoPreview/index.ts @@ -1 +1 @@ -export { BaseVideoPreview } from './VideoPreview'; +export { VideoPreview } from './VideoPreview'; diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index 9da15bca6..79579555f 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -19,7 +19,7 @@ import { StoryRepository } from '@amityco/ts-sdk'; import { HyperLinkConfig } from '~/v4/social/components'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; import { useNotifications } from '~/v4/core/providers/NotificationProvider'; -import { BaseVideoPreview } from '~/v4/social/internal-components/VideoPreview'; +import { VideoPreview } from '~/v4/social/internal-components/VideoPreview'; import { useCommunityInfo } from '~/social/components/CommunityInfo/hooks'; import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; import { useNavigation } from '~/v4/core/providers/NavigationProvider'; @@ -258,7 +258,7 @@ export const PlainDraftStoryPage = ({ /> </div> ) : mediaType?.type === 'video' ? ( - <BaseVideoPreview + <VideoPreview className={styles.videoPreview} src={file ? URL.createObjectURL(file) : mediaType.url} mediaFit="contain" From 98b8d658a08adc051cf911897de532eae7a236f4 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Thu, 20 Jun 2024 16:31:59 +0700 Subject: [PATCH 146/300] fix: move post creator out of infinite scroll (#430) --- src/social/components/Feed/index.tsx | 449 ++++++++++++++------------- 1 file changed, 227 insertions(+), 222 deletions(-) diff --git a/src/social/components/Feed/index.tsx b/src/social/components/Feed/index.tsx index f33f72449..c77c83f76 100644 --- a/src/social/components/Feed/index.tsx +++ b/src/social/components/Feed/index.tsx @@ -43,58 +43,60 @@ const GlobalFeed = ({ } return ( - <FeedScrollContainer - className={className} - dataLength={contents.length} - next={loadMore} - hasMore={hasMore} - loader={null} - > - {!isHiddenProfile ? ( - <> - {showPostCreator ? ( - <PostCreator - data-qa-anchor="feed-post-creator-textarea" - targetType={'user'} - targetId={currentUserId || undefined} - enablePostTargetPicker={false} - onCreateSuccess={(newPost) => prependItem(newPost)} - /> - ) : null} - {isLoading && !loadMoreHasBeenCalled ? renderLoadingSkeleton() : null} - - {(!isLoading || loadMoreHasBeenCalled) && contents.length > 0 && ( - <LoadMoreWrapper - hasMore={hasMore} - loadMore={loadMore} - className="load-more no-border" - contentSlot={contents.map((content) => ( - <Post - key={content.postId} - postId={content.postId} - hidePostTarget={false} - readonly={readonly} - onDeleted={(postId) => removeItem(postId)} - /> - ))} - /> - )} - - {!isLoading && contents.length === 0 && ( - <EmptyFeed - targetType={'global'} - goToExplore={goToExplore} - canPost={showPostCreator} - feedType={feedType} - /> - )} - - {isLoading && loadMoreHasBeenCalled ? renderLoadingSkeleton() : null} - </> - ) : ( - <PrivateFeed /> - )} - </FeedScrollContainer> + <> + {showPostCreator ? ( + <PostCreator + data-qa-anchor="feed-post-creator-textarea" + targetType={'user'} + targetId={currentUserId || undefined} + enablePostTargetPicker={false} + onCreateSuccess={(newPost) => prependItem(newPost)} + /> + ) : null} + <FeedScrollContainer + className={className} + dataLength={contents.length} + next={loadMore} + hasMore={hasMore} + loader={null} + > + {!isHiddenProfile ? ( + <> + {isLoading && !loadMoreHasBeenCalled ? renderLoadingSkeleton() : null} + + {(!isLoading || loadMoreHasBeenCalled) && contents.length > 0 && ( + <LoadMoreWrapper + hasMore={hasMore} + loadMore={loadMore} + className="load-more no-border" + contentSlot={contents.map((content) => ( + <Post + key={content.postId} + postId={content.postId} + hidePostTarget={false} + readonly={readonly} + onDeleted={(postId) => removeItem(postId)} + /> + ))} + /> + )} + + {!isLoading && contents.length === 0 && ( + <EmptyFeed + targetType={'global'} + goToExplore={goToExplore} + canPost={showPostCreator} + feedType={feedType} + /> + )} + + {isLoading && loadMoreHasBeenCalled ? renderLoadingSkeleton() : null} + </> + ) : ( + <PrivateFeed /> + )} + </FeedScrollContainer> + </> ); }; @@ -147,63 +149,64 @@ const MyFeed = ({ } return ( - <FeedScrollContainer - className={className} - dataLength={posts.length} - next={loadMore} - hasMore={hasMore} - loader={null} - > - {!isHiddenProfile ? ( - <> - <PostCreator - data-qa-anchor="feed-post-creator-textarea" - targetType={'user'} - targetId={targetId || undefined} - communities={communities} - enablePostTargetPicker={false} - hasMoreCommunities={hasMoreCommunities} - loadMoreCommunities={loadMoreCommunitiesCB} - onCreateSuccess={onPostCreated} - /> - - {isLoading && !loadMoreHasBeenCalled ? renderLoadingSkeleton() : null} - - {(!isLoading || loadMoreHasBeenCalled) && posts.length > 0 && ( - <LoadMoreWrapper - hasMore={hasMore} - loadMore={loadMore} - className="load-more no-border" - contentSlot={ - <> - {posts.map((post) => ( - <Post - key={post.postId} - postId={post.postId} - hidePostTarget={true} - readonly={readonly} - /> - ))} - </> - } - /> - )} - - {!isLoading && posts.length === 0 && ( - <EmptyFeed - targetType={targetType} - goToExplore={goToExplore} - canPost={true} - feedType={feedType} - /> - )} - - {isLoading && loadMoreHasBeenCalled ? renderLoadingSkeleton() : null} - </> - ) : ( - <PrivateFeed /> - )} - </FeedScrollContainer> + <> + <PostCreator + data-qa-anchor="feed-post-creator-textarea" + targetType={'user'} + targetId={targetId || undefined} + communities={communities} + enablePostTargetPicker={false} + hasMoreCommunities={hasMoreCommunities} + loadMoreCommunities={loadMoreCommunitiesCB} + onCreateSuccess={onPostCreated} + /> + <FeedScrollContainer + className={className} + dataLength={posts.length} + next={loadMore} + hasMore={hasMore} + loader={null} + > + {!isHiddenProfile ? ( + <> + {isLoading && !loadMoreHasBeenCalled ? renderLoadingSkeleton() : null} + + {(!isLoading || loadMoreHasBeenCalled) && posts.length > 0 && ( + <LoadMoreWrapper + hasMore={hasMore} + loadMore={loadMore} + className="load-more no-border" + contentSlot={ + <> + {posts.map((post) => ( + <Post + key={post.postId} + postId={post.postId} + hidePostTarget={true} + readonly={readonly} + /> + ))} + </> + } + /> + )} + + {!isLoading && posts.length === 0 && ( + <EmptyFeed + targetType={targetType} + goToExplore={goToExplore} + canPost={true} + feedType={feedType} + /> + )} + + {isLoading && loadMoreHasBeenCalled ? renderLoadingSkeleton() : null} + </> + ) : ( + <PrivateFeed /> + )} + </FeedScrollContainer> + </> ); }; @@ -246,61 +249,62 @@ const CommunityFeed = ({ } return ( - <FeedScrollContainer - className={className} - dataLength={posts.length} - next={loadMore} - hasMore={hasMore} - loader={null} - > - {!isHiddenProfile ? ( - <> - {showPostCreator ? ( - <PostCreator - data-qa-anchor="feed-post-creator-textarea" - targetType={targetType} - targetId={targetId} - enablePostTargetPicker={false} - onCreateSuccess={onPostCreated} - /> - ) : null} - - {isLoading && !loadMoreHasBeenCalled ? renderLoadingSkeleton() : null} - - {!isLoading && posts.length > 0 && ( - <LoadMoreWrapper - hasMore={hasMore} - loadMore={loadMore} - className="load-more no-border" - contentSlot={ - <> - {posts.map((post) => ( - <Post - key={post.postId} - postId={post.postId} - hidePostTarget - readonly={readonly} - /> - ))} - </> - } - /> - )} - - {!isLoading && posts.length === 0 && ( - <EmptyFeed - targetType={targetType} - goToExplore={goToExplore} - canPost={showPostCreator} - /> - )} - - {isLoading && loadMoreHasBeenCalled ? renderLoadingSkeleton() : null} - </> - ) : ( - <PrivateFeed /> - )} - </FeedScrollContainer> + <> + {showPostCreator ? ( + <PostCreator + data-qa-anchor="feed-post-creator-textarea" + targetType={targetType} + targetId={targetId} + enablePostTargetPicker={false} + onCreateSuccess={onPostCreated} + /> + ) : null} + <FeedScrollContainer + className={className} + dataLength={posts.length} + next={loadMore} + hasMore={hasMore} + loader={null} + > + {!isHiddenProfile ? ( + <> + {isLoading && !loadMoreHasBeenCalled ? renderLoadingSkeleton() : null} + + {!isLoading && posts.length > 0 && ( + <LoadMoreWrapper + hasMore={hasMore} + loadMore={loadMore} + className="load-more no-border" + contentSlot={ + <> + {posts.map((post) => ( + <Post + key={post.postId} + postId={post.postId} + hidePostTarget + readonly={readonly} + /> + ))} + </> + } + /> + )} + + {!isLoading && posts.length === 0 && ( + <EmptyFeed + targetType={targetType} + goToExplore={goToExplore} + canPost={showPostCreator} + /> + )} + + {isLoading && loadMoreHasBeenCalled ? renderLoadingSkeleton() : null} + </> + ) : ( + <PrivateFeed /> + )} + </FeedScrollContainer> + </> ); }; @@ -345,65 +349,66 @@ const BaseFeed = ({ } return ( - <FeedScrollContainer - className={className} - dataLength={posts.length} - next={loadMore} - hasMore={hasMore} - loader={null} - > - {!isHiddenProfile ? ( - <> - {showPostCreator && ( - <PostCreator - data-qa-anchor="feed-post-creator-textarea" - targetType={targetType} - targetId={targetId} - communities={communities} - enablePostTargetPicker={false} - hasMoreCommunities={hasMoreCommunities} - loadMoreCommunities={loadMoreCommunities} - onCreateSuccess={onPostCreated} - /> - )} - - {isLoading && !loadMoreHasBeenCalled ? renderLoadingSkeleton() : null} - - {!isLoading && posts.length > 0 && ( - <LoadMoreWrapper - hasMore={hasMore} - loadMore={loadMore} - className="load-more no-border" - contentSlot={ - <> - {posts.map((post) => ( - <Post - key={post.postId} - postId={post.postId} - hidePostTarget - readonly={readonly} - /> - ))} - </> - } - /> - )} - - {!isLoading && posts.length === 0 && ( - <EmptyFeed - targetType={targetType} - goToExplore={goToExplore} - canPost={showPostCreator} - feedType={feedType} - /> - )} - - {isLoading && loadMoreHasBeenCalled ? renderLoadingSkeleton() : null} - </> - ) : ( - <PrivateFeed /> + <> + {showPostCreator && ( + <PostCreator + data-qa-anchor="feed-post-creator-textarea" + targetType={targetType} + targetId={targetId} + communities={communities} + enablePostTargetPicker={false} + hasMoreCommunities={hasMoreCommunities} + loadMoreCommunities={loadMoreCommunities} + onCreateSuccess={onPostCreated} + /> )} - </FeedScrollContainer> + <FeedScrollContainer + className={className} + dataLength={posts.length} + next={loadMore} + hasMore={hasMore} + loader={null} + > + {!isHiddenProfile ? ( + <> + {isLoading && !loadMoreHasBeenCalled ? renderLoadingSkeleton() : null} + + {!isLoading && posts.length > 0 && ( + <LoadMoreWrapper + hasMore={hasMore} + loadMore={loadMore} + className="load-more no-border" + contentSlot={ + <> + {posts.map((post) => ( + <Post + key={post.postId} + postId={post.postId} + hidePostTarget + readonly={readonly} + /> + ))} + </> + } + /> + )} + + {!isLoading && posts.length === 0 && ( + <EmptyFeed + targetType={targetType} + goToExplore={goToExplore} + canPost={showPostCreator} + feedType={feedType} + /> + )} + + {isLoading && loadMoreHasBeenCalled ? renderLoadingSkeleton() : null} + </> + ) : ( + <PrivateFeed /> + )} + </FeedScrollContainer> + </> ); }; From 09c00fadbcf0168899af87a7f3c7e4681e1870a0 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Thu, 20 Jun 2024 18:22:57 +0700 Subject: [PATCH 147/300] fix: append a created poll post (#432) --- src/social/components/post/Creator/index.tsx | 76 +++++++++++--------- 1 file changed, 41 insertions(+), 35 deletions(-) diff --git a/src/social/components/post/Creator/index.tsx b/src/social/components/post/Creator/index.tsx index 01958cb2d..e9b43aa8f 100644 --- a/src/social/components/post/Creator/index.tsx +++ b/src/social/components/post/Creator/index.tsx @@ -168,43 +168,10 @@ const PostCreatorBar = ({ }); }; - async function onCreatePost() { + async function createPost(createPostParams: Parameters<typeof PostRepository.createPost>[0]) { if (!target.targetId) return; try { setIsCreating(true); - const data: { text?: string } = {}; - const attachments = []; - - if (text) { - data.text = text; - } - - if (postImages.length) { - attachments.push(...postImages.map((i) => ({ fileId: i.fileId, type: FileType.IMAGE }))); - } - - if (postVideos.length) { - attachments.push(...postVideos.map((i) => ({ fileId: i.fileId, type: FileType.VIDEO }))); - } - - if (postFiles.length) { - attachments.push(...postFiles.map((i) => ({ fileId: i.fileId, type: FileType.FILE }))); - } - - if (data.text?.length && data.text.length > MAXIMUM_POST_CHARACTERS) { - overCharacterModal(); - return; - } - - const createPostParams: Parameters<typeof PostRepository.createPost>[0] = { - targetId: target.targetId, - targetType: target.targetType, - data, - dataType: 'text', - attachments, - metadata, - mentionees, - }; const postData = await PostRepository.createPost(createPostParams); @@ -244,6 +211,45 @@ const PostCreatorBar = ({ } } + async function onCreatePost() { + if (!target.targetId) return; + const data: { text?: string } = {}; + const attachments = []; + + if (text) { + data.text = text; + } + + if (postImages.length) { + attachments.push(...postImages.map((i) => ({ fileId: i.fileId, type: FileType.IMAGE }))); + } + + if (postVideos.length) { + attachments.push(...postVideos.map((i) => ({ fileId: i.fileId, type: FileType.VIDEO }))); + } + + if (postFiles.length) { + attachments.push(...postFiles.map((i) => ({ fileId: i.fileId, type: FileType.FILE }))); + } + + if (data.text?.length && data.text.length > MAXIMUM_POST_CHARACTERS) { + overCharacterModal(); + return; + } + + const createPostParams: Parameters<typeof PostRepository.createPost>[0] = { + targetId: target.targetId, + targetType: target.targetType, + data, + dataType: 'text', + attachments, + metadata, + mentionees, + }; + + return createPost(createPostParams); + } + const onMaxFilesLimit = () => { notification.info({ content: <FormattedMessage id="upload.attachmentLimit" values={{ maxFiles }} />, @@ -300,7 +306,7 @@ const PostCreatorBar = ({ targetType={creatorTargetType} onCreatePoll={async (pollId, text, pollMentionees, metadata) => { if (!creatorTargetId) return; - await PostRepository.createPost({ + createPost({ targetId: creatorTargetId, targetType: creatorTargetType, data: { pollId, text }, From fdfd85a146e7bb7bbcc56d8d1b745df1bad8ab98 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Thu, 20 Jun 2024 21:16:57 +0700 Subject: [PATCH 148/300] fix: ASC-22315 - error noti when user upload unsupported file (#417) * fix: story tab background color * fix: update navigate * fix: styled to css module * fix: remove console.log * fix: remove console.log * fix: story tab * fix: type * fix: remove console.log * fix: story tab type * fix: noti css * fix: story avatar * fix: remove unused * fix: remove unused * fix: share story button use amityElement * fix: icon * fix: add default communityProfileImageBackground * fix: story tab community * fix: story tab * fix: navigate * fix: story navigate * fix: remove unused * fix: red border story tab * fix: import and type * fix: import and type * fix: remove error icon z-index * fix: official badge condition * fix: remove unused * fix: height * fix: story avatar * fix: share story button --- src/core/components/Notification/index.tsx | 2 +- .../CommunityInfo/UICommunityInfo.tsx | 4 +- src/social/pages/Application/index.tsx | 3 +- src/social/pages/DraftPage.tsx | 5 +- src/social/pages/ViewStoryPage.tsx | 7 +- src/social/providers/NavigationProvider.tsx | 7 +- .../Notification/Notification.module.css | 2 +- src/v4/core/hooks/collections/useCommunity.ts | 1 - src/v4/core/providers/NavigationProvider.tsx | 16 +-- .../core/providers/PageBehaviorProvider.tsx | 5 +- .../social/components/StoryTab/StoryTab.tsx | 3 +- .../StoryTab/StoryTabCommunity.module.css | 23 +++- .../components/StoryTab/StoryTabCommunity.tsx | 99 ++++++++++----- .../StoryTab/StoryTabGlobalFeed.module.css | 3 +- .../StoryTab/StoryTabGlobalFeed.tsx | 1 + .../StoryTab/StoryTabItem.module.css | 9 +- .../components/StoryTab/StoryTabItem.tsx | 76 +++++++++-- .../elements/CloseButton/CloseButton.tsx | 42 +++--- .../elements/ExploreButton/ExploreButton.tsx | 1 - .../OverflowMenuButton.module.css | 3 + .../OverflowMenuButton/OverflowMenuButton.tsx | 70 +++++----- .../elements/OverflowMenuButton/styles.tsx | 30 ----- .../ShareStoryButton.module.css | 19 +-- .../ShareStoryButton/ShareStoryButton.tsx | 55 +++++--- .../social/elements/StoryRing/StoryRing.tsx | 9 +- .../StoryViewer/Renderers/Image.tsx | 1 - .../Renderers/Renderers.module.css | 1 - .../Renderers/Wrappers/Header/index.tsx | 6 +- src/v4/social/pages/Application/index.tsx | 4 +- .../pages/DraftsPage/DraftsPage.module.css | 6 +- src/v4/social/pages/DraftsPage/DraftsPage.tsx | 120 +++++++++--------- .../pages/StoryPage/GlobalFeedStory.tsx | 3 +- .../pages/StoryPage/ViewGlobalFeedStory.tsx | 9 +- .../social/pages/StoryPage/ViewStoryPage.tsx | 4 +- 34 files changed, 374 insertions(+), 275 deletions(-) create mode 100644 src/v4/social/elements/OverflowMenuButton/OverflowMenuButton.module.css delete mode 100644 src/v4/social/elements/OverflowMenuButton/styles.tsx diff --git a/src/core/components/Notification/index.tsx b/src/core/components/Notification/index.tsx index 7e8e157bd..0a84c14fa 100644 --- a/src/core/components/Notification/index.tsx +++ b/src/core/components/Notification/index.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode, useEffect, useState } from 'react'; +import React, { ReactNode } from 'react'; import { useNotificationData } from '~/core/providers/NotificationProvider'; import { NotificationContainer, Notifications } from './styles'; diff --git a/src/social/components/CommunityInfo/UICommunityInfo.tsx b/src/social/components/CommunityInfo/UICommunityInfo.tsx index 126ae36d6..6c02b40ae 100644 --- a/src/social/components/CommunityInfo/UICommunityInfo.tsx +++ b/src/social/components/CommunityInfo/UICommunityInfo.tsx @@ -142,9 +142,7 @@ const UICommunityInfo = ({ </JoinButton> )} - {isJoined && !canEditCommunity && ( - <StoryTab type="communityFeed" communityId={communityId} /> - )} + {isJoined && <StoryTab type="communityFeed" communityId={communityId} />} {isJoined && canEditCommunity && ( <Button diff --git a/src/social/pages/Application/index.tsx b/src/social/pages/Application/index.tsx index b5f93a5e6..699e7b8f8 100644 --- a/src/social/pages/Application/index.tsx +++ b/src/social/pages/Application/index.tsx @@ -46,7 +46,7 @@ const Wrapper = styled.div` `; const Community = () => { - const { page, onBack } = useNavigation(); + const { page } = useNavigation(); const { client } = useSDK(); const [socialSettings, setSocialSettings] = useState<Amity.SocialSettings | null>(null); @@ -100,6 +100,7 @@ const Community = () => { mediaType={page.mediaType} targetId={page.targetId} targetType={page.targetType} + storyType={page.storyType} /> )} diff --git a/src/social/pages/DraftPage.tsx b/src/social/pages/DraftPage.tsx index 58cf3983b..1c3fd5395 100644 --- a/src/social/pages/DraftPage.tsx +++ b/src/social/pages/DraftPage.tsx @@ -3,8 +3,8 @@ import { AmityDraftStoryPageProps, PlainDraftStoryPage, } from '~/v4/social/pages/DraftsPage/DraftsPage'; -import { PageTypes } from '../constants'; -import { useNavigation } from '../providers/NavigationProvider'; +import { PageTypes } from '~/social/constants'; +import { useNavigation } from '~/social/providers/NavigationProvider'; export const AmityDraftStoryPage = (props: AmityDraftStoryPageProps) => { const { onChangePage, onClickCommunity } = useNavigation(); @@ -15,6 +15,7 @@ export const AmityDraftStoryPage = (props: AmityDraftStoryPageProps) => { onDiscardCreateStory={() => onChangePage(PageTypes.NewsFeed)} goToCommunityPage={(communityId) => onClickCommunity(communityId)} goToGlobalFeedPage={() => onChangePage(PageTypes.NewsFeed)} + storyType={props.storyType} /> ); }; diff --git a/src/social/pages/ViewStoryPage.tsx b/src/social/pages/ViewStoryPage.tsx index 3999025d1..baa420fba 100644 --- a/src/social/pages/ViewStoryPage.tsx +++ b/src/social/pages/ViewStoryPage.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { CommunityFeedStory } from '~/v4/social/pages/StoryPage/CommunityFeedStory'; import { useNavigation } from '~/social/providers/NavigationProvider'; import { ViewGlobalFeedStoryPage } from '~/v4/social/pages/StoryPage/ViewGlobalFeedStory'; -import { PageTypes } from '../constants'; +import { PageTypes } from '~/social/constants'; type ViewStoryPageType = 'communityFeed' | 'globalFeed'; @@ -31,13 +31,12 @@ const ViewStoryPage: React.FC<AmityViewStoryPageProps> = ({ type, targetId }) => targetId={targetId} onChangePage={() => onChangePage(PageTypes.NewsFeed)} onClose={() => { - console.log('hello'); onChangePage(PageTypes.NewsFeed); }} onSwipeDown={() => onChangePage(PageTypes.NewsFeed)} onClickStory={(targetId) => onClickStory(targetId, 'globalFeed')} - goToDraftStoryPage={({ targetId, targetType, mediaType }) => - goToDraftStoryPage(targetId, targetType, mediaType) + goToDraftStoryPage={({ targetId, targetType, mediaType, storyType }) => + goToDraftStoryPage(targetId, targetType, mediaType, storyType) } onClickCommunity={(targetId) => onClickCommunity(targetId)} /> diff --git a/src/social/providers/NavigationProvider.tsx b/src/social/providers/NavigationProvider.tsx index 5cd327eb6..056501c3d 100644 --- a/src/social/providers/NavigationProvider.tsx +++ b/src/social/providers/NavigationProvider.tsx @@ -44,6 +44,7 @@ type Page = mediaType: AmityStoryMediaType; targetId: string; targetType: Amity.StoryTargetType; + storyType: 'communityFeed' | 'globalFeed'; }; type ContextValue = { @@ -66,6 +67,7 @@ type ContextValue = { targetId: string, targetType: string, mediaType: AmityStoryMediaType, + storyType: 'communityFeed' | 'globalFeed', ) => void; setNavigationBlocker?: ( params: @@ -123,7 +125,7 @@ if (process.env.NODE_ENV !== 'production') { onEditUser: (userId) => console.log(`NavigationContext onEditUser(${userId})`), onMessageUser: (userId) => console.log(`NavigationContext onMessageUser(${userId})`), onBack: () => console.log('NavigationContext onBack()'), - goToDraftStoryPage: (targetId, targetType, mediaType) => + goToDraftStoryPage: (targetId, targetType, mediaType, storyType) => console.log(`NavigationContext goToDraftStoryPage(${targetId})`), }; } @@ -370,12 +372,13 @@ export default function NavigationProvider({ ); const goToDraftStoryPage = useCallback( - (targetId, targetType, mediaType) => { + (targetId, targetType, mediaType, storyType) => { const next = { type: PageTypes.DraftPage, targetId, targetType, mediaType, + storyType, }; if (onChangePage) return onChangePage(next); diff --git a/src/v4/core/components/Notification/Notification.module.css b/src/v4/core/components/Notification/Notification.module.css index fb9cde5ab..0acb559a7 100644 --- a/src/v4/core/components/Notification/Notification.module.css +++ b/src/v4/core/components/Notification/Notification.module.css @@ -25,7 +25,7 @@ } .notificationContainer { - width: 100%; + width: 21.438rem; display: flex; justify-content: flex-start; align-items: center; diff --git a/src/v4/core/hooks/collections/useCommunity.ts b/src/v4/core/hooks/collections/useCommunity.ts index afdc7803a..7810c2c79 100644 --- a/src/v4/core/hooks/collections/useCommunity.ts +++ b/src/v4/core/hooks/collections/useCommunity.ts @@ -1,5 +1,4 @@ import { CommunityRepository } from '@amityco/ts-sdk'; - import useLiveObject from '~/v4/core/hooks/useLiveObject'; const useCommunity = ({ diff --git a/src/v4/core/providers/NavigationProvider.tsx b/src/v4/core/providers/NavigationProvider.tsx index d4d6c1151..883326abe 100644 --- a/src/v4/core/providers/NavigationProvider.tsx +++ b/src/v4/core/providers/NavigationProvider.tsx @@ -100,10 +100,11 @@ type ContextValue = { targetId: string; targetType: string; mediaType: AmityStoryMediaType; + storyType: 'communityFeed' | 'globalFeed'; }) => void; goToViewStoryPage: (context: { targetId: string; - targetType: string; + targetType: Amity.StoryTargetType; storyType: 'communityFeed' | 'globalFeed'; }) => void; setNavigationBlocker?: ( @@ -132,7 +133,7 @@ let defaultValue: ContextValue = { goToPostDetailPage: (postId: string) => {}, goToViewStoryPage: (context: { targetId: string; - targetType: string; + targetType: Amity.StoryTargetType; storyType: 'communityFeed' | 'globalFeed'; }) => {}, goToDraftStoryPage: (context: { @@ -156,8 +157,8 @@ if (process.env.NODE_ENV !== 'production') { onClickCommunity: (communityId) => console.log(`NavigationContext onClickCommunity(${communityId})`), onClickUser: (userId) => console.log(`NavigationContext onClickUser(${userId})`), - goToViewStoryPage: ({ targetId, storyType }) => - console.log(`NavigationContext goToViewStoryPage(${targetId}, ${storyType})`), + goToViewStoryPage: ({ targetId, storyType, targetType }) => + console.log(`NavigationContext goToViewStoryPage(${targetId}, ${storyType}, ${targetType})`), onCommunityCreated: (communityId) => console.log(`NavigationContext onCommunityCreated(${communityId})`), onEditCommunity: (communityId) => @@ -198,7 +199,7 @@ interface NavigationProviderProps { goToViewStoryPage?: (context: { storyId: string; storyType: 'communityFeed' | 'globalFeed'; - targetId?: string[]; + targetType: Amity.StoryTargetType; }) => void; goToDraftStoryPage?: (context: { targetId: string; @@ -375,14 +376,13 @@ export default function NavigationProvider({ }, [onChangePage, onBack, popPage]); const goToViewStoryPage = useCallback( - ({ targetId, storyType, targetIds }) => { + ({ targetId, storyType, targetType }) => { const next = { type: PageTypes.ViewStoryPage, context: { targetId, - targetType: 'community', storyType, - targetIds, + targetType, }, }; diff --git a/src/v4/core/providers/PageBehaviorProvider.tsx b/src/v4/core/providers/PageBehaviorProvider.tsx index f5782587c..68e4e96bd 100644 --- a/src/v4/core/providers/PageBehaviorProvider.tsx +++ b/src/v4/core/providers/PageBehaviorProvider.tsx @@ -15,7 +15,7 @@ export interface PageBehavior { goToPostDetailPage: (context: { postId: string }) => void; goToViewStoryPage: (context: { targetId: string; - targetType: string; + targetType: Amity.StoryTargetType; storyType: 'communityFeed' | 'globalFeed'; targetIds?: string[]; }) => void; @@ -87,9 +87,8 @@ export const PageBehaviorProvider: React.FC<PageBehaviorProviderProps> = ({ }, goToViewStoryPage: (context: { targetId: string; - targetType: string; + targetType: Amity.StoryTargetType; storyType: 'communityFeed' | 'globalFeed'; - targetIds?: string[]; }) => { if (pageBehavior?.AmityGlobalFeedComponentBehavior?.goToViewStoryPage) { return pageBehavior?.AmityGlobalFeedComponentBehavior.goToViewStoryPage(context); diff --git a/src/v4/social/components/StoryTab/StoryTab.tsx b/src/v4/social/components/StoryTab/StoryTab.tsx index 64c1edcdf..74bf74384 100644 --- a/src/v4/social/components/StoryTab/StoryTab.tsx +++ b/src/v4/social/components/StoryTab/StoryTab.tsx @@ -41,6 +41,7 @@ export const StoryTab = <T extends StoryTabType>({ mediaType: file.type.includes('image') ? { type: 'image', url: URL.createObjectURL(file) } : { type: 'video', url: URL.createObjectURL(file) }, + storyType: 'communityFeed', }); } }} @@ -61,7 +62,7 @@ export const StoryTab = <T extends StoryTabType>({ goToViewStoryPage={({ storyTarget, storyTargets }) => { AmityGlobalFeedComponentBehavior.goToViewStoryPage({ targetId: storyTarget.targetId, - targetType: storyTarget.targetType, + targetType: storyTarget.targetType as Amity.StoryTargetType, storyType: 'globalFeed', targetIds: storyTargets.map((s) => s.targetId), }); diff --git a/src/v4/social/components/StoryTab/StoryTabCommunity.module.css b/src/v4/social/components/StoryTab/StoryTabCommunity.module.css index e2eb42a76..97e990987 100644 --- a/src/v4/social/components/StoryTab/StoryTabCommunity.module.css +++ b/src/v4/social/components/StoryTab/StoryTabCommunity.module.css @@ -1,4 +1,4 @@ -.errorButton { +.errorIcon { position: absolute; bottom: 0; right: 0; @@ -26,6 +26,7 @@ } .storyTabContainer { + background-color: var(--asc-color-base-background); position: relative; width: 3rem; display: flex; @@ -36,14 +37,22 @@ align-items: center; } -.storyAvatar { +.storyAvatarContainer { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); width: 2.5rem; height: 2.5rem; - position: absolute; - top: 0.25rem; - left: 0.25rem; - z-index: 1; - cursor: pointer; + border-radius: 50%; + overflow: hidden; +} + +.storyAvatar { + width: 100% !important; + height: 100% !important; + object-fit: cover; + border-radius: 50%; } .storyTitle { diff --git a/src/v4/social/components/StoryTab/StoryTabCommunity.tsx b/src/v4/social/components/StoryTab/StoryTabCommunity.tsx index f209f5624..1f9a782a8 100644 --- a/src/v4/social/components/StoryTab/StoryTabCommunity.tsx +++ b/src/v4/social/components/StoryTab/StoryTabCommunity.tsx @@ -1,26 +1,58 @@ import React, { useRef } from 'react'; import Truncate from 'react-truncate-markup'; -import { backgroundImage as CommunityImage } from '~/icons/Community'; import useStories from '~/social/hooks/useStories'; import useSDK from '~/core/hooks/useSDK'; -import { checkStoryPermission } from '~/utils'; -import { useCommunityInfo } from '~/social/components/CommunityInfo/hooks'; -import { useNavigation } from '~/social/providers/NavigationProvider'; -import { - AddStoryButton, - ErrorButton, - HiddenInput, - StoryAvatar, - StoryTabContainer, - StoryTitle, - StoryWrapper, -} from './styles'; -import { isAdmin, isModerator } from '~/helpers/permissions'; import { FormattedMessage } from 'react-intl'; -import useUser from '~/core/hooks/useUser'; import { StoryRing } from '~/v4/social/elements/StoryRing/StoryRing'; +import clsx from 'clsx'; + +import styles from './StoryTabCommunity.module.css'; +import useUser from '~/v4/core/hooks/objects/useUser'; +import { useCommunityInfo } from '~/social/components/CommunityInfo/hooks'; +import { isAdmin, isModerator } from '~/v4/utils/permissions'; +import { checkStoryPermission } from '~/v4/social/utils'; +import { Avatar } from '~/v4/core/components'; +import { AVATAR_SIZE } from '~/v4/core/components/Avatar'; +import CommunityDefaultImg from '~/v4/icons/Community'; + +const AddIcon = (props: React.SVGProps<SVGSVGElement>) => { + return ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="16" + height="16" + fill="none" + viewBox="0 0 16 16" + {...props} + > + <circle cx="8" cy="8" r="7.25" fill="#1054DE" stroke="#fff" strokeWidth="1.5"></circle> + <path + fill="#fff" + d="M11.438 7.625c.156 0 .312.156.312.313v.625a.321.321 0 01-.313.312H8.626v2.813a.321.321 0 01-.313.312h-.624a.308.308 0 01-.313-.313V8.876H4.562a.309.309 0 01-.312-.313v-.624c0-.157.137-.313.313-.313h2.812V4.812c0-.156.137-.312.313-.312h.625c.156 0 .312.156.312.313v2.812h2.813z" + ></path> + </svg> + ); +}; +const ErrorIcon = (props: React.SVGProps<SVGSVGElement>) => { + return ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="16" + height="16" + fill="none" + viewBox="0 0 16 16" + {...props} + > + <circle cx="8" cy="8" r="7.25" fill="#FA4D30" stroke="#fff" strokeWidth="1.5"></circle> + <path + fill="#fff" + d="M9.25 10.75C9.25 11.453 8.687 12 8 12c-.703 0-1.25-.547-1.25-1.25 0-.688.547-1.25 1.25-1.25.688 0 1.25.563 1.25 1.25zM6.89 4.406A.378.378 0 017.267 4h1.453c.219 0 .39.188.375.406l-.203 4.25A.387.387 0 018.516 9H7.469a.387.387 0 01-.375-.344l-.203-4.25z" + ></path> + </svg> + ); +}; interface StoryTabCommunityFeedProps { pageId: string; @@ -64,7 +96,7 @@ export const StoryTabCommunityFeed: React.FC<StoryTabCommunityFeedProps> = ({ }; const { currentUserId, client } = useSDK(); - const user = useUser(currentUserId); + const { user } = useUser(currentUserId); const isGlobalAdmin = isAdmin(user?.roles); const isCommunityModerator = isModerator(user?.roles); const hasStoryPermission = @@ -76,8 +108,8 @@ export const StoryTabCommunityFeed: React.FC<StoryTabCommunityFeedProps> = ({ const isErrored = stories.some((story) => story?.syncState === 'error'); return ( - <StoryTabContainer> - <StoryWrapper> + <div className={clsx(styles.storyTabContainer)}> + <div className={clsx(styles.storyWrapper)}> {hasStoryRing && ( <StoryRing pageId={pageId} @@ -88,15 +120,22 @@ export const StoryTabCommunityFeed: React.FC<StoryTabCommunityFeedProps> = ({ size={48} /> )} - <StoryAvatar - onClick={handleOnClick} - avatar={avatarFileUrl} - backgroundImage={CommunityImage} - /> + + <div className={clsx(styles.storyAvatarContainer)}> + <Avatar + avatar={avatarFileUrl} + size={AVATAR_SIZE.SMALL} + className={clsx(styles.storyAvatar)} + onClick={handleOnClick} + defaultImage={<CommunityDefaultImg />} + /> + </div> + {hasStoryPermission && ( <> - <AddStoryButton onClick={handleAddIconClick} /> - <HiddenInput + <AddIcon className={styles.addStoryButton} onClick={handleAddIconClick} /> + <input + className={clsx(styles.hiddenInput)} ref={fileInputRef} type="file" accept="image/*,video/*" @@ -104,13 +143,13 @@ export const StoryTabCommunityFeed: React.FC<StoryTabCommunityFeedProps> = ({ /> </> )} - {isErrored && <ErrorButton />} - </StoryWrapper> + {isErrored && <ErrorIcon className={clsx(styles.errorIcon)} />} + </div> <Truncate lines={1}> - <StoryTitle> + <div className={clsx(styles.storyTitle)}> <FormattedMessage id="storyTab.title" /> - </StoryTitle> + </div> </Truncate> - </StoryTabContainer> + </div> ); }; diff --git a/src/v4/social/components/StoryTab/StoryTabGlobalFeed.module.css b/src/v4/social/components/StoryTab/StoryTabGlobalFeed.module.css index 2cbc469d1..e89f4f79f 100644 --- a/src/v4/social/components/StoryTab/StoryTabGlobalFeed.module.css +++ b/src/v4/social/components/StoryTab/StoryTabGlobalFeed.module.css @@ -2,8 +2,7 @@ display: flex; overflow: auto; padding: 0.625rem; - background-color: var(--asc-color-white); - border-bottom: 0.0625rem solid #e6e6e6; + background-color: var(--asc-color-base-background); gap: var(--asc-spacing-s1); } diff --git a/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx b/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx index d6b313c87..5d054da5d 100644 --- a/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx +++ b/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx @@ -80,6 +80,7 @@ export const StoryTabGlobalFeed = ({ key={story.targetId} targetId={story.targetId} hasUnseen={story.hasUnseen} + isErrored={story.failedStoriesCount > 0} onClick={() => goToViewStoryPage({ storyTargets: stories, diff --git a/src/v4/social/components/StoryTab/StoryTabItem.module.css b/src/v4/social/components/StoryTab/StoryTabItem.module.css index ad1e132a8..17435b850 100644 --- a/src/v4/social/components/StoryTab/StoryTabItem.module.css +++ b/src/v4/social/components/StoryTab/StoryTabItem.module.css @@ -16,7 +16,6 @@ position: absolute; bottom: 0; right: 0; - z-index: 2; fill: var(--asc-color-primary-default); } @@ -46,4 +45,12 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + color: var(--asc-color-secondary-default); +} + +.errorIcon { + position: absolute; + bottom: 0; + right: 0; + cursor: pointer; } diff --git a/src/v4/social/components/StoryTab/StoryTabItem.tsx b/src/v4/social/components/StoryTab/StoryTabItem.tsx index a0a2980fd..be173c87b 100644 --- a/src/v4/social/components/StoryTab/StoryTabItem.tsx +++ b/src/v4/social/components/StoryTab/StoryTabItem.tsx @@ -1,12 +1,57 @@ import React from 'react'; -import useImage from '~/core/hooks/useImage'; -import useCommunity from '~/social/hooks/useCommunity'; + import { StoryRing } from '~/v4/social/elements/StoryRing'; -import { PrivateIcon } from '~/social/components/community/Name/styles'; -import styles from './StoryTabItem.module.css'; import { Typography } from '~/v4/core/components'; import Verified from '~/v4/icons/Verified'; +import clsx from 'clsx'; +import useCommunity from '~/v4/core/hooks/collections/useCommunity'; +import useImage from '~/v4/core/hooks/useImage'; + +import styles from './StoryTabItem.module.css'; + +const ErrorIcon = (props: React.SVGProps<SVGSVGElement>) => { + return ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="16" + height="16" + fill="none" + viewBox="0 0 16 16" + {...props} + > + <circle cx="8" cy="8" r="7.25" fill="#FA4D30" stroke="#fff" strokeWidth="1.5"></circle> + <path + fill="#fff" + d="M9.25 10.75C9.25 11.453 8.687 12 8 12c-.703 0-1.25-.547-1.25-1.25 0-.688.547-1.25 1.25-1.25.688 0 1.25.563 1.25 1.25zM6.89 4.406A.378.378 0 017.267 4h1.453c.219 0 .39.188.375.406l-.203 4.25A.387.387 0 018.516 9H7.469a.387.387 0 01-.375-.344l-.203-4.25z" + ></path> + </svg> + ); +}; + +const LockIcon = (props: React.SVGProps<SVGSVGElement>) => { + return ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="16" + height="16" + fill="none" + viewBox="0 0 16 16" + {...props} + > + <path + fill="#000" + fillOpacity=".2" + d="M8 0c4.41 0 8 3.59 8 8s-3.59 8-8 8-8-3.59-8-8 3.59-8 8-8z" + ></path> + <path + fill="#000" + fillOpacity=".2" + d="M8 2c2.21 0 4 1.79 4 4v1h-1V6c0-1.104-.896-2-2-2s-2 .896-2 2v1H4V6c0-2.21 1.79-4 4-4z" + ></path> + </svg> + ); +}; interface StoryTabProps { pageId: string; @@ -15,6 +60,7 @@ interface StoryTabProps { hasUnseen: boolean; onClick: () => void; size: number; + isErrored?: boolean; } export const StoryTabItem: React.FC<StoryTabProps> = ({ @@ -23,24 +69,34 @@ export const StoryTabItem: React.FC<StoryTabProps> = ({ targetId, hasUnseen, onClick, + isErrored, }) => { - const community = useCommunity(targetId); + const { community } = useCommunity({ + communityId: targetId, + }); const communityAvatar = useImage({ fileId: community?.avatarFileId, imageSize: 'small' }); return ( - <div className={styles.container} onClick={onClick}> + <div className={clsx(styles.container)} onClick={onClick}> <div className={styles.avatarContainer}> - <StoryRing pageId={pageId} componentId={componentId} hasUnseen={hasUnseen} /> - {community?.isOfficial && <Verified className={styles.verifiedIcon} />} + <StoryRing + pageId={pageId} + componentId={componentId} + hasUnseen={hasUnseen} + isErrored={isErrored} + /> + <div className={styles.avatarBackground}> {communityAvatar && ( <img className={styles.avatar} src={communityAvatar} alt={community?.displayName} /> )} </div> + {isErrored && <ErrorIcon className={styles.errorIcon} />} + {community?.isOfficial && <Verified className={styles.verifiedIcon} />} </div> - <Typography.Caption className={styles.displayName}> - {!community?.isPublic && <PrivateIcon />} + <Typography.Caption className={clsx(styles.displayName)}> + {!community?.isPublic && <LockIcon />} {community?.displayName} </Typography.Caption> </div> diff --git a/src/v4/social/elements/CloseButton/CloseButton.tsx b/src/v4/social/elements/CloseButton/CloseButton.tsx index 6c69ce5f4..3aee16611 100644 --- a/src/v4/social/elements/CloseButton/CloseButton.tsx +++ b/src/v4/social/elements/CloseButton/CloseButton.tsx @@ -4,11 +4,30 @@ import { useAmityElement } from '~/v4/core/hooks/uikit'; import clsx from 'clsx'; import { IconComponent } from '~/v4/core/IconComponent'; +const CloseIconSVG = (props: React.SVGProps<SVGSVGElement>) => ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="100%" + height="100%" + viewBox="0 0 320 512" + fill="currentColor" + {...props} + > + <path + d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 + 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 + 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 + 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 + 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 + 0-22.58L207.6 256z" + /> + </svg> +); + interface CloseButtonProps { pageId?: string; componentId?: string; onClick?: (e: React.MouseEvent) => void; - style?: React.CSSProperties; 'data-qa-anchor'?: string; defaultClassName?: string; imgClassName?: string; @@ -18,30 +37,9 @@ export const CloseButton = ({ pageId = '*', componentId = '*', onClick = () => {}, - style, defaultClassName, imgClassName, }: CloseButtonProps) => { - const CloseIconSVG = (props: React.SVGProps<SVGSVGElement>) => ( - <svg - xmlns="http://www.w3.org/2000/svg" - width="100%" - height="100%" - viewBox="0 0 320 512" - fill="currentColor" - {...props} - > - <path - d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 - 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 - 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 - 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 - 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 - 0-22.58L207.6 256z" - /> - </svg> - ); - const elementId = 'close_button'; const { isExcluded, config, uiReference } = useAmityElement({ pageId, diff --git a/src/v4/social/elements/ExploreButton/ExploreButton.tsx b/src/v4/social/elements/ExploreButton/ExploreButton.tsx index 33b98e3c0..913c44c8e 100644 --- a/src/v4/social/elements/ExploreButton/ExploreButton.tsx +++ b/src/v4/social/elements/ExploreButton/ExploreButton.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { useAmityElement } from '~/v4/core/hooks/uikit'; -import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; import { TabButton } from '~/v4/social/internal-components/TabButton'; export interface ExploreButtonProps { diff --git a/src/v4/social/elements/OverflowMenuButton/OverflowMenuButton.module.css b/src/v4/social/elements/OverflowMenuButton/OverflowMenuButton.module.css new file mode 100644 index 000000000..ff6e0e1b2 --- /dev/null +++ b/src/v4/social/elements/OverflowMenuButton/OverflowMenuButton.module.css @@ -0,0 +1,3 @@ +.overflowMenuIcon { + cursor: pointer; +} diff --git a/src/v4/social/elements/OverflowMenuButton/OverflowMenuButton.tsx b/src/v4/social/elements/OverflowMenuButton/OverflowMenuButton.tsx index 096f97a3b..551aa8abe 100644 --- a/src/v4/social/elements/OverflowMenuButton/OverflowMenuButton.tsx +++ b/src/v4/social/elements/OverflowMenuButton/OverflowMenuButton.tsx @@ -1,47 +1,59 @@ +import clsx from 'clsx'; import React from 'react'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { IconComponent } from '~/v4/core/IconComponent'; +import styles from './OverflowMenuButton.module.css'; -import { UIOverflowButton, RemoteImageButton } from './styles'; -import { isValidHttpUrl } from '~/utils'; -import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; +const OverflowMenuSvg = (props: React.SVGProps<SVGSVGElement>) => { + return ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="24" + height="24" + fill="none" + viewBox="0 0 24 24" + {...props} + > + <path + fill="#fff" + d="M13.688 12.25c0 .95-.774 1.688-1.688 1.688-.95 0-1.688-.739-1.688-1.688 0-.914.739-1.688 1.688-1.688a1.71 1.71 0 011.688 1.688zm4.218-1.688c.914 0 1.688.774 1.688 1.688 0 .95-.774 1.688-1.688 1.688-.949 0-1.687-.739-1.687-1.688 0-.914.738-1.688 1.687-1.688zm-11.812 0c.914 0 1.687.774 1.687 1.688 0 .95-.773 1.688-1.687 1.688-.95 0-1.688-.739-1.688-1.688 0-.914.739-1.688 1.688-1.688z" + ></path> + </svg> + ); +}; interface OverflowMenuButtonProps { - pageId?: 'story_page'; - componentId?: '*'; + pageId?: string; + componentId?: string; onClick?: (e: React.MouseEvent) => void; - style?: React.CSSProperties; + defaultClassName?: string; + imgClassName?: string; 'data-qa-anchor'?: string; } export const OverflowMenuButton = ({ - pageId = 'story_page', + pageId = '*', componentId = '*', onClick = () => {}, - style, - ...props + defaultClassName, + imgClassName, }: OverflowMenuButtonProps) => { const elementId = 'overflow_menu'; - const { getConfig, isExcluded } = useCustomization(); - const elementConfig = getConfig(`${pageId}/${componentId}/${elementId}`); - const isElementExcluded = isExcluded(`${pageId}/${componentId}/${elementId}`); - - if (isElementExcluded) return null; - - const overflowMenuIcon = elementConfig?.overflow_menu_icon; - const isRemoteImage = overflowMenuIcon && isValidHttpUrl(overflowMenuIcon); + const { config, defaultConfig, uiReference } = useAmityElement({ + pageId, + componentId, + elementId, + }); - return isRemoteImage ? ( - <RemoteImageButton - data-qa-anchor="overflow_menu_button" - src={overflowMenuIcon} + return ( + <IconComponent onClick={onClick} - {...props} - /> - ) : ( - <UIOverflowButton - data-qa-anchor="overflow_menu_button" - name={'EllipsisH'} - onClick={onClick} - {...props} + defaultIcon={() => ( + <OverflowMenuSvg className={clsx(styles.overflowMenuIcon, defaultClassName)} /> + )} + imgIcon={() => <img src={config.icon} alt={uiReference} className={imgClassName} />} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} /> ); }; diff --git a/src/v4/social/elements/OverflowMenuButton/styles.tsx b/src/v4/social/elements/OverflowMenuButton/styles.tsx deleted file mode 100644 index b7217bcdc..000000000 --- a/src/v4/social/elements/OverflowMenuButton/styles.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import styled from 'styled-components'; -import { Icon } from '~/v4/core/components'; - -export const UIOverflowButton = styled(Icon)<{ backgroundColor?: string }>` - cursor: pointer; - fill: #ffffff; - background-color: ${({ backgroundColor }) => backgroundColor}; -`; - -export const RemoteImageButton = styled.img` - width: 1.5rem; - height: 1.5rem; - cursor: pointer; - background-color: transparent; - border: none; - outline: none; - padding: 0; - margin: 0; - display: flex; - align-items: center; - justify-content: center; - - &:hover { - opacity: 0.8; - } - - &:active { - opacity: 0.6; - } -`; diff --git a/src/v4/social/elements/ShareStoryButton/ShareStoryButton.module.css b/src/v4/social/elements/ShareStoryButton/ShareStoryButton.module.css index bbc519e58..19086651a 100644 --- a/src/v4/social/elements/ShareStoryButton/ShareStoryButton.module.css +++ b/src/v4/social/elements/ShareStoryButton/ShareStoryButton.module.css @@ -1,4 +1,4 @@ -.uiShareStoryButton { +.shareStoryButton { display: inline-flex; height: 2.5rem; padding: 0.375rem 0.5rem 0.375rem 0.25rem; @@ -12,23 +12,14 @@ color: var(--asc-color-black); } -.uiShareStoryButton > span { - font-weight: var(--asc-text-font-weight-bold); - font-family: var(--asc-text-global-font-family); - font-size: var(--asc-text-font-size-md); - line-height: var(--asc-line-height-md); +.shareStoryButton[data-hideAvatar='true'] { + display: none; } .shareStoryIcon { margin-left: var(--asc-spacing-s1); } -.remoteImageIcon { - width: 1.5rem; - height: 1.5rem; - background-color: transparent; - border: none; - outline: none; - padding: 0; - margin: 0; +.hideAvatar { + display: none; } diff --git a/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx b/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx index 4da490afc..bd247b814 100644 --- a/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx +++ b/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx @@ -1,14 +1,31 @@ import React from 'react'; +import clsx from 'clsx'; -import { useIntl } from 'react-intl'; -import { isValidHttpUrl } from '~/utils'; -import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; -import { Icon } from '~/v4/core/components/Icon'; +import { Avatar, Typography } from '~/v4/core/components'; +import { useAmityElement } from '~/v4/core/hooks/uikit/index'; import Community from '~/v4/icons/Community'; -import styles from './ShareStoryButton.module.css'; -import { Avatar } from '~/v4/core/components'; import { AVATAR_SIZE } from '~/v4/core/components/Avatar/Avatar'; +import styles from './ShareStoryButton.module.css'; + +const ArrowRightIcon = (props: React.SVGProps<SVGSVGElement>) => { + return ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="15" + height="14" + fill="none" + viewBox="0 0 15 14" + {...props} + > + <path + fill="currentColor" + d="M6.813.219c.125-.157.375-.157.53 0l6.532 6.531a.36.36 0 010 .531l-6.531 6.532c-.157.156-.407.156-.532 0l-.625-.594c-.156-.156-.156-.406 0-.531l4.844-4.876H.375A.361.361 0 010 7.438v-.875a.38.38 0 01.375-.375h10.656L6.187 1.345c-.156-.125-.156-.375 0-.532L6.813.22z" + ></path> + </svg> + ); +}; + interface ShareButtonProps { onClick: () => void; pageId?: string; @@ -25,24 +42,24 @@ export const ShareStoryButton = ({ avatar, }: ShareButtonProps) => { const elementId = 'share_story_button'; - const { formatMessage } = useIntl(); - const { getConfig, isExcluded } = useCustomization(); - const elementConfig = getConfig(`${pageId}/${componentId}/${elementId}`); - const isElementExcluded = isExcluded(`${pageId}/${componentId}/${elementId}`); - if (isElementExcluded) return null; + const { config, isExcluded } = useAmityElement({ + pageId, + componentId, + elementId, + }); - const shareIcon = elementConfig?.share_icon; - const isRemoteImage = shareIcon && isValidHttpUrl(shareIcon); + if (isExcluded) return null; return ( <button role="button" - className={styles.uiShareStoryButton} + className={clsx(styles.shareStoryButton)} data-qa-anchor="share_story_button" onClick={onClick} + data-hideAvatar={config?.hide_avatar} > - {!elementConfig?.hide_avatar && ( + {!config?.hide_avatar && ( <Avatar data-qa-anchor="share_story_button_image_view" size={AVATAR_SIZE.SMALL} @@ -50,12 +67,8 @@ export const ShareStoryButton = ({ defaultImage={<Community />} /> )} - <span>{formatMessage({ id: 'storyDraft.button.shareStory' })}</span> - {isRemoteImage ? ( - <img src={shareIcon} alt="Share Story Icon" className={styles.remoteImageIcon} /> - ) : ( - <Icon className={styles.shareStoryIcon} name={'ArrowRight2Icon'} /> - )} + <Typography.BodyBold>{config?.text || 'Share story'}</Typography.BodyBold> + <ArrowRightIcon /> </button> ); }; diff --git a/src/v4/social/elements/StoryRing/StoryRing.tsx b/src/v4/social/elements/StoryRing/StoryRing.tsx index 376c8a0f5..75b32d364 100644 --- a/src/v4/social/elements/StoryRing/StoryRing.tsx +++ b/src/v4/social/elements/StoryRing/StoryRing.tsx @@ -1,6 +1,7 @@ import React from 'react'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; + import styles from './StoryRing.module.css'; -import { useAmityElement } from '~/v4/core/hooks/uikit/index'; const EmptyStateRingSvg = ({ pageId, @@ -197,9 +198,9 @@ export const StoryRing = ({ {...props} > <circle - cx="24" - cy="24" - r="23" + cx={size / 2} + cy={size / 2} + r={size / 2 - 1} stroke={getComputedStyle(document.documentElement).getPropertyValue('--asc-color-alert')} strokeWidth="2" ></circle> diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index 4c4565b1d..ddb1fd7c1 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -144,7 +144,6 @@ export const renderer: CustomRenderer = ({ }; const handleOnClose = () => { - console.log('handleOnClose'); onClose(); }; diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css b/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css index 04d323080..5d10ae2dd 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css @@ -58,7 +58,6 @@ display: flex; justify-content: center; align-items: center; - color: #ccc; } .storyImage { diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/index.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/index.tsx index 0237b22cf..7ceef497d 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/index.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/index.tsx @@ -84,10 +84,8 @@ const Header: React.FC< onClick={onPause} /> )} - {isHaveActions && ( - <OverflowMenuButton pageId="story_page" componentId="*" onClick={onAction} /> - )} - <CloseButton pageId="story_page" componentId="*" onClick={onClose} /> + {isHaveActions && <OverflowMenuButton pageId="story_page" onClick={onAction} />} + <CloseButton pageId="story_page" onClick={onClose} /> </div> </div> ); diff --git a/src/v4/social/pages/Application/index.tsx b/src/v4/social/pages/Application/index.tsx index 7d1eca54f..7f7ff625a 100644 --- a/src/v4/social/pages/Application/index.tsx +++ b/src/v4/social/pages/Application/index.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import styles from './Application.module.css'; + import { SocialHomePage } from '~/v4/social/pages/SocialHomePage'; import { PostDetailPage } from '~/v4/social/pages/PostDetailPage'; import { PageTypes, useNavigation } from '~/v4/core/providers/NavigationProvider'; @@ -8,6 +8,8 @@ import { SocialGlobalSearchPage } from '~/v4/social/pages/SocialGlobalSearchPage import { ViewStoryPage } from '~/v4/social/pages/StoryPage'; import { SelectPostTargetPage } from '../SelectPostTargetPage'; +import styles from './Application.module.css'; + const Application = () => { const { page } = useNavigation(); diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.module.css b/src/v4/social/pages/DraftsPage/DraftsPage.module.css index 83edd6f7c..76fccc90d 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.module.css +++ b/src/v4/social/pages/DraftsPage/DraftsPage.module.css @@ -1,8 +1,8 @@ -.draftPage { +.draftPageContainer { display: flex; flex-direction: column; - width: 23.438rem; - height: 40.875rem; + width: 100%; + height: 100vh; position: relative; font-family: var(--asc-text-global-font-family); font-style: var(--asc-text-global-font-style); diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index 79579555f..210255e27 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -30,6 +30,7 @@ export type AmityDraftStoryPageProps = { targetId: string; targetType: Amity.StoryTargetType; mediaType?: AmityStoryMediaType; + storyType: 'communityFeed' | 'globalFeed'; }; export type HyperLinkFormInputs = { @@ -221,80 +222,77 @@ export const PlainDraftStoryPage = ({ }, [file, imageMode, mediaType]); return ( - <> - <div id="asc-uikit-create-story" className={styles.draftPage}> - <div className={styles.headerContainer}> - <div className={styles.header}> - <BackButton pageId={pageId} onClick={discardCreateStory} /> - <div className={styles.topRightButtons}> - {mediaType?.type === 'image' && ( - <AspectRatioButton pageId={pageId} onClick={onClickImageMode} /> - )} - <HyperLinkButton pageId={pageId} onClick={handleOnClickHyperLinkActionButton} /> - </div> + <div id="asc-uikit-create-story" className={styles.draftPageContainer}> + <div className={styles.headerContainer}> + <div className={styles.header}> + <BackButton pageId={pageId} onClick={discardCreateStory} /> + <div className={styles.topRightButtons}> + {mediaType?.type === 'image' && ( + <AspectRatioButton pageId={pageId} onClick={onClickImageMode} /> + )} + <HyperLinkButton pageId={pageId} onClick={handleOnClickHyperLinkActionButton} /> </div> </div> + </div> - {mediaType?.type === 'image' ? ( - <div - className={styles.mainContainer} - style={{ - background: `linear-gradient( + {mediaType?.type === 'image' ? ( + <div + className={styles.mainContainer} + style={{ + background: `linear-gradient( 180deg, ${colors?.length > 0 ? colors[0].hex : 'var(--asc-color-black)'} 0%, ${colors?.length > 0 ? colors[colors?.length - 1].hex : 'var(--asc-color-black)'} 100% )`, - }} - > - <img - className={styles.previewImage} - src={file ? URL.createObjectURL(file) : mediaType.url} - style={{ - width: '100%', - height: '100%', - objectFit: imageMode === 'fit' ? 'contain' : 'cover', - }} - alt="Draft" - /> - </div> - ) : mediaType?.type === 'video' ? ( - <VideoPreview - className={styles.videoPreview} + }} + > + <img + className={styles.previewImage} src={file ? URL.createObjectURL(file) : mediaType.url} - mediaFit="contain" - autoPlay - loop - controls={false} + style={{ + width: '100%', + height: '100%', + objectFit: imageMode === 'fit' ? 'contain' : 'cover', + }} + alt="Draft" /> - ) : null} - {hyperLink[0]?.data?.url && ( - <div className={styles.hyperLinkContainer}> - <HyperLink onClick={() => setIsHyperLinkBottomSheetOpen(true)}> - {hyperLink[0]?.data?.customText || hyperLink[0].data.url.replace(/^https?:\/\//, '')} - </HyperLink> - </div> - )} + </div> + ) : mediaType?.type === 'video' ? ( + <video + className={styles.videoPreview} + src={file ? URL.createObjectURL(file) : mediaType.url} + autoPlay + loop + controls={false} + /> + ) : null} + {hyperLink[0]?.data?.url && ( + <div className={styles.hyperLinkContainer}> + <HyperLink onClick={() => setIsHyperLinkBottomSheetOpen(true)}> + {hyperLink[0]?.data?.customText || hyperLink[0].data.url.replace(/^https?:\/\//, '')} + </HyperLink> + </div> + )} + + <HyperLinkConfig + pageId={pageId} + isOpen={isHyperLinkBottomSheetOpen} + onClose={handleHyperLinkBottomSheetClose} + onSubmit={onSubmitHyperLink} + onRemove={onRemoveHyperLink} + isHaveHyperLink={hyperLink?.[0]?.data?.url !== ''} + /> - <HyperLinkConfig + <div className={styles.footer}> + <ShareStoryButton pageId={pageId} - isOpen={isHyperLinkBottomSheetOpen} - onClose={handleHyperLinkBottomSheetClose} - onSubmit={onSubmitHyperLink} - onRemove={onRemoveHyperLink} - isHaveHyperLink={hyperLink?.[0]?.data?.url !== ''} + avatar={community.avatarFileUrl} + onClick={() => + onCreateStory(file, imageMode, {}, hyperLink[0]?.data?.url ? hyperLink : []) + } /> - - <div className={styles.footer}> - <ShareStoryButton - pageId={pageId} - avatar={community.avatarFileUrl} - onClick={() => - onCreateStory(file, imageMode, {}, hyperLink[0]?.data?.url ? hyperLink : []) - } - /> - </div> </div> - </> + </div> ); }; diff --git a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx index a7701904d..29d744b36 100644 --- a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx @@ -38,6 +38,7 @@ interface GlobalFeedStoryProps { mediaType: { type: 'image' | 'video'; url: string }; targetId: string; targetType: string; + storyType: 'globalFeed'; }) => void; onClose: (targetId: string) => void; onSwipeDown: (targetId: string) => void; @@ -71,7 +72,6 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ renderer({ ...props, onClose: () => { - console.log('onClose'); onClose(targetId); }, onSwipeDown: () => onSwipeDown(targetId), @@ -316,6 +316,7 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ mediaType: file.type.includes('image') ? { type: 'image', url: URL.createObjectURL(file) } : { type: 'video', url: URL.createObjectURL(file) }, + storyType: 'globalFeed', }); } diff --git a/src/v4/social/pages/StoryPage/ViewGlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/ViewGlobalFeedStory.tsx index a56380c38..0c72600fa 100644 --- a/src/v4/social/pages/StoryPage/ViewGlobalFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/ViewGlobalFeedStory.tsx @@ -1,6 +1,7 @@ import React from 'react'; -import { useGlobalStoryTargets } from '../../hooks/collections/useGlobalStoryTargets'; -import { GlobalFeedStory } from './GlobalFeedStory'; +import { useGlobalStoryTargets } from '~/v4/social/hooks/collections/useGlobalStoryTargets'; +import { GlobalFeedStory } from '~/v4/social/pages/StoryPage/GlobalFeedStory'; +import { AmityStoryMediaType } from '~/v4/social/pages/DraftsPage/DraftsPage'; export const ViewGlobalFeedStoryPage = ({ targetId, @@ -18,10 +19,12 @@ export const ViewGlobalFeedStoryPage = ({ targetId, targetType, mediaType, + storyType, }: { targetId: string; targetType: string; - mediaType: any; + mediaType: AmityStoryMediaType; + storyType: 'globalFeed'; }) => void; onClose: (targetId: string) => void; onSwipeDown: (targetId: string) => void; diff --git a/src/v4/social/pages/StoryPage/ViewStoryPage.tsx b/src/v4/social/pages/StoryPage/ViewStoryPage.tsx index 46f7c335f..c51bd4106 100644 --- a/src/v4/social/pages/StoryPage/ViewStoryPage.tsx +++ b/src/v4/social/pages/StoryPage/ViewStoryPage.tsx @@ -39,8 +39,8 @@ const ViewStoryPage: React.FC<AmityViewStoryPageProps> = ({ type, targetId }) => targetType: 'community', }) } - goToDraftStoryPage={({ targetId, targetType, mediaType }) => - goToDraftStoryPage({ targetId, targetType, mediaType }) + goToDraftStoryPage={({ targetId, targetType, mediaType, storyType }) => + goToDraftStoryPage({ targetId, targetType, mediaType, storyType }) } onClickCommunity={(targetId) => onClickCommunity(targetId)} /> From 175f3841cca8b1cb25b007e1e7364fff978640bd Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Thu, 20 Jun 2024 21:21:17 +0700 Subject: [PATCH 149/300] fix: ASC-20535 - deleted reply block (#418) * fix: story view page onClose * fix: to v4 * fix: overlay bug * fix: remove console.log * fix: remove styled component * fix: add story arrow left right to customization provider * fix: view story full width and height * fix: notification v4 * fix: delete first multiple stories go to next story * fix: story tab background color * fix: update navigate * fix: styled to css module * fix: remove console.log * fix: remove console.log * fix: story tab * fix: type * fix: remove console.log * fix: story tab type * fix: noti css * fix: story avatar * fix: remove unused * fix: remove unused * fix: share story button use amityElement * fix: icon * fix: add default communityProfileImageBackground * fix: story tab community * fix: story tab * fix: navigate * fix: story navigate * fix: remove unused * fix: red border story tab * fix: remove ts-sdk peer dep * fix: delete comment * fix: i18n * fix: add margin-bottom to container * fix: remove packagemanager * fix: story ring empty state color * fix: import and type * fix: import and type * fix: remove error icon z-index * fix: official badge condition * fix: remove i18n * fix: no comment word --- pnpm-lock.yaml | 332 +++++++++--------- src/v4/core/IconComponent.tsx | 1 - src/v4/core/components/Avatar/Avatar.tsx | 1 + .../LoadMoreWrapper.module.css | 1 + .../core/providers/CustomizationProvider.tsx | 4 + src/v4/social/constants/default-avatar.ts | 1 + .../CommentBubbleDeleted.module.css | 19 + .../CommentBubbleDeleted.tsx | 59 ++++ .../elements/CommentBubbleDeleted/index.ts | 1 + .../ShareStoryButton/ShareStoryButton.tsx | 1 - .../elements/StoryRing/StoryRing.module.css | 2 +- .../Comment/Comment.module.css | 1 - .../internal-components/Comment/index.tsx | 1 - .../CommentList/CommentList.tsx | 21 +- .../pages/StoryPage/GlobalFeedStory.tsx | 5 +- .../pages/StoryPage/StoryPage.module.css | 2 +- 16 files changed, 268 insertions(+), 184 deletions(-) create mode 100644 src/v4/social/constants/default-avatar.ts create mode 100644 src/v4/social/elements/CommentBubbleDeleted/CommentBubbleDeleted.module.css create mode 100644 src/v4/social/elements/CommentBubbleDeleted/CommentBubbleDeleted.tsx create mode 100644 src/v4/social/elements/CommentBubbleDeleted/index.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 35ecc2545..17e545f4f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,16 +19,16 @@ importers: version: 0.2.2(@fortawesome/fontawesome-svg-core@1.3.0)(react@18.3.1) '@hookform/error-message': specifier: ^2.0.1 - version: 2.0.1(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.51.5(react@18.3.1))(react@18.3.1) + version: 2.0.1(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.52.0(react@18.3.1))(react@18.3.1) '@hookform/resolvers': specifier: ^3.3.4 - version: 3.6.0(react-hook-form@7.51.5(react@18.3.1)) + version: 3.6.0(react-hook-form@7.52.0(react@18.3.1)) '@radix-ui/react-tabs': specifier: ^1.0.4 version: 1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@tanstack/react-query': specifier: ^5.28.14 - version: 5.44.0(react@18.3.1) + version: 5.45.1(react@18.3.1) clsx: specifier: ^2.1.0 version: 2.1.1 @@ -67,7 +67,7 @@ importers: version: 4.3.1 react-hook-form: specifier: ^7.49.2 - version: 7.51.5(react@18.3.1) + version: 7.52.0(react@18.3.1) react-infinite-scroll-component: specifier: ^6.1.0 version: 6.1.0(react@18.3.1) @@ -152,7 +152,7 @@ importers: version: 7.6.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@4.9.5) '@storybook/react-vite': specifier: ^7.6.7 - version: 7.6.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.18.0)(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.2)(less@4.2.0)(sass@1.77.4)(stylus@0.62.0)(terser@5.31.1)) + version: 7.6.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.18.0)(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1)) '@storybook/theming': specifier: ^7.6.7 version: 7.6.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -194,7 +194,7 @@ importers: version: 6.21.0(eslint@8.57.0)(typescript@4.9.5) '@vitejs/plugin-react': specifier: ^4.2.1 - version: 4.3.1(vite@4.5.3(@types/node@20.14.2)(less@4.2.0)(sass@1.77.4)(stylus@0.62.0)(terser@5.31.1)) + version: 4.3.1(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1)) autoprefixer: specifier: ^10.4.19 version: 10.4.19(postcss@8.4.38) @@ -218,13 +218,13 @@ importers: version: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) eslint-plugin-jest: specifier: ^27.6.1 - version: 27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(jest@29.7.0(@types/node@20.14.2))(typescript@4.9.5) + version: 27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(jest@29.7.0(@types/node@20.14.4))(typescript@4.9.5) husky: specifier: ^8.0.3 version: 8.0.3 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.14.2) + version: 29.7.0(@types/node@20.14.4) lint-staged: specifier: ^13.3.0 version: 13.3.0 @@ -263,10 +263,10 @@ importers: version: 8.0.1(stylelint@16.6.1(typescript@4.9.5)) svg-url-loader: specifier: ^7.1.1 - version: 7.1.1(webpack@5.91.0(esbuild@0.19.12)) + version: 7.1.1(webpack@5.92.0(esbuild@0.19.12)) ts-jest: specifier: ^29.1.1 - version: 29.1.4(@babel/core@7.24.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(esbuild@0.19.12)(jest@29.7.0(@types/node@20.14.2))(typescript@4.9.5) + version: 29.1.5(@babel/core@7.24.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(esbuild@0.19.12)(jest@29.7.0(@types/node@20.14.4))(typescript@4.9.5) tsup: specifier: ^7.3.0 version: 7.3.0(postcss@8.4.38)(typescript@4.9.5) @@ -278,10 +278,10 @@ importers: version: 5.1.0(typescript@4.9.5) vite: specifier: ^4.5.1 - version: 4.5.3(@types/node@20.14.2)(less@4.2.0)(sass@1.77.4)(stylus@0.62.0)(terser@5.31.1) + version: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1) vite-tsconfig-paths: specifier: ^4.2.3 - version: 4.3.2(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.2)(less@4.2.0)(sass@1.77.4)(stylus@0.62.0)(terser@5.31.1)) + version: 4.3.2(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1)) packages: @@ -2230,11 +2230,11 @@ packages: '@swc/helpers@0.5.11': resolution: {integrity: sha512-YNlnKRWF2sVojTpIyzwou9XoTNbzbzONwRhOoniEioF1AtaitTvVZblaQRrAzChWQ1bLYyYSWzM18y4WwgzJ+A==} - '@tanstack/query-core@5.44.0': - resolution: {integrity: sha512-Fa1J7iEEyJnjXG1N4+Fz4OXNH/INve3XSn0Htms3X4wgRsXHxMDwqBE2XtDCts7swkwSIs4izEtaFqWVFr/eLQ==} + '@tanstack/query-core@5.45.0': + resolution: {integrity: sha512-RVfIZQmFUTdjhSAAblvueimfngYyfN6HlwaJUPK71PKd7yi43Vs1S/rdimmZedPWX/WGppcq/U1HOj7O7FwYxw==} - '@tanstack/react-query@5.44.0': - resolution: {integrity: sha512-zmaD6cw8P2cBOfRHhqRmwvDsAi56gqbiwG2CDR6oQOCXSDOvRagvpP6RUStn+RrfcVUrNjKsxBmrgG6Za8p5zg==} + '@tanstack/react-query@5.45.1': + resolution: {integrity: sha512-mYYfJujKg2kxmkRRjA6nn4YKG3ITsKuH22f1kteJ5IuVQqgKUgbaSQfYwVP0gBS05mhwxO03HVpD0t7BMN7WOA==} peerDependencies: react: ^18.0.0 @@ -2355,11 +2355,11 @@ packages: '@types/node-fetch@2.6.11': resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==} - '@types/node@18.19.34': - resolution: {integrity: sha512-eXF4pfBNV5DAMKGbI02NnDtWrQ40hAN558/2vvS4gMpMIxaf6JmD7YjnZbq0Q9TDSSkKBamime8ewRoomHdt4g==} + '@types/node@18.19.36': + resolution: {integrity: sha512-tX1BNmYSWEvViftB26VLNxT6mEr37M7+ldUtq7rlKnv4/2fKYsJIOmqJAjT6h1DNuwQjIKgw3VJ/Dtw3yiTIQw==} - '@types/node@20.14.2': - resolution: {integrity: sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==} + '@types/node@20.14.4': + resolution: {integrity: sha512-1ChboN+57suCT2t/f8lwtPY/k3qTpuD/qnqQuYoBg6OQOcPyaw7PiZVdGpaZYAvhDDtqrt0oAaM8+oSu1xsUGw==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -2609,8 +2609,8 @@ packages: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} - acorn-import-assertions@1.9.0: - resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==} + acorn-import-attributes@1.9.5: + resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} peerDependencies: acorn: ^8 @@ -2628,8 +2628,8 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - acorn@8.11.3: - resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} + acorn@8.12.0: + resolution: {integrity: sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==} engines: {node: '>=0.4.0'} hasBin: true @@ -2982,8 +2982,8 @@ packages: camelize@1.0.1: resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} - caniuse-lite@1.0.30001632: - resolution: {integrity: sha512-udx3o7yHJfUxMLkGohMlVHCvFvWmirKh9JAH/d7WOLPetlH+LTL5cocMZ0t7oZx/mdlOWXti97xLZWc8uURRHg==} + caniuse-lite@1.0.30001636: + resolution: {integrity: sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg==} chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} @@ -3523,8 +3523,8 @@ packages: engines: {node: '>=0.10.0'} hasBin: true - electron-to-chromium@1.4.798: - resolution: {integrity: sha512-by9J2CiM9KPGj9qfp5U4FcPSbXJG7FNzqnYaY4WLzX+v2PHieVGmnsA4dxfpGE3QEC7JofpPZmn7Vn1B9NR2+Q==} + electron-to-chromium@1.4.805: + resolution: {integrity: sha512-8W4UJwX/w9T0QSzINJckTKG6CYpAUTqsaWcWIsdud3I1FYJcMgW9QqT1/4CBff/pP/TihWh13OmiyY8neto6vw==} element-resize-detector@1.2.4: resolution: {integrity: sha512-Fl5Ftk6WwXE0wqCgNoseKWndjzZlDCwuPTcoVZfCP9R3EHQF8qUtr3YUPNETegRBOKqQKPW3n4kiIWngGi8tKg==} @@ -3920,8 +3920,8 @@ packages: flatted@3.3.1: resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} - flow-parser@0.237.2: - resolution: {integrity: sha512-mvI/kdfr3l1waaPbThPA8dJa77nHXrfZIun+SWvFwSwDjmeByU7mGJGRmv1+7guU6ccyLV8e1lqZA1lD4iMGnQ==} + flow-parser@0.238.0: + resolution: {integrity: sha512-VE7XSv1epljsIN2YeBnxCmGJihpNIAnLLu/pPOdA+Gkso7qDltJwUi6vfHjgxdBbjSdAuPGnhuOHJUQG+yYwIg==} engines: {node: '>=0.4.0'} follow-redirects@1.15.6: @@ -3936,8 +3936,8 @@ packages: for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} - foreground-child@3.1.1: - resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} + foreground-child@3.2.1: + resolution: {integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==} engines: {node: '>=14'} form-data@4.0.0: @@ -4239,8 +4239,8 @@ packages: engines: {node: '>=14'} hasBin: true - hyphenate-style-name@1.0.5: - resolution: {integrity: sha512-fedL7PRwmeVkgyhu9hLeTBaI6wcGk7JGJswdaRsa5aUbkXI1kr1xZwTPBtaYPpwf56878iDek6VbVnuWMebJmw==} + hyphenate-style-name@1.1.0: + resolution: {integrity: sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==} iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} @@ -5576,6 +5576,10 @@ packages: q@1.5.1: resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==} engines: {node: '>=0.6.0', teleport: '>=0.2.0'} + deprecated: |- + You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. + + (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) qs@6.11.0: resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} @@ -5632,11 +5636,11 @@ packages: react: ^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0 react-dom: ^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0 - react-hook-form@7.51.5: - resolution: {integrity: sha512-J2ILT5gWx1XUIJRETiA7M19iXHlG74+6O3KApzvqB/w8S5NQR7AbU8HVZrMALdmDgWpRPYiZJl0zx8Z4L2mP6Q==} + react-hook-form@7.52.0: + resolution: {integrity: sha512-mJX506Xc6mirzLsmXUJyqlAI3Kj9Ph2RhplYhUVffeOQSnubK2uVqBFOBJmvKikvbFV91pxVXmDiR+QMF19x6A==} engines: {node: '>=12.22.0'} peerDependencies: - react: ^16.8.0 || ^17 || ^18 + react: ^16.8.0 || ^17 || ^18 || ^19 react-infinite-scroll-component@6.1.0: resolution: {integrity: sha512-SQu5nCqy8DxQWpnUVLx7V7b7LcA37aM7tvoWjTLZp1dk6EJibM5/4EJKzOnl07/BsM1Y40sKLuqjCwwH/xV0TQ==} @@ -5903,8 +5907,8 @@ packages: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rfdc@1.3.1: - resolution: {integrity: sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==} + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} rimraf@2.6.3: resolution: {integrity: sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==} @@ -5954,8 +5958,8 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sass@1.77.4: - resolution: {integrity: sha512-vcF3Ckow6g939GMA4PeU7b2K/9FALXk2KF9J87txdHzXbUF9XRQRwSxcAs/fGaTnJeBFd7UoV22j3lzMLdM0Pw==} + sass@1.77.6: + resolution: {integrity: sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q==} engines: {node: '>=14.0.0'} hasBin: true @@ -6459,8 +6463,8 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - ts-jest@29.1.4: - resolution: {integrity: sha512-YiHwDhSvCiItoAgsKtoLFCuakDzDsJ1DLDnSouTaTmdOcOwIkSzbLXduaQ6M5DRVhuZC/NYaaZ/mtHbWMv/S6Q==} + ts-jest@29.1.5: + resolution: {integrity: sha512-UuClSYxM7byvvYfyWdFI+/2UxMmwNyJb0NPkZPQE2hew3RurV7l7zURgOHAd/1I1ZdPpe3GUsXNXAcN8TFKSIg==} engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -6816,8 +6820,8 @@ packages: webpack-virtual-modules@0.6.2: resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} - webpack@5.91.0: - resolution: {integrity: sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==} + webpack@5.92.0: + resolution: {integrity: sha512-Bsw2X39MYIgxouNATyVpCNVWBCuUwDgWtN78g6lSdPJRLaQ/PUVm/oXcaRAyY/sMFoKFQrsPeqvTizWtq7QPCA==} engines: {node: '>=10.13.0'} hasBin: true peerDependencies: @@ -6888,8 +6892,8 @@ packages: utf-8-validate: optional: true - ws@6.2.2: - resolution: {integrity: sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==} + ws@6.2.3: + resolution: {integrity: sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==} peerDependencies: bufferutil: ^4.0.1 utf-8-validate: ^5.0.2 @@ -6899,8 +6903,8 @@ packages: utf-8-validate: optional: true - ws@7.5.9: - resolution: {integrity: sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==} + ws@7.5.10: + resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} engines: {node: '>=8.3.0'} peerDependencies: bufferutil: ^4.0.1 @@ -6911,8 +6915,8 @@ packages: utf-8-validate: optional: true - ws@8.17.0: - resolution: {integrity: sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==} + ws@8.17.1: + resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -8139,15 +8143,15 @@ snapshots: prop-types: 15.8.1 react: 18.3.1 - '@hookform/error-message@2.0.1(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.51.5(react@18.3.1))(react@18.3.1)': + '@hookform/error-message@2.0.1(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.52.0(react@18.3.1))(react@18.3.1)': dependencies: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-hook-form: 7.51.5(react@18.3.1) + react-hook-form: 7.52.0(react@18.3.1) - '@hookform/resolvers@3.6.0(react-hook-form@7.51.5(react@18.3.1))': + '@hookform/resolvers@3.6.0(react-hook-form@7.52.0(react@18.3.1))': dependencies: - react-hook-form: 7.51.5(react@18.3.1) + react-hook-form: 7.52.0(react@18.3.1) '@humanwhocodes/config-array@0.11.14': dependencies: @@ -8185,7 +8189,7 @@ snapshots: '@jest/console@29.7.0': dependencies: '@jest/types': 29.6.3 - '@types/node': 20.14.2 + '@types/node': 20.14.4 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 @@ -8198,14 +8202,14 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.14.2 + '@types/node': 20.14.4 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.14.2) + jest-config: 29.7.0(@types/node@20.14.4) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -8230,7 +8234,7 @@ snapshots: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.14.2 + '@types/node': 20.14.4 jest-mock: 29.7.0 '@jest/expect-utils@29.7.0': @@ -8248,7 +8252,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 20.14.2 + '@types/node': 20.14.4 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -8270,7 +8274,7 @@ snapshots: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.25 - '@types/node': 20.14.2 + '@types/node': 20.14.4 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 @@ -8340,17 +8344,17 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.14.2 + '@types/node': 20.14.4 '@types/yargs': 17.0.32 chalk: 4.1.2 - '@joshwooding/vite-plugin-react-docgen-typescript@0.3.0(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.2)(less@4.2.0)(sass@1.77.4)(stylus@0.62.0)(terser@5.31.1))': + '@joshwooding/vite-plugin-react-docgen-typescript@0.3.0(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1))': dependencies: glob: 7.2.3 glob-promise: 4.2.2(glob@7.2.3) magic-string: 0.27.0 react-docgen-typescript: 2.2.2(typescript@4.9.5) - vite: 4.5.3(@types/node@20.14.2)(less@4.2.0)(sass@1.77.4)(stylus@0.62.0)(terser@5.31.1) + vite: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1) optionalDependencies: typescript: 4.9.5 @@ -9033,7 +9037,7 @@ snapshots: - encoding - supports-color - '@storybook/builder-vite@7.6.19(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.2)(less@4.2.0)(sass@1.77.4)(stylus@0.62.0)(terser@5.31.1))': + '@storybook/builder-vite@7.6.19(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1))': dependencies: '@storybook/channels': 7.6.19 '@storybook/client-logger': 7.6.19 @@ -9051,7 +9055,7 @@ snapshots: fs-extra: 11.2.0 magic-string: 0.30.10 rollup: 3.29.4 - vite: 4.5.3(@types/node@20.14.2)(less@4.2.0)(sass@1.77.4)(stylus@0.62.0)(terser@5.31.1) + vite: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1) optionalDependencies: typescript: 4.9.5 transitivePeerDependencies: @@ -9185,7 +9189,7 @@ snapshots: '@storybook/node-logger': 7.6.19 '@storybook/types': 7.6.19 '@types/find-cache-dir': 3.2.1 - '@types/node': 18.19.34 + '@types/node': 18.19.36 '@types/node-fetch': 2.6.11 '@types/pretty-hrtime': 1.0.3 chalk: 4.1.2 @@ -9234,7 +9238,7 @@ snapshots: '@storybook/telemetry': 7.6.19 '@storybook/types': 7.6.19 '@types/detect-port': 1.3.5 - '@types/node': 18.19.34 + '@types/node': 18.19.36 '@types/pretty-hrtime': 1.0.3 '@types/semver': 7.5.8 better-opn: 3.0.2 @@ -9258,7 +9262,7 @@ snapshots: util: 0.12.5 util-deprecate: 1.0.2 watchpack: 2.4.1 - ws: 8.17.0 + ws: 8.17.1 transitivePeerDependencies: - bufferutil - encoding @@ -9376,18 +9380,18 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@storybook/react-vite@7.6.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.18.0)(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.2)(less@4.2.0)(sass@1.77.4)(stylus@0.62.0)(terser@5.31.1))': + '@storybook/react-vite@7.6.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.18.0)(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1))': dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.3.0(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.2)(less@4.2.0)(sass@1.77.4)(stylus@0.62.0)(terser@5.31.1)) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.3.0(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1)) '@rollup/pluginutils': 5.1.0(rollup@4.18.0) - '@storybook/builder-vite': 7.6.19(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.2)(less@4.2.0)(sass@1.77.4)(stylus@0.62.0)(terser@5.31.1)) + '@storybook/builder-vite': 7.6.19(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1)) '@storybook/react': 7.6.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@4.9.5) - '@vitejs/plugin-react': 3.1.0(vite@4.5.3(@types/node@20.14.2)(less@4.2.0)(sass@1.77.4)(stylus@0.62.0)(terser@5.31.1)) + '@vitejs/plugin-react': 3.1.0(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1)) magic-string: 0.30.10 react: 18.3.1 react-docgen: 7.0.3 react-dom: 18.3.1(react@18.3.1) - vite: 4.5.3(@types/node@20.14.2)(less@4.2.0)(sass@1.77.4)(stylus@0.62.0)(terser@5.31.1) + vite: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1) transitivePeerDependencies: - '@preact/preset-vite' - encoding @@ -9407,7 +9411,7 @@ snapshots: '@storybook/types': 7.6.19 '@types/escodegen': 0.0.6 '@types/estree': 0.0.51 - '@types/node': 18.19.34 + '@types/node': 18.19.36 acorn: 7.4.1 acorn-jsx: 5.3.2(acorn@7.4.1) acorn-walk: 7.2.0 @@ -9483,11 +9487,11 @@ snapshots: dependencies: tslib: 2.6.3 - '@tanstack/query-core@5.44.0': {} + '@tanstack/query-core@5.45.0': {} - '@tanstack/react-query@5.44.0(react@18.3.1)': + '@tanstack/react-query@5.45.1(react@18.3.1)': dependencies: - '@tanstack/query-core': 5.44.0 + '@tanstack/query-core': 5.45.0 react: 18.3.1 '@types/babel__core@7.20.5': @@ -9514,15 +9518,15 @@ snapshots: '@types/body-parser@1.19.5': dependencies: '@types/connect': 3.4.38 - '@types/node': 20.14.2 + '@types/node': 20.14.4 '@types/connect@3.4.38': dependencies: - '@types/node': 20.14.2 + '@types/node': 20.14.4 '@types/cross-spawn@6.0.6': dependencies: - '@types/node': 20.14.2 + '@types/node': 20.14.4 '@types/detect-port@1.3.5': {} @@ -9552,7 +9556,7 @@ snapshots: '@types/express-serve-static-core@4.19.3': dependencies: - '@types/node': 20.14.2 + '@types/node': 20.14.4 '@types/qs': 6.9.15 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 @@ -9569,11 +9573,11 @@ snapshots: '@types/glob@7.2.0': dependencies: '@types/minimatch': 5.1.2 - '@types/node': 20.14.2 + '@types/node': 20.14.4 '@types/graceful-fs@4.1.9': dependencies: - '@types/node': 20.14.2 + '@types/node': 20.14.4 '@types/hoist-non-react-statics@3.3.5': dependencies: @@ -9619,14 +9623,14 @@ snapshots: '@types/node-fetch@2.6.11': dependencies: - '@types/node': 18.19.34 + '@types/node': 18.19.36 form-data: 4.0.0 - '@types/node@18.19.34': + '@types/node@18.19.36': dependencies: undici-types: 5.26.5 - '@types/node@20.14.2': + '@types/node@20.14.4': dependencies: undici-types: 5.26.5 @@ -9679,12 +9683,12 @@ snapshots: '@types/send@0.17.4': dependencies: '@types/mime': 1.3.5 - '@types/node': 20.14.2 + '@types/node': 20.14.4 '@types/serve-static@1.15.7': dependencies: '@types/http-errors': 2.0.4 - '@types/node': 20.14.2 + '@types/node': 20.14.4 '@types/send': 0.17.4 '@types/stack-utils@2.0.3': {} @@ -9836,25 +9840,25 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vitejs/plugin-react@3.1.0(vite@4.5.3(@types/node@20.14.2)(less@4.2.0)(sass@1.77.4)(stylus@0.62.0)(terser@5.31.1))': + '@vitejs/plugin-react@3.1.0(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1))': dependencies: '@babel/core': 7.24.7 '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.24.7) '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.24.7) magic-string: 0.27.0 react-refresh: 0.14.2 - vite: 4.5.3(@types/node@20.14.2)(less@4.2.0)(sass@1.77.4)(stylus@0.62.0)(terser@5.31.1) + vite: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1) transitivePeerDependencies: - supports-color - '@vitejs/plugin-react@4.3.1(vite@4.5.3(@types/node@20.14.2)(less@4.2.0)(sass@1.77.4)(stylus@0.62.0)(terser@5.31.1))': + '@vitejs/plugin-react@4.3.1(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1))': dependencies: '@babel/core': 7.24.7 '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.24.7) '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.24.7) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 4.5.3(@types/node@20.14.2)(less@4.2.0)(sass@1.77.4)(stylus@0.62.0)(terser@5.31.1) + vite: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1) transitivePeerDependencies: - supports-color @@ -9965,23 +9969,23 @@ snapshots: mime-types: 2.1.35 negotiator: 0.6.3 - acorn-import-assertions@1.9.0(acorn@8.11.3): + acorn-import-attributes@1.9.5(acorn@8.12.0): dependencies: - acorn: 8.11.3 + acorn: 8.12.0 acorn-jsx@5.3.2(acorn@7.4.1): dependencies: acorn: 7.4.1 - acorn-jsx@5.3.2(acorn@8.11.3): + acorn-jsx@5.3.2(acorn@8.12.0): dependencies: - acorn: 8.11.3 + acorn: 8.12.0 acorn-walk@7.2.0: {} acorn@7.4.1: {} - acorn@8.11.3: {} + acorn@8.12.0: {} add-stream@1.0.0: {} @@ -10146,7 +10150,7 @@ snapshots: autoprefixer@10.4.19(postcss@8.4.38): dependencies: browserslist: 4.23.1 - caniuse-lite: 1.0.30001632 + caniuse-lite: 1.0.30001636 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.0.1 @@ -10323,8 +10327,8 @@ snapshots: browserslist@4.23.1: dependencies: - caniuse-lite: 1.0.30001632 - electron-to-chromium: 1.4.798 + caniuse-lite: 1.0.30001636 + electron-to-chromium: 1.4.805 node-releases: 2.0.14 update-browserslist-db: 1.0.16(browserslist@4.23.1) @@ -10380,7 +10384,7 @@ snapshots: camelize@1.0.1: {} - caniuse-lite@1.0.30001632: {} + caniuse-lite@1.0.30001636: {} chalk@2.4.2: dependencies: @@ -10705,13 +10709,13 @@ snapshots: optionalDependencies: typescript: 4.9.5 - create-jest@29.7.0(@types/node@20.14.2): + create-jest@29.7.0(@types/node@20.14.4): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.14.2) + jest-config: 29.7.0(@types/node@20.14.4) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -10734,7 +10738,7 @@ snapshots: css-in-js-utils@3.1.0: dependencies: - hyphenate-style-name: 1.0.5 + hyphenate-style-name: 1.1.0 css-to-react-native@3.2.0: dependencies: @@ -10896,7 +10900,7 @@ snapshots: doiuse@6.0.2: dependencies: browserslist: 4.23.1 - caniuse-lite: 1.0.30001632 + caniuse-lite: 1.0.30001636 css-tokenize: 1.0.1 duplexify: 4.1.3 ldjson-stream: 1.2.1 @@ -10940,7 +10944,7 @@ snapshots: dependencies: jake: 10.9.1 - electron-to-chromium@1.4.798: {} + electron-to-chromium@1.4.805: {} element-resize-detector@1.2.4: dependencies: @@ -11236,13 +11240,13 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(jest@29.7.0(@types/node@20.14.2))(typescript@4.9.5): + eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(jest@29.7.0(@types/node@20.14.4))(typescript@4.9.5): dependencies: '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@4.9.5) eslint: 8.57.0 optionalDependencies: '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(typescript@4.9.5) - jest: 29.7.0(@types/node@20.14.2) + jest: 29.7.0(@types/node@20.14.4) transitivePeerDependencies: - supports-color - typescript @@ -11304,8 +11308,8 @@ snapshots: espree@9.6.1: dependencies: - acorn: 8.11.3 - acorn-jsx: 5.3.2(acorn@8.11.3) + acorn: 8.12.0 + acorn-jsx: 5.3.2(acorn@8.12.0) eslint-visitor-keys: 3.4.3 esprima@4.0.1: {} @@ -11475,11 +11479,11 @@ snapshots: dependencies: flat-cache: 5.0.0 - file-loader@6.2.0(webpack@5.91.0(esbuild@0.19.12)): + file-loader@6.2.0(webpack@5.92.0(esbuild@0.19.12)): dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.92.0(esbuild@0.19.12) file-system-cache@2.3.0: dependencies: @@ -11551,7 +11555,7 @@ snapshots: flatted@3.3.1: {} - flow-parser@0.237.2: {} + flow-parser@0.238.0: {} follow-redirects@1.15.6(debug@4.3.5): optionalDependencies: @@ -11561,7 +11565,7 @@ snapshots: dependencies: is-callable: 1.2.7 - foreground-child@3.1.1: + foreground-child@3.2.1: dependencies: cross-spawn: 7.0.3 signal-exit: 4.1.0 @@ -11713,7 +11717,7 @@ snapshots: glob@10.4.1: dependencies: - foreground-child: 3.1.1 + foreground-child: 3.2.1 jackspeak: 3.4.0 minimatch: 9.0.4 minipass: 7.1.2 @@ -11866,7 +11870,7 @@ snapshots: husky@8.0.3: {} - hyphenate-style-name@1.0.5: {} + hyphenate-style-name@1.1.0: {} iconv-lite@0.4.24: dependencies: @@ -12152,7 +12156,7 @@ snapshots: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.14.2 + '@types/node': 20.14.4 chalk: 4.1.2 co: 4.6.0 dedent: 1.5.3 @@ -12172,16 +12176,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@20.14.2): + jest-cli@29.7.0(@types/node@20.14.4): dependencies: '@jest/core': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.14.2) + create-jest: 29.7.0(@types/node@20.14.4) exit: 0.1.2 import-local: 3.1.0 - jest-config: 29.7.0(@types/node@20.14.2) + jest-config: 29.7.0(@types/node@20.14.4) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -12191,7 +12195,7 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@20.14.2): + jest-config@29.7.0(@types/node@20.14.4): dependencies: '@babel/core': 7.24.7 '@jest/test-sequencer': 29.7.0 @@ -12216,7 +12220,7 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 20.14.2 + '@types/node': 20.14.4 transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -12245,7 +12249,7 @@ snapshots: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.14.2 + '@types/node': 20.14.4 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -12255,7 +12259,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 20.14.2 + '@types/node': 20.14.4 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -12294,7 +12298,7 @@ snapshots: jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.14.2 + '@types/node': 20.14.4 jest-util: 29.7.0 jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): @@ -12329,7 +12333,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.14.2 + '@types/node': 20.14.4 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -12357,7 +12361,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.14.2 + '@types/node': 20.14.4 chalk: 4.1.2 cjs-module-lexer: 1.3.1 collect-v8-coverage: 1.0.2 @@ -12403,7 +12407,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.14.2 + '@types/node': 20.14.4 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -12422,7 +12426,7 @@ snapshots: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.14.2 + '@types/node': 20.14.4 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -12431,23 +12435,23 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 20.14.2 + '@types/node': 20.14.4 merge-stream: 2.0.0 supports-color: 8.1.1 jest-worker@29.7.0: dependencies: - '@types/node': 20.14.2 + '@types/node': 20.14.4 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@20.14.2): + jest@29.7.0(@types/node@20.14.4): dependencies: '@jest/core': 29.7.0 '@jest/types': 29.6.3 import-local: 3.1.0 - jest-cli: 29.7.0(@types/node@20.14.2) + jest-cli: 29.7.0(@types/node@20.14.4) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -12487,7 +12491,7 @@ snapshots: '@babel/register': 7.24.6(@babel/core@7.24.7) babel-core: 7.0.0-bridge.0(@babel/core@7.24.7) chalk: 4.1.2 - flow-parser: 0.237.2 + flow-parser: 0.238.0 graceful-fs: 4.2.11 micromatch: 4.0.7 neo-async: 2.6.2 @@ -12615,7 +12619,7 @@ snapshots: colorette: 2.0.20 eventemitter3: 5.0.1 log-update: 5.0.1 - rfdc: 1.3.1 + rfdc: 1.4.1 wrap-ansi: 8.1.0 load-json-file@4.0.0: @@ -12885,9 +12889,9 @@ snapshots: pump: 3.0.0 readable-stream: 3.6.2 reinterval: 1.1.0 - rfdc: 1.3.1 + rfdc: 1.4.1 split2: 3.2.2 - ws: 7.5.9 + ws: 7.5.10 xtend: 4.0.2 transitivePeerDependencies: - bufferutil @@ -13336,7 +13340,7 @@ snapshots: progress: 2.0.3 proxy-from-env: 1.1.0 rimraf: 2.7.1 - ws: 6.2.2 + ws: 6.2.3 transitivePeerDependencies: - bufferutil - supports-color @@ -13411,7 +13415,7 @@ snapshots: react-dom: 18.3.1(react@18.3.1) react-is: 18.1.0 - react-hook-form@7.51.5(react@18.3.1): + react-hook-form@7.52.0(react@18.3.1): dependencies: react: 18.3.1 @@ -13729,7 +13733,7 @@ snapshots: reusify@1.0.4: {} - rfdc@1.3.1: {} + rfdc@1.4.1: {} rimraf@2.6.3: dependencies: @@ -13796,7 +13800,7 @@ snapshots: safer-buffer@2.1.2: {} - sass@1.77.4: + sass@1.77.6: dependencies: chokidar: 3.6.0 immutable: 4.3.6 @@ -14249,11 +14253,11 @@ snapshots: svg-tags@1.0.0: {} - svg-url-loader@7.1.1(webpack@5.91.0(esbuild@0.19.12)): + svg-url-loader@7.1.1(webpack@5.92.0(esbuild@0.19.12)): dependencies: - file-loader: 6.2.0(webpack@5.91.0(esbuild@0.19.12)) + file-loader: 6.2.0(webpack@5.92.0(esbuild@0.19.12)) loader-utils: 2.0.4 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.92.0(esbuild@0.19.12) synchronous-promise@2.0.17: {} @@ -14309,21 +14313,21 @@ snapshots: type-fest: 0.16.0 unique-string: 2.0.0 - terser-webpack-plugin@5.3.10(esbuild@0.19.12)(webpack@5.91.0(esbuild@0.19.12)): + terser-webpack-plugin@5.3.10(esbuild@0.19.12)(webpack@5.92.0(esbuild@0.19.12)): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 terser: 5.31.1 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.92.0(esbuild@0.19.12) optionalDependencies: esbuild: 0.19.12 terser@5.31.1: dependencies: '@jridgewell/source-map': 0.3.6 - acorn: 8.11.3 + acorn: 8.12.0 commander: 2.20.3 source-map-support: 0.5.21 @@ -14403,11 +14407,11 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.1.4(@babel/core@7.24.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(esbuild@0.19.12)(jest@29.7.0(@types/node@20.14.2))(typescript@4.9.5): + ts-jest@29.1.5(@babel/core@7.24.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(esbuild@0.19.12)(jest@29.7.0(@types/node@20.14.4))(typescript@4.9.5): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@20.14.2) + jest: 29.7.0(@types/node@20.14.4) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -14548,7 +14552,7 @@ snapshots: postcss-modules-local-by-default: 4.0.5(postcss@8.4.38) postcss-modules-scope: 3.2.0(postcss@8.4.38) reserved-words: 0.1.2 - sass: 1.77.4 + sass: 1.77.6 source-map-js: 1.2.0 stylus: 0.62.0 tsconfig-paths: 4.2.0 @@ -14607,7 +14611,7 @@ snapshots: unplugin@1.10.1: dependencies: - acorn: 8.11.3 + acorn: 8.12.0 chokidar: 3.6.0 webpack-sources: 3.2.3 webpack-virtual-modules: 0.6.2 @@ -14700,27 +14704,27 @@ snapshots: - '@types/react' - '@types/react-dom' - vite-tsconfig-paths@4.3.2(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.2)(less@4.2.0)(sass@1.77.4)(stylus@0.62.0)(terser@5.31.1)): + vite-tsconfig-paths@4.3.2(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1)): dependencies: debug: 4.3.5 globrex: 0.1.2 tsconfck: 3.1.0(typescript@4.9.5) optionalDependencies: - vite: 4.5.3(@types/node@20.14.2)(less@4.2.0)(sass@1.77.4)(stylus@0.62.0)(terser@5.31.1) + vite: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1) transitivePeerDependencies: - supports-color - typescript - vite@4.5.3(@types/node@20.14.2)(less@4.2.0)(sass@1.77.4)(stylus@0.62.0)(terser@5.31.1): + vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1): dependencies: esbuild: 0.18.20 postcss: 8.4.38 rollup: 3.29.4 optionalDependencies: - '@types/node': 20.14.2 + '@types/node': 20.14.4 fsevents: 2.3.3 less: 4.2.0 - sass: 1.77.4 + sass: 1.77.6 stylus: 0.62.0 terser: 5.31.1 @@ -14745,15 +14749,15 @@ snapshots: webpack-virtual-modules@0.6.2: {} - webpack@5.91.0(esbuild@0.19.12): + webpack@5.92.0(esbuild@0.19.12): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.5 '@webassemblyjs/ast': 1.12.1 '@webassemblyjs/wasm-edit': 1.12.1 '@webassemblyjs/wasm-parser': 1.12.1 - acorn: 8.11.3 - acorn-import-assertions: 1.9.0(acorn@8.11.3) + acorn: 8.12.0 + acorn-import-attributes: 1.9.5(acorn@8.12.0) browserslist: 4.23.1 chrome-trace-event: 1.0.4 enhanced-resolve: 5.17.0 @@ -14768,7 +14772,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(esbuild@0.19.12)(webpack@5.91.0(esbuild@0.19.12)) + terser-webpack-plugin: 5.3.10(esbuild@0.19.12)(webpack@5.92.0(esbuild@0.19.12)) watchpack: 2.4.1 webpack-sources: 3.2.3 transitivePeerDependencies: @@ -14849,13 +14853,13 @@ snapshots: dependencies: async-limiter: 1.0.1 - ws@6.2.2: + ws@6.2.3: dependencies: async-limiter: 1.0.1 - ws@7.5.9: {} + ws@7.5.10: {} - ws@8.17.0: {} + ws@8.17.1: {} xmlhttprequest-ssl@1.6.3: {} diff --git a/src/v4/core/IconComponent.tsx b/src/v4/core/IconComponent.tsx index 9042345e6..721f3b72b 100644 --- a/src/v4/core/IconComponent.tsx +++ b/src/v4/core/IconComponent.tsx @@ -20,7 +20,6 @@ export const IconComponent = ({ configIconName, className, }: IconComponentProps) => { - return ( <button className={className} data-qa-anchor={'data-qa-anchor'} onClick={onClick} style={style}> {defaultIconName === configIconName ? defaultIcon() : imgIcon()} diff --git a/src/v4/core/components/Avatar/Avatar.tsx b/src/v4/core/components/Avatar/Avatar.tsx index 8a324f3dd..35c560958 100644 --- a/src/v4/core/components/Avatar/Avatar.tsx +++ b/src/v4/core/components/Avatar/Avatar.tsx @@ -42,6 +42,7 @@ export const Avatar = ({ styles[size], )} onClick={onClick} + style={{ backgroundImage: backgroundImage ? backgroundImage : undefined }} {...props} > {loading ? ( diff --git a/src/v4/core/components/LoadMoreWrapper/LoadMoreWrapper.module.css b/src/v4/core/components/LoadMoreWrapper/LoadMoreWrapper.module.css index e9e8331be..14fdd88c0 100644 --- a/src/v4/core/components/LoadMoreWrapper/LoadMoreWrapper.module.css +++ b/src/v4/core/components/LoadMoreWrapper/LoadMoreWrapper.module.css @@ -7,6 +7,7 @@ padding: 0.3125rem 0.75rem 0.3125rem 0.5rem; border-radius: 0.25rem; margin-left: 3rem; + margin-bottom: var(--asc-spacing-m1); } .loadMoreButton.textCenter { diff --git a/src/v4/core/providers/CustomizationProvider.tsx b/src/v4/core/providers/CustomizationProvider.tsx index d6522cb11..86a031777 100644 --- a/src/v4/core/providers/CustomizationProvider.tsx +++ b/src/v4/core/providers/CustomizationProvider.tsx @@ -238,6 +238,10 @@ export const defaultConfig: DefaultConfig = { '*/comment_tray_component/*': { theme: {}, }, + '*/comment_tray_component/comment_bubble_deleted_view': { + comment_bubble_deleted_icon: 'comment_bubble_deleted.png', + text: 'This reply has been deleted', + }, '*/story_tab_component/*': {}, '*/story_tab_component/story_ring': { progress_color: ['#339AF9', '#78FA58'], diff --git a/src/v4/social/constants/default-avatar.ts b/src/v4/social/constants/default-avatar.ts new file mode 100644 index 000000000..8abc8af88 --- /dev/null +++ b/src/v4/social/constants/default-avatar.ts @@ -0,0 +1 @@ +export const communityProfileImageBackground = `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100%' height='100%' viewBox='0 0 64 64' fill='none'%3E%3Crect width='64' height='64' rx='32' fill='%23D9E5FC'/%3E%3Cpath d='M31.7539 19.2C33.2201 19.2 34.6262 19.7696 35.6629 20.7836C36.6997 21.7976 37.2821 23.1728 37.2821 24.6068C37.2821 26.0408 36.6997 27.4161 35.6629 28.4301C34.6262 29.4441 33.2201 30.0137 31.7539 30.0137C30.2877 30.0137 28.8816 29.4441 27.8449 28.4301C26.8081 27.4161 26.2257 26.0408 26.2257 24.6068C26.2257 23.1728 26.8081 21.7976 27.8449 20.7836C28.8816 19.7696 30.2877 19.2 31.7539 19.2ZM20.6975 23.062C21.582 23.062 22.4033 23.2937 23.1141 23.7108C22.8772 25.92 23.5406 28.1136 24.8989 29.8284C24.1092 31.3114 22.5297 32.331 20.6975 32.331C19.4408 32.331 18.2355 31.8427 17.3469 30.9736C16.4583 30.1044 15.959 28.9256 15.959 27.6965C15.959 26.4674 16.4583 25.2886 17.3469 24.4194C18.2355 23.5503 19.4408 23.062 20.6975 23.062ZM42.8103 23.062C44.067 23.062 45.2723 23.5503 46.1609 24.4194C47.0495 25.2886 47.5488 26.4674 47.5488 27.6965C47.5488 28.9256 47.0495 30.1044 46.1609 30.9736C45.2723 31.8427 44.067 32.331 42.8103 32.331C40.9781 32.331 39.3986 31.3114 38.6089 29.8284C39.9672 28.1136 40.6306 25.92 40.3937 23.7108C41.1045 23.2937 41.9258 23.062 42.8103 23.062ZM21.4872 38.8965C21.4872 35.6987 26.0835 33.1034 31.7539 33.1034C37.4243 33.1034 42.0206 35.6987 42.0206 38.8965V41.5999H21.4872V38.8965ZM12.8 41.5999V39.2827C12.8 37.1354 15.7853 35.3279 19.8288 34.8027C18.8969 35.8532 18.3283 37.3053 18.3283 38.8965V41.5999H12.8ZM50.7077 41.5999H45.1795V38.8965C45.1795 37.3053 44.6109 35.8532 43.679 34.8027C47.7225 35.3279 50.7077 37.1354 50.7077 39.2827V41.5999Z' fill='white'/%3E%3C/svg%3E")`; diff --git a/src/v4/social/elements/CommentBubbleDeleted/CommentBubbleDeleted.module.css b/src/v4/social/elements/CommentBubbleDeleted/CommentBubbleDeleted.module.css new file mode 100644 index 000000000..12418468d --- /dev/null +++ b/src/v4/social/elements/CommentBubbleDeleted/CommentBubbleDeleted.module.css @@ -0,0 +1,19 @@ +.commentBubbleDeleted__container { + margin-left: 3.25rem; + display: inline-flex; + padding: 0.3125rem 0.75rem 0.3125rem 0.5rem; + justify-content: flex-start; + align-items: center; + gap: 0.625rem; + color: var(--asc-color-secondary-shade2); + background-color: var(--asc-color-base-shade4); + border-radius: 0.25rem; + margin-bottom: var(--asc-spacing-m1); +} + +.commentBubbleDeleted__icon { + cursor: auto; + width: 1rem; + height: 1rem; + fill: var(--asc-color-secondary-shade2); +} diff --git a/src/v4/social/elements/CommentBubbleDeleted/CommentBubbleDeleted.tsx b/src/v4/social/elements/CommentBubbleDeleted/CommentBubbleDeleted.tsx new file mode 100644 index 000000000..0808742f1 --- /dev/null +++ b/src/v4/social/elements/CommentBubbleDeleted/CommentBubbleDeleted.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import clsx from 'clsx'; +import { Typography } from '~/v4/core/components'; +import { IconComponent } from '~/v4/core/IconComponent'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; + +import styles from './CommentBubbleDeleted.module.css'; + +const CommentBubbleDeletedSvg = (props: React.SVGProps<SVGSVGElement>) => ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="16" + height="16" + fill="none" + viewBox="0 0 16 16" + {...props} + > + <path d="M4.375 8.875A.361.361 0 014 8.5v-1a.38.38 0 01.375-.375h7.25c.188 0 .375.188.375.375v1a.38.38 0 01-.375.375h-7.25zM15.75 8A7.749 7.749 0 018 15.75 7.749 7.749 0 01.25 8 7.749 7.749 0 018 .25 7.749 7.749 0 0115.75 8zm-1.5 0c0-3.438-2.813-6.25-6.25-6.25A6.248 6.248 0 001.75 8 6.228 6.228 0 008 14.25 6.248 6.248 0 0014.25 8z"></path> + </svg> +); + +interface CommentBubbleDeletedProps { + pageId?: string; + componentId?: string; + defaultIconClassName?: string; + imgIconClassName?: string; +} + +export function CommentBubbleDeleted({ + pageId = '*', + componentId = '*', + defaultIconClassName, + imgIconClassName, +}: CommentBubbleDeletedProps) { + const elementId = 'comment_bubble_deleted_view'; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference } = useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + return ( + <IconComponent + defaultIcon={() => ( + <div className={styles.commentBubbleDeleted__container} data-qa-anchor={accessibilityId}> + <CommentBubbleDeletedSvg + className={clsx(styles.commentBubbleDeleted__icon, defaultIconClassName)} + /> + <Typography.Caption>{config.text}</Typography.Caption> + </div> + )} + imgIcon={() => <img src={config.icon} alt={uiReference} className={imgIconClassName} />} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} + /> + ); +} diff --git a/src/v4/social/elements/CommentBubbleDeleted/index.ts b/src/v4/social/elements/CommentBubbleDeleted/index.ts new file mode 100644 index 000000000..dbcb2574a --- /dev/null +++ b/src/v4/social/elements/CommentBubbleDeleted/index.ts @@ -0,0 +1 @@ +export { CommentBubbleDeleted } from './CommentBubbleDeleted'; diff --git a/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx b/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx index bd247b814..f93b57c2a 100644 --- a/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx +++ b/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx @@ -1,6 +1,5 @@ import React from 'react'; import clsx from 'clsx'; - import { Avatar, Typography } from '~/v4/core/components'; import { useAmityElement } from '~/v4/core/hooks/uikit/index'; import Community from '~/v4/icons/Community'; diff --git a/src/v4/social/elements/StoryRing/StoryRing.module.css b/src/v4/social/elements/StoryRing/StoryRing.module.css index 2c45afd9a..5752587d1 100644 --- a/src/v4/social/elements/StoryRing/StoryRing.module.css +++ b/src/v4/social/elements/StoryRing/StoryRing.module.css @@ -1,5 +1,5 @@ .emptyStateRing { - stroke: var(--asc-color-base-shade4); + stroke: var(--asc-color-secondary-shade4); stroke-width: 2; fill: none; } diff --git a/src/v4/social/internal-components/Comment/Comment.module.css b/src/v4/social/internal-components/Comment/Comment.module.css index e7bf63915..46a8e9a08 100644 --- a/src/v4/social/internal-components/Comment/Comment.module.css +++ b/src/v4/social/internal-components/Comment/Comment.module.css @@ -30,7 +30,6 @@ text-align: center; color: var(--asc-color-base-shade2); background-color: var(--asc-color-base-background); - border-top: 1px solid var(--asc-color-base-shade4); } .deletedCommentBlock:first-child { diff --git a/src/v4/social/internal-components/Comment/index.tsx b/src/v4/social/internal-components/Comment/index.tsx index 7665d2cda..326d052ab 100644 --- a/src/v4/social/internal-components/Comment/index.tsx +++ b/src/v4/social/internal-components/Comment/index.tsx @@ -175,7 +175,6 @@ export const Comment = ({ commentId, readonly, onClickReply }: CommentProps) => const title = isReplyComment ? 'reply.delete' : 'comment.delete'; const content = isReplyComment ? 'reply.deleteBody' : 'comment.deleteBody'; confirm({ - 'data-qa-anchor': 'delete-comment', title: <FormattedMessage id={title} />, content: <FormattedMessage id={content} />, cancelText: formatMessage({ id: 'comment.deleteConfirmCancelText' }), diff --git a/src/v4/social/internal-components/CommentList/CommentList.tsx b/src/v4/social/internal-components/CommentList/CommentList.tsx index 2582dbd8a..cf89b7681 100644 --- a/src/v4/social/internal-components/CommentList/CommentList.tsx +++ b/src/v4/social/internal-components/CommentList/CommentList.tsx @@ -1,11 +1,11 @@ import React, { memo } from 'react'; -import { useIntl } from 'react-intl'; -import { Comment } from '../Comment'; +import { Comment } from '~/v4/social/internal-components/Comment/'; import styles from './CommentList.module.css'; import { ExpandIcon } from '~/v4/social/icons'; import { LoadMoreWrapper } from '~/v4/core/components/LoadMoreWrapper/LoadMoreWrapper'; import useCommentsCollection from '~/v4/social/hooks/collections/useCommentsCollection'; +import { CommentBubbleDeleted } from '~/v4/social/elements/CommentBubbleDeleted'; interface CommentListProps { parentId?: string; @@ -39,13 +39,12 @@ export const CommentList = ({ includeDeleted, }); - const { formatMessage } = useIntl(); const isReplyComment = !!parentId; const commentCount = comments?.length; const loadMoreText = isReplyComment - ? formatMessage({ id: 'collapsible.viewMoreReplies' }, { count: commentCount }) - : formatMessage({ id: 'collapsible.viewMoreComments' }); + ? `View ${commentCount === 1 ? '1 reply' : `${commentCount} replies`}` + : 'View more comments'; const prependIcon = isReplyComment ? ( <div className={styles.tabIconContainer}> @@ -53,15 +52,13 @@ export const CommentList = ({ </div> ) : null; - if (comments?.length === 0 && referenceType === 'story') { - return ( - <div className={styles.noCommentsContainer}> - {formatMessage({ id: 'storyViewer.commentSheet.empty' })} - </div> - ); + if (comments.length === 0 && isReplyComment) { + return <CommentBubbleDeleted componentId="comment_tray_component" />; } - if (comments?.length === 0) return null; + if (comments?.length === 0) { + return <div className={styles.noCommentsContainer}>No comments yet</div>; + } const renderComments = () => { return comments.map((comment) => ( diff --git a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx index 29d744b36..86049a960 100644 --- a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx @@ -20,12 +20,13 @@ import clsx from 'clsx'; import { ArrowRightButton } from '~/v4/social/elements/ArrowRightButton/ArrowRightButton'; import styles from './StoryPage.module.css'; -import { Trash2Icon } from '~/icons'; + import useSDK from '~/v4/core/hooks/useSDK'; import { CustomRendererProps, RendererObject, } from '~/v4/social/internal-components/StoryViewer/Renderers/types'; +import { TrashIcon } from '~/v4/social/icons'; const DURATION = 5000; @@ -212,7 +213,7 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ name: 'delete', action: () => deleteStory(story?.storyId as string), icon: ( - <Trash2Icon + <TrashIcon fill={getComputedStyle(document.documentElement).getPropertyValue( '--asc-color-base-default', )} diff --git a/src/v4/social/pages/StoryPage/StoryPage.module.css b/src/v4/social/pages/StoryPage/StoryPage.module.css index 490050738..fd9282080 100644 --- a/src/v4/social/pages/StoryPage/StoryPage.module.css +++ b/src/v4/social/pages/StoryPage/StoryPage.module.css @@ -52,7 +52,7 @@ justify-content: center; align-items: center; width: 100%; - height: 100%; + height: 100vh; gap: 1rem; overflow: hidden; } From 0d890424f79c12bce40ea23e9b8c71fc7f7c787e Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Thu, 20 Jun 2024 21:33:38 +0700 Subject: [PATCH 150/300] fix: ASC-20356 - story tab should navigate to unseen (#419) * fix: story view page onClose * fix: to v4 * fix: overlay bug * fix: remove console.log * fix: remove styled component * fix: add story arrow left right to customization provider * fix: view story full width and height * fix: notification v4 * fix: delete first multiple stories go to next story * fix: story tab background color * fix: update navigate * fix: styled to css module * fix: remove console.log * fix: remove console.log * fix: story tab * fix: type * fix: remove console.log * fix: story tab type * fix: noti css * fix: story avatar * fix: remove unused * fix: remove unused * fix: share story button use amityElement * fix: icon * fix: add default communityProfileImageBackground * fix: story tab community * fix: story tab * fix: navigate * fix: story navigate * fix: remove unused * fix: red border story tab * fix: remove ts-sdk peer dep * fix: delete comment * fix: i18n * fix: add margin-bottom to container * fix: remove packagemanager * fix: story ring empty state color * fix: story tab should navigate to unseen * fix: type * fix: import and type * fix: type * fix: background image * fix: element * fix: community story tab condition * fix: remove unused * fix: create story button * fix: community story tab render condition * fix: delete community story condition * fix: discard create story navigate condition * fix: add create new story button * fix: export create new story button * chore: add ui story for create new story button * fix: share story button * fix: remove unused * fix: close button color * fix: remove unused * fix: remove unused function * fix: remove unused * fix: flicker render * fix: loading overlay width height * fix: draft page container * fix: remove inline function * fix: story wrapper height * fix: community story tab condition * fix: aspect ratio button to use useAmityElement * fix: elements * fix: remove inline function * fix: elements * fix: import and type * fix: remove error icon z-index * fix: official badge condition * fix: prop * fix: import * fix: community avatar * fix: story tab condition * fix: type * fix: remove console.log * fix: story tab condition * fix: remove i18n * fix: no comment word * fix: elements * fix: type * fix: add pageId to confirm --- .../CommunityInfo/UICommunityInfo.tsx | 2 +- src/social/components/StoryTab/StoryTab.tsx | 2 +- src/social/pages/DraftPage.tsx | 8 +- src/social/pages/ViewStoryPage.tsx | 26 +++- src/utils.ts | 9 -- .../CreatePostMenu/CreatePostMenu.tsx | 3 +- .../StoryTab/CreateNewStoryButton.tsx | 32 ---- .../components/StoryTab/StoryTabCommunity.tsx | 27 ++-- src/v4/social/components/StoryTab/styles.tsx | 80 ---------- .../AspectRatioButton.module.css | 14 +- .../AspectRatioButton/AspectRatioButton.tsx | 34 ++--- .../elements/BackButton/BackButton.module.css | 15 +- .../social/elements/BackButton/BackButton.tsx | 25 ++-- .../CommentBubbleDeleted.module.css | 8 +- .../CommentBubbleDeleted.tsx | 10 +- .../elements/CommentButton/CommentButton.tsx | 34 +++-- .../CommunityAvatar/CommunityAvatar.tsx | 13 +- .../CreateNewStoryButton.module.css | 7 + .../CreateNewStoryButton.tsx | 62 ++++++++ .../elements/CreateNewStoryButton/index.ts | 1 + .../CreateNewStoryButton/ui.stories.tsx | 15 ++ .../CreateStoryButton.module.css | 0 .../CreateStoryButton/CreateStoryButton.tsx | 101 +++++++------ .../elements/CreateStoryButton/index.ts | 1 - .../index.tsx | 0 .../elements/CreateStoryButton/styles.tsx | 34 ----- .../CreateStoryButtons/CreateStoryButton.tsx | 63 -------- .../HyperLinkButton.module.css | 3 + .../HyperLinkButton/HyperLinkButton.tsx | 40 ++++- .../ImpressionButton.module.css | 5 + .../ImpressionButton/ImpressionButton.tsx | 86 +++++++---- .../elements/ImpressionButton/styles.tsx | 21 --- .../social/elements/SaveButton/SaveButton.tsx | 68 ++++----- src/v4/social/elements/SaveButton/styles.tsx | 23 --- .../ShareStoryButton.module.css | 4 - .../ShareStoryButton/ShareStoryButton.tsx | 18 +-- .../SpeakerButton/SpeakerButton.module.css | 12 ++ .../elements/SpeakerButton/SpeakerButton.tsx | 139 +++++++++--------- .../social/elements/SpeakerButton/styles.tsx | 36 ----- .../StoryCommentButton.module.css | 27 ---- .../StoryCommentButton/StoryCommentButton.tsx | 63 -------- .../elements/StoryCommentButton/index.ts | 1 - .../social/elements/StoryRing/StoryRing.tsx | 1 - src/v4/social/elements/index.ts | 2 +- .../StoryViewer/Renderers/Image.tsx | 66 +++------ .../Renderers/Renderers.module.css | 2 + .../StoryViewer/Renderers/Video.tsx | 9 +- .../Wrappers/Footer/Footer.module.css | 13 ++ .../Renderers/Wrappers/Footer/index.tsx | 20 ++- .../Renderers/Wrappers/Footer/styles.tsx | 111 -------------- .../Wrappers/Header/Header.module.css | 2 +- .../Renderers/Wrappers/Header/index.tsx | 24 +-- .../pages/DraftsPage/DraftsPage.module.css | 2 +- src/v4/social/pages/DraftsPage/DraftsPage.tsx | 16 +- .../pages/StoryPage/CommunityFeedStory.tsx | 124 ++++++++-------- .../pages/StoryPage/GlobalFeedStory.tsx | 55 ++++--- .../pages/StoryPage/ViewGlobalFeedStory.tsx | 6 +- .../social/pages/StoryPage/ViewStoryPage.tsx | 7 + 58 files changed, 652 insertions(+), 980 deletions(-) delete mode 100644 src/v4/social/components/StoryTab/CreateNewStoryButton.tsx delete mode 100644 src/v4/social/components/StoryTab/styles.tsx create mode 100644 src/v4/social/elements/CreateNewStoryButton/CreateNewStoryButton.module.css create mode 100644 src/v4/social/elements/CreateNewStoryButton/CreateNewStoryButton.tsx create mode 100644 src/v4/social/elements/CreateNewStoryButton/index.ts create mode 100644 src/v4/social/elements/CreateNewStoryButton/ui.stories.tsx rename src/v4/social/elements/{CreateStoryButtons => CreateStoryButton}/CreateStoryButton.module.css (100%) delete mode 100644 src/v4/social/elements/CreateStoryButton/index.ts rename src/v4/social/elements/{CreateStoryButtons => CreateStoryButton}/index.tsx (100%) delete mode 100644 src/v4/social/elements/CreateStoryButton/styles.tsx delete mode 100644 src/v4/social/elements/CreateStoryButtons/CreateStoryButton.tsx create mode 100644 src/v4/social/elements/HyperLinkButton/HyperLinkButton.module.css create mode 100644 src/v4/social/elements/ImpressionButton/ImpressionButton.module.css delete mode 100644 src/v4/social/elements/ImpressionButton/styles.tsx delete mode 100644 src/v4/social/elements/SaveButton/styles.tsx create mode 100644 src/v4/social/elements/SpeakerButton/SpeakerButton.module.css delete mode 100644 src/v4/social/elements/SpeakerButton/styles.tsx delete mode 100644 src/v4/social/elements/StoryCommentButton/StoryCommentButton.module.css delete mode 100644 src/v4/social/elements/StoryCommentButton/StoryCommentButton.tsx delete mode 100644 src/v4/social/elements/StoryCommentButton/index.ts delete mode 100644 src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/styles.tsx diff --git a/src/social/components/CommunityInfo/UICommunityInfo.tsx b/src/social/components/CommunityInfo/UICommunityInfo.tsx index 6c02b40ae..6bb97a467 100644 --- a/src/social/components/CommunityInfo/UICommunityInfo.tsx +++ b/src/social/components/CommunityInfo/UICommunityInfo.tsx @@ -142,7 +142,7 @@ const UICommunityInfo = ({ </JoinButton> )} - {isJoined && <StoryTab type="communityFeed" communityId={communityId} />} + <StoryTab type="communityFeed" communityId={communityId} /> {isJoined && canEditCommunity && ( <Button diff --git a/src/social/components/StoryTab/StoryTab.tsx b/src/social/components/StoryTab/StoryTab.tsx index a9c0133c8..016e249cd 100644 --- a/src/social/components/StoryTab/StoryTab.tsx +++ b/src/social/components/StoryTab/StoryTab.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { useNavigation } from '~/social/providers/NavigationProvider'; - import { StoryTabCommunityFeed } from '~/v4/social/components/StoryTab/StoryTabCommunity'; import { StoryTabGlobalFeed } from '~/v4/social/components/StoryTab/StoryTabGlobalFeed'; import { useStoryContext } from '~/v4/social/providers/StoryProvider'; @@ -36,6 +35,7 @@ export const StoryTab = <T extends StoryTabType>({ type, communityId }: StoryTab file.type.includes('image') ? { type: 'image', url: URL.createObjectURL(file) } : { type: 'video', url: URL.createObjectURL(file) }, + 'communityFeed', ); } }} diff --git a/src/social/pages/DraftPage.tsx b/src/social/pages/DraftPage.tsx index 1c3fd5395..6fc187717 100644 --- a/src/social/pages/DraftPage.tsx +++ b/src/social/pages/DraftPage.tsx @@ -12,7 +12,13 @@ export const AmityDraftStoryPage = (props: AmityDraftStoryPageProps) => { return ( <PlainDraftStoryPage {...props} - onDiscardCreateStory={() => onChangePage(PageTypes.NewsFeed)} + onDiscardCreateStory={() => { + if (props.storyType === 'communityFeed') { + onClickCommunity(props.targetId); + } else { + onChangePage(PageTypes.NewsFeed); + } + }} goToCommunityPage={(communityId) => onClickCommunity(communityId)} goToGlobalFeedPage={() => onChangePage(PageTypes.NewsFeed)} storyType={props.storyType} diff --git a/src/social/pages/ViewStoryPage.tsx b/src/social/pages/ViewStoryPage.tsx index baa420fba..70d26edc6 100644 --- a/src/social/pages/ViewStoryPage.tsx +++ b/src/social/pages/ViewStoryPage.tsx @@ -15,25 +15,35 @@ const ViewStoryPage: React.FC<AmityViewStoryPageProps> = ({ type, targetId }) => const { onBack, goToDraftStoryPage, onClickCommunity, onChangePage, onClickStory } = useNavigation(); + const goToNewsFeed = () => { + onChangePage(PageTypes.NewsFeed); + }; + + const goToCommunity = (communityId: string) => { + onClickCommunity(communityId); + }; + if (type === 'communityFeed') return ( <CommunityFeedStory communityId={targetId} onBack={onBack} - onClose={(communityId) => onClickCommunity(communityId)} - onSwipeDown={(communityId) => onClickCommunity(communityId)} - onClickCommunity={(communityId) => onClickCommunity(communityId)} + onClose={goToCommunity} + onSwipeDown={goToCommunity} + onClickCommunity={goToCommunity} + goToDraftStoryPage={({ targetId, targetType, mediaType, storyType }) => + goToDraftStoryPage(targetId, targetType, mediaType, storyType) + } /> ); + if (type === 'globalFeed') return ( <ViewGlobalFeedStoryPage targetId={targetId} - onChangePage={() => onChangePage(PageTypes.NewsFeed)} - onClose={() => { - onChangePage(PageTypes.NewsFeed); - }} - onSwipeDown={() => onChangePage(PageTypes.NewsFeed)} + onChangePage={goToNewsFeed} + onClose={goToNewsFeed} + onSwipeDown={goToNewsFeed} onClickStory={(targetId) => onClickStory(targetId, 'globalFeed')} goToDraftStoryPage={({ targetId, targetType, mediaType, storyType }) => goToDraftStoryPage(targetId, targetType, mediaType, storyType) diff --git a/src/utils.ts b/src/utils.ts index 8dd500000..f334aebde 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -5,15 +5,6 @@ export function isLoadingItem<T>(item: T | { skeleton?: boolean }): item is { sk return !!(item as { skeleton?: boolean }).skeleton; } -export function isValidHttpUrl(url: string) { - try { - const newUrl = new URL(url); - return newUrl.protocol === 'http:' || newUrl.protocol === 'https:'; - } catch (err) { - return false; - } -} - export const checkStoryPermission = ( client: Amity.Client | null | undefined, communityId?: string, diff --git a/src/v4/social/components/CreatePostMenu/CreatePostMenu.tsx b/src/v4/social/components/CreatePostMenu/CreatePostMenu.tsx index d4ae2666c..19438a786 100644 --- a/src/v4/social/components/CreatePostMenu/CreatePostMenu.tsx +++ b/src/v4/social/components/CreatePostMenu/CreatePostMenu.tsx @@ -1,9 +1,10 @@ import React from 'react'; import styles from './CreatePostMenu.module.css'; import { CreatePostButton } from '~/v4/social/elements/CreatePostButton'; -import { CreateStoryButton } from '~/v4/social/elements/CreateStoryButtons'; + import { CreatePollButton } from '~/v4/social/elements/CreatePollButton/CreatePollButton'; import { CreateLivestreamButton } from '~/v4/social/elements/CreateLivestreamButton'; +import { CreateStoryButton } from '~/v4/social/elements/CreateStoryButton'; interface CreatePostMenuProps { pageId: string; diff --git a/src/v4/social/components/StoryTab/CreateNewStoryButton.tsx b/src/v4/social/components/StoryTab/CreateNewStoryButton.tsx deleted file mode 100644 index aa69c41f1..000000000 --- a/src/v4/social/components/StoryTab/CreateNewStoryButton.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react'; - -import { AddStoryButton } from './styles'; -import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; - -interface CreateNewStoryButtonProps { - pageId?: 'story_page'; - componentId?: '*'; - onClick?: (e: React.MouseEvent) => void; -} - -export const CreateNewStoryButton = ({ - pageId = 'story_page', - componentId = '*', - onClick = () => {}, -}: CreateNewStoryButtonProps) => { - const elementId = 'create_new_story_button'; - const { getConfig, isExcluded } = useCustomization(); - const elementConfig = getConfig(`${pageId}/${componentId}/${elementId}`); - const isElementExcluded = isExcluded(`${pageId}/${componentId}/${elementId}`); - - if (isElementExcluded) return null; - - return ( - <AddStoryButton - onClick={onClick} - style={{ - backgroundColor: elementConfig?.background_color, - }} - /> - ); -}; diff --git a/src/v4/social/components/StoryTab/StoryTabCommunity.tsx b/src/v4/social/components/StoryTab/StoryTabCommunity.tsx index 1f9a782a8..dce30cd72 100644 --- a/src/v4/social/components/StoryTab/StoryTabCommunity.tsx +++ b/src/v4/social/components/StoryTab/StoryTabCommunity.tsx @@ -74,9 +74,20 @@ export const StoryTabCommunityFeed: React.FC<StoryTabCommunityFeedProps> = ({ targetType: 'community', options: { orderBy: 'asc', sortBy: 'createdAt' }, }); - const { avatarFileUrl } = useCommunityInfo(communityId); + const { avatarFileUrl, community } = useCommunityInfo(communityId); const fileInputRef = useRef<HTMLInputElement>(null); + const { currentUserId, client } = useSDK(); + const { user } = useUser(currentUserId); + const isGlobalAdmin = isAdmin(user?.roles); + const isCommunityModerator = isModerator(user?.roles); + const hasStoryPermission = + isGlobalAdmin || isCommunityModerator || checkStoryPermission(client, communityId); + const hasStories = stories?.length > 0; + const hasUnSeen = stories.some((story) => !story?.isSeen); + const uploading = stories.some((story) => story?.syncState === 'syncing'); + const isErrored = stories.some((story) => story?.syncState === 'error'); + const handleAddIconClick = () => { if (fileInputRef.current) { fileInputRef.current.click(); @@ -95,22 +106,12 @@ export const StoryTabCommunityFeed: React.FC<StoryTabCommunityFeedProps> = ({ onStoryClick(); }; - const { currentUserId, client } = useSDK(); - const { user } = useUser(currentUserId); - const isGlobalAdmin = isAdmin(user?.roles); - const isCommunityModerator = isModerator(user?.roles); - const hasStoryPermission = - isGlobalAdmin || isCommunityModerator || checkStoryPermission(client, communityId); - - const hasStoryRing = stories?.length > 0; - const hasUnSeen = stories.some((story) => !story?.isSeen); - const uploading = stories.some((story) => story?.syncState === 'syncing'); - const isErrored = stories.some((story) => story?.syncState === 'error'); + if (!community?.isJoined && !hasStories) return null; return ( <div className={clsx(styles.storyTabContainer)}> <div className={clsx(styles.storyWrapper)}> - {hasStoryRing && ( + {hasStories && ( <StoryRing pageId={pageId} componentId={componentId} diff --git a/src/v4/social/components/StoryTab/styles.tsx b/src/v4/social/components/StoryTab/styles.tsx deleted file mode 100644 index 2b820c7ce..000000000 --- a/src/v4/social/components/StoryTab/styles.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import styled, { keyframes } from 'styled-components'; -import Avatar from '~/core/components/Avatar'; -import { AddIcon, ErrorIcon } from '~/icons'; - -export const ErrorButton = styled(ErrorIcon)` - position: absolute; - bottom: 0; - right: 0; - cursor: pointer; - z-index: 2; -`; - -export const AddStoryButton = styled(AddIcon)` - position: absolute; - bottom: 0; - right: 0; - cursor: pointer; - z-index: 2; -`; - -export const HiddenInput = styled.input` - display: none; -`; - -export const StoryWrapper = styled.div` - width: 3rem; - height: 3rem; - position: relative; - cursor: pointer; -`; - -export const StoryTabContainer = styled.div` - position: relative; - width: 3rem; - display: flex; - gap: 0.13rem; - flex-direction: column; - text-align: center; - padding: 1rem 0.75rem; - align-items: center; -`; - -export const StoryAvatar = styled(Avatar)` - width: 2.5rem; - height: 2.5rem; - position: absolute; - top: 0.25rem; - left: 0.25rem; - z-index: 1; - cursor: pointer; -`; - -export const StoryTitle = styled.div` - ${({ theme }) => theme.typography.caption}; - color: ${({ theme }) => theme.palette.base.main}; - cursor: pointer; -`; - -export const AddButton = styled(AddIcon)` - position: absolute; - bottom: 0; - right: 0; - cursor: pointer; - z-index: 2; -`; - -const animateRing = keyframes` - 0% { - stroke-dashoffset: 339; - } - 100% { - stroke-dashoffset: 0; - } -`; - -export const ProgressRing = styled.circle<{ uploading?: boolean }>` - animation: ${(props) => (props.uploading ? animateRing : 'none')} 2s linear 0s infinite; - -webkit-animation: ${(props) => (props.uploading ? animateRing : 'none')} 2s linear 0s infinite; - -moz-animation: ${(props) => (props.uploading ? animateRing : 'none')} 2s linear 0s infinite; -`; diff --git a/src/v4/social/elements/AspectRatioButton/AspectRatioButton.module.css b/src/v4/social/elements/AspectRatioButton/AspectRatioButton.module.css index 9da6d9166..b82f214ed 100644 --- a/src/v4/social/elements/AspectRatioButton/AspectRatioButton.module.css +++ b/src/v4/social/elements/AspectRatioButton/AspectRatioButton.module.css @@ -1,17 +1,5 @@ .aspectRatioButton { - width: 2rem; - height: 2rem; - display: flex; - align-items: center; - justify-content: center; - border: none; - background-color: rgb(0 0 0 / 50%); - color: var(--asc-color-white); - cursor: pointer; - padding: 0.1875rem 0; - border-radius: 50%; - transition: background-color 0.3s; - flex-shrink: 0; + fill: var(--asc-color-white); } .aspectRatioButton__icon { diff --git a/src/v4/social/elements/AspectRatioButton/AspectRatioButton.tsx b/src/v4/social/elements/AspectRatioButton/AspectRatioButton.tsx index 92de404a8..98bed4d63 100644 --- a/src/v4/social/elements/AspectRatioButton/AspectRatioButton.tsx +++ b/src/v4/social/elements/AspectRatioButton/AspectRatioButton.tsx @@ -1,21 +1,16 @@ import React from 'react'; import clsx from 'clsx'; import { IconComponent } from '~/v4/core/IconComponent'; -import styles from './AspectRatioButton.module.css'; import { useAmityElement } from '~/v4/core/hooks/uikit'; +import styles from './AspectRatioButton.module.css'; + const AspectRatioSvg = (props: React.SVGProps<SVGSVGElement>) => ( - <svg - xmlns="http://www.w3.org/2000/svg" - width="24" - height="24" - fill="none" - viewBox="0 0 24 24" - {...props} - > + <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none" viewBox="0 0 32 32"> + <circle cx="16" cy="16" r="16" fill="#000" fillOpacity="0.5"></circle> <path fill="currentColor" - d="M4.125 9.578v-4.36c0-.456.352-.843.844-.843h4.36c.21 0 .421.21.421.422v.844c0 .246-.21.421-.422.421H5.813v3.516c0 .246-.211.422-.422.422h-.844a.406.406 0 01-.422-.422zM14.25 4.797c0-.211.176-.422.422-.422h4.36c.456 0 .843.387.843.844v4.36c0 .245-.21.421-.422.421h-.844a.406.406 0 01-.422-.422V6.063h-3.515a.406.406 0 01-.422-.422v-.844zm5.203 9.703c.211 0 .422.21.422.422v4.36a.833.833 0 01-.844.843h-4.36a.406.406 0 01-.421-.422v-.844c0-.21.176-.422.422-.422h3.515v-3.515c0-.211.176-.422.422-.422h.844zM9.75 19.703c0 .246-.21.422-.422.422h-4.36c-.491 0-.843-.352-.843-.844v-4.36c0-.21.176-.421.422-.421h.844c.21 0 .421.21.421.422v3.515h3.516c.211 0 .422.211.422.422v.844z" + d="M8.125 13.578v-4.36c0-.456.352-.843.844-.843h4.36c.21 0 .421.21.421.422v.844c0 .246-.21.421-.422.421H9.813v3.516c0 .246-.211.422-.422.422h-.844a.406.406 0 01-.422-.422zM18.25 8.797c0-.211.176-.422.422-.422h4.36c.456 0 .843.387.843.844v4.36c0 .245-.21.421-.422.421h-.844a.406.406 0 01-.422-.422v-3.515h-3.515a.406.406 0 01-.422-.422v-.844zm5.203 9.703c.211 0 .422.21.422.422v4.36a.833.833 0 01-.844.843h-4.36a.406.406 0 01-.421-.422v-.844c0-.21.176-.422.422-.422h3.515v-3.515c0-.211.176-.422.422-.422h.844zm-9.703 5.203c0 .246-.21.422-.422.422h-4.36c-.491 0-.843-.352-.843-.844v-4.36c0-.21.176-.421.422-.421h.844c.21 0 .421.21.421.422v3.515h3.516c.211 0 .422.211.422.422v.844z" ></path> </svg> ); @@ -45,15 +40,14 @@ export function AspectRatioButton({ if (isExcluded) return null; return ( - <button onClick={onClick} className={styles.aspectRatioButton} data-qa-anchor={accessibilityId}> - <IconComponent - defaultIcon={() => ( - <AspectRatioSvg className={clsx(styles.aspectRatioButton__icon, defaultIconClassName)} /> - )} - imgIcon={() => <img src={config.icon} alt={uiReference} className={imgIconClassName} />} - defaultIconName={defaultConfig.icon} - configIconName={config.icon} - /> - </button> + <IconComponent + data-qa-anchor={accessibilityId} + className={clsx(styles.aspectRatioButton)} + onClick={onClick} + defaultIcon={() => <AspectRatioSvg className={clsx(defaultIconClassName)} />} + imgIcon={() => <img src={config.icon} alt={uiReference} className={clsx(imgIconClassName)} />} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} + /> ); } diff --git a/src/v4/social/elements/BackButton/BackButton.module.css b/src/v4/social/elements/BackButton/BackButton.module.css index c27a31ba8..1f04120c6 100644 --- a/src/v4/social/elements/BackButton/BackButton.module.css +++ b/src/v4/social/elements/BackButton/BackButton.module.css @@ -1,16 +1,3 @@ -button { - appearance: none; - border-radius: 0; - text-align: inherit; - background: none; - box-shadow: none; - padding: 0; - cursor: pointer; - border: none; - color: inherit; - font: inherit; -} - .backButton { - fill: var(--asc-color-base-default); + fill: var(--asc-color-white); } diff --git a/src/v4/social/elements/BackButton/BackButton.tsx b/src/v4/social/elements/BackButton/BackButton.tsx index 4ad522ed0..888d0b6f3 100644 --- a/src/v4/social/elements/BackButton/BackButton.tsx +++ b/src/v4/social/elements/BackButton/BackButton.tsx @@ -1,19 +1,24 @@ import React from 'react'; -import { getDefaultConfig, useCustomization } from '~/v4/core/providers/CustomizationProvider'; import { IconComponent } from '~/v4/core/IconComponent'; -import styles from './BackButton.module.css'; import { useAmityElement } from '~/v4/core/hooks/uikit'; +import clsx from 'clsx'; + +import styles from './BackButton.module.css'; const BackButtonSvg = (props: React.SVGProps<SVGSVGElement>) => ( <svg - width="10" - height="17" - viewBox="0 0 10 17" - fill="none" xmlns="http://www.w3.org/2000/svg" + width="32" + height="32" + fill="none" + viewBox="0 0 32 32" {...props} > - <path d="M8.62109 15.9141C8.44531 16.0898 8.19922 16.0898 8.02344 15.9141L0.640625 8.56641C0.5 8.39062 0.5 8.14453 0.640625 7.96875L8.02344 0.621094C8.19922 0.445312 8.44531 0.445312 8.62109 0.621094L9.32422 1.28906C9.46484 1.46484 9.46484 1.74609 9.32422 1.88672L2.96094 8.25L9.32422 14.6484C9.46484 14.7891 9.46484 15.0703 9.32422 15.2461L8.62109 15.9141Z" /> + <circle cx="16" cy="16" r="16" fill="#000" fillOpacity="0.5"></circle> + <path + fill="currentColor" + d="M16.176 23.914c-.14.176-.422.176-.598 0L8.23 16.566a.405.405 0 010-.597l7.348-7.348c.176-.176.457-.176.598 0l.703.668c.176.176.176.457 0 .598l-5.45 5.449h12.024c.211 0 .422.21.422.422v.984c0 .246-.21.422-.422.422H11.43l5.449 5.484c.176.141.176.422 0 .598l-.703.668z" + ></path> </svg> ); @@ -45,9 +50,9 @@ export const BackButton = ({ return ( <IconComponent data-qa-anchor={accessibilityId} - className={styles.backButton} - defaultIcon={() => <BackButtonSvg className={defaultClassName} />} - imgIcon={() => <img src={config.icon} alt={uiReference} className={imgClassName} />} + className={clsx(styles.backButton, defaultClassName)} + defaultIcon={() => <BackButtonSvg />} + imgIcon={() => <img src={config.icon} alt={uiReference} className={clsx(imgClassName)} />} defaultIconName={defaultConfig.icon} configIconName={config.icon} onClick={onClick} diff --git a/src/v4/social/elements/CommentBubbleDeleted/CommentBubbleDeleted.module.css b/src/v4/social/elements/CommentBubbleDeleted/CommentBubbleDeleted.module.css index 12418468d..672ee479f 100644 --- a/src/v4/social/elements/CommentBubbleDeleted/CommentBubbleDeleted.module.css +++ b/src/v4/social/elements/CommentBubbleDeleted/CommentBubbleDeleted.module.css @@ -1,4 +1,8 @@ -.commentBubbleDeleted__container { +.commentBubbleDeletedBlock { + cursor: auto; +} + +.commentBubbleDeleted { margin-left: 3.25rem; display: inline-flex; padding: 0.3125rem 0.75rem 0.3125rem 0.5rem; @@ -11,7 +15,7 @@ margin-bottom: var(--asc-spacing-m1); } -.commentBubbleDeleted__icon { +.commentBubbleDeletedIcon { cursor: auto; width: 1rem; height: 1rem; diff --git a/src/v4/social/elements/CommentBubbleDeleted/CommentBubbleDeleted.tsx b/src/v4/social/elements/CommentBubbleDeleted/CommentBubbleDeleted.tsx index 0808742f1..0feb50794 100644 --- a/src/v4/social/elements/CommentBubbleDeleted/CommentBubbleDeleted.tsx +++ b/src/v4/social/elements/CommentBubbleDeleted/CommentBubbleDeleted.tsx @@ -43,15 +43,19 @@ export function CommentBubbleDeleted({ return ( <IconComponent + className={clsx(styles.commentBubbleDeletedBlock)} defaultIcon={() => ( - <div className={styles.commentBubbleDeleted__container} data-qa-anchor={accessibilityId}> + <div + className={clsx(styles.commentBubbleDeleted, defaultIconClassName)} + data-qa-anchor={accessibilityId} + > <CommentBubbleDeletedSvg - className={clsx(styles.commentBubbleDeleted__icon, defaultIconClassName)} + className={clsx(styles.commentBubbleDeletedIcon, defaultIconClassName)} /> <Typography.Caption>{config.text}</Typography.Caption> </div> )} - imgIcon={() => <img src={config.icon} alt={uiReference} className={imgIconClassName} />} + imgIcon={() => <img src={config.icon} alt={uiReference} className={clsx(imgIconClassName)} />} defaultIconName={defaultConfig.icon} configIconName={config.icon} /> diff --git a/src/v4/social/elements/CommentButton/CommentButton.tsx b/src/v4/social/elements/CommentButton/CommentButton.tsx index 06e48cccf..66b3d1796 100644 --- a/src/v4/social/elements/CommentButton/CommentButton.tsx +++ b/src/v4/social/elements/CommentButton/CommentButton.tsx @@ -2,9 +2,10 @@ import React from 'react'; import clsx from 'clsx'; import { Typography } from '~/v4/core/components'; import { IconComponent } from '~/v4/core/IconComponent'; -import styles from './CommentButton.module.css'; import { useAmityElement } from '~/v4/core/hooks/uikit'; +import styles from './CommentButton.module.css'; + const CommentSvg = (props: React.SVGProps<SVGSVGElement>) => ( <svg width="18" @@ -22,16 +23,20 @@ interface CommentButtonProps { pageId?: string; componentId?: string; commentsCount?: number; + className?: string; defaultIconClassName?: string; imgIconClassName?: string; + onClick?: (e: React.MouseEvent) => void; } export function CommentButton({ pageId = '*', componentId = '*', commentsCount, + className = '', defaultIconClassName, imgIconClassName, + onClick = () => {}, }: CommentButtonProps) { const elementId = 'comment_button'; const { accessibilityId, config, defaultConfig, isExcluded, uiReference } = useAmityElement({ @@ -43,18 +48,21 @@ export function CommentButton({ if (isExcluded) return null; return ( - <div className={styles.commentButton} data-qa-anchor={accessibilityId}> - <IconComponent - defaultIcon={() => ( + <IconComponent + className={clsx(className)} + data-qa-anchor={accessibilityId} + onClick={onClick} + defaultIcon={() => ( + <div className={clsx(styles.commentButton)}> <CommentSvg className={clsx(styles.commentButton__icon, defaultIconClassName)} /> - )} - imgIcon={() => <img src={config.icon} alt={uiReference} className={imgIconClassName} />} - defaultIconName={defaultConfig.icon} - configIconName={config.icon} - /> - <Typography.BodyBold className={styles.commentButton__text}> - {typeof commentsCount === 'number' ? commentsCount : config.text} - </Typography.BodyBold> - </div> + <Typography.BodyBold className={styles.commentButton__text}> + {typeof commentsCount === 'number' ? commentsCount : config.text} + </Typography.BodyBold> + </div> + )} + imgIcon={() => <img src={config.icon} alt={uiReference} className={imgIconClassName} />} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} + /> ); } diff --git a/src/v4/social/elements/CommunityAvatar/CommunityAvatar.tsx b/src/v4/social/elements/CommunityAvatar/CommunityAvatar.tsx index 170fd2300..6c1015b50 100644 --- a/src/v4/social/elements/CommunityAvatar/CommunityAvatar.tsx +++ b/src/v4/social/elements/CommunityAvatar/CommunityAvatar.tsx @@ -24,7 +24,7 @@ const CommunityAvatarSvg = (props: React.SVGProps<SVGSVGElement>) => ( export interface CommunityAvatarProps { pageId?: string; componentId?: string; - community: Amity.Community; + community?: Amity.Community | null; } export function CommunityAvatar({ @@ -33,12 +33,11 @@ export function CommunityAvatar({ community, }: CommunityAvatarProps) { const elementId = 'community_avatar'; - const { accessibilityId, isExcluded, themeStyles } = - useAmityElement({ - pageId, - componentId, - elementId, - }); + const { accessibilityId, isExcluded, themeStyles } = useAmityElement({ + pageId, + componentId, + elementId, + }); const avatarFile = useImage({ fileId: community?.avatarFileId }); diff --git a/src/v4/social/elements/CreateNewStoryButton/CreateNewStoryButton.module.css b/src/v4/social/elements/CreateNewStoryButton/CreateNewStoryButton.module.css new file mode 100644 index 000000000..b4c87e34a --- /dev/null +++ b/src/v4/social/elements/CreateNewStoryButton/CreateNewStoryButton.module.css @@ -0,0 +1,7 @@ +.createNewStoryIcon { + position: absolute; + bottom: 0; + right: 0; + width: 1rem; + height: 1rem; +} diff --git a/src/v4/social/elements/CreateNewStoryButton/CreateNewStoryButton.tsx b/src/v4/social/elements/CreateNewStoryButton/CreateNewStoryButton.tsx new file mode 100644 index 000000000..40db14d9f --- /dev/null +++ b/src/v4/social/elements/CreateNewStoryButton/CreateNewStoryButton.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import { IconComponent } from '~/v4/core/IconComponent'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import clsx from 'clsx'; + +import styles from './CreateNewStoryButton.module.css'; + +const CreateNewStoryButtonSvg = (props: React.SVGProps<SVGSVGElement>) => ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="16" + height="16" + fill="none" + viewBox="0 0 16 16" + {...props} + > + <circle cx="8" cy="8" r="7.25" fill="#1054DE" stroke="#fff" strokeWidth="1.5"></circle> + <path + fill="#fff" + d="M11.438 7.625c.156 0 .312.156.312.313v.625a.321.321 0 01-.313.312H8.626v2.813a.321.321 0 01-.313.312h-.624a.308.308 0 01-.313-.313V8.876H4.562a.309.309 0 01-.312-.313v-.624c0-.157.137-.313.313-.313h2.812V4.812c0-.156.137-.312.313-.312h.625c.156 0 .312.156.312.313v2.812h2.813z" + ></path> + </svg> +); + +interface BackButtonProps { + pageId?: string; + componentId?: string; + defaultClassName?: string; + imgClassName?: string; + onClick?: (e: React.MouseEvent) => void; +} + +export const CreateNewStoryButton = ({ + pageId = '*', + componentId = '*', + defaultClassName, + imgClassName, + onClick = () => {}, +}: BackButtonProps) => { + const elementId = 'create_new_story_button'; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + return ( + <IconComponent + data-qa-anchor={accessibilityId} + className={clsx(styles.createNewStoryIcon, defaultClassName)} + defaultIcon={() => <CreateNewStoryButtonSvg />} + imgIcon={() => <img src={config.icon} alt={uiReference} className={clsx(imgClassName)} />} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} + onClick={onClick} + style={themeStyles} + /> + ); +}; diff --git a/src/v4/social/elements/CreateNewStoryButton/index.ts b/src/v4/social/elements/CreateNewStoryButton/index.ts new file mode 100644 index 000000000..c7f273b91 --- /dev/null +++ b/src/v4/social/elements/CreateNewStoryButton/index.ts @@ -0,0 +1 @@ +export { CreateNewStoryButton } from './CreateNewStoryButton'; diff --git a/src/v4/social/elements/CreateNewStoryButton/ui.stories.tsx b/src/v4/social/elements/CreateNewStoryButton/ui.stories.tsx new file mode 100644 index 000000000..2f77fffe7 --- /dev/null +++ b/src/v4/social/elements/CreateNewStoryButton/ui.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { CreateNewStoryButton } from './CreateNewStoryButton'; + +export default { + title: 'v4-social/elements/CreateNewStoryButton', +}; + +export const ClearButtonStory = { + render: () => { + return <CreateNewStoryButton />; + }, + + name: 'CreateNewStoryButton', +}; diff --git a/src/v4/social/elements/CreateStoryButtons/CreateStoryButton.module.css b/src/v4/social/elements/CreateStoryButton/CreateStoryButton.module.css similarity index 100% rename from src/v4/social/elements/CreateStoryButtons/CreateStoryButton.module.css rename to src/v4/social/elements/CreateStoryButton/CreateStoryButton.module.css diff --git a/src/v4/social/elements/CreateStoryButton/CreateStoryButton.tsx b/src/v4/social/elements/CreateStoryButton/CreateStoryButton.tsx index 27bc6c43e..1078a7a76 100644 --- a/src/v4/social/elements/CreateStoryButton/CreateStoryButton.tsx +++ b/src/v4/social/elements/CreateStoryButton/CreateStoryButton.tsx @@ -1,56 +1,63 @@ import React from 'react'; +import { IconComponent } from '~/v4/core/IconComponent'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { Typography } from '~/v4/core/components'; +import styles from './CreateStoryButton.module.css'; +import clsx from 'clsx'; -import { IconButton, RemoteImageButton } from './styles'; -import { isValidHttpUrl } from '~/utils'; -import { useTheme } from 'styled-components'; -import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; +const CreateStoryButtonSvg = (props: React.SVGProps<SVGSVGElement>) => ( + <svg width="17" height="18" viewBox="0 0 17 18" xmlns="http://www.w3.org/2000/svg" {...props}> + <path + fillRule="evenodd" + clipRule="evenodd" + d="M0.771159 12.2248C1.00906 12.7861 1.30474 13.3088 1.65819 13.7927C1.85468 14.0618 2.2428 14.0749 2.47778 13.8387L2.85238 13.4622C3.05585 13.2578 3.07242 12.9352 2.91359 12.6944C2.68511 12.3479 2.4918 11.9785 2.33366 11.5862C2.17162 11.1841 2.04651 10.7654 1.95831 10.33C1.90156 10.0497 1.66209 9.83785 1.37619 9.83785H0.7981C0.460143 9.83785 0.194237 10.1301 0.252477 10.463C0.359316 11.0737 0.53221 11.661 0.771159 12.2248ZM0.791992 5.77587C0.565717 6.33406 0.392758 6.91522 0.273115 7.51935C0.206005 7.85821 0.473794 8.1628 0.819238 8.1628H1.37619C1.66209 8.1628 1.90156 7.95091 1.95831 7.6707C2.04651 7.23526 2.17162 6.81652 2.33366 6.41448C2.4918 6.0221 2.68511 5.6527 2.91359 5.30628C3.07242 5.06546 3.05585 4.7429 2.85238 4.53841L2.47778 4.16192C2.2428 3.92576 1.85456 3.93884 1.65906 4.2086C1.30847 4.69235 1.01944 5.21477 0.791992 5.77587ZM5.26074 16.7265C5.81526 16.966 6.39491 17.1396 6.9997 17.247C7.33267 17.3061 7.62533 17.0401 7.62533 16.7019V16.1571C7.62533 15.8715 7.41372 15.6325 7.13436 15.5731C6.70488 15.4817 6.29562 15.3497 5.90658 15.177C5.52127 15.0061 5.14859 14.8025 4.78852 14.5662C4.5475 14.4081 4.22544 14.4327 4.02574 14.6406L3.64108 15.041C3.40868 15.2829 3.43286 15.6735 3.7084 15.8649C4.19354 16.2017 4.71098 16.4889 5.26074 16.7265ZM3.71752 2.12947C3.43846 2.32272 3.41731 2.71924 3.65673 2.95986L4.05715 3.36229C4.26214 3.56831 4.58771 3.58533 4.82847 3.42255C5.1704 3.19137 5.53324 2.99173 5.91699 2.82361C6.3137 2.64981 6.73277 2.5172 7.17418 2.42577C7.45449 2.3677 7.66699 2.12833 7.66699 1.84207V1.29669C7.66699 0.959243 7.37557 0.693429 7.04294 0.750218C6.41529 0.857371 5.82122 1.03203 5.26074 1.27419C4.71443 1.51024 4.20002 1.79533 3.71752 2.12947ZM9.82271 15.5735C9.54395 15.6315 9.33366 15.8703 9.33366 16.155V16.6953C9.33366 17.0359 9.63023 17.3024 9.96541 17.2422C11.8264 16.908 13.4005 16.0311 14.6878 14.6117C16.1184 13.0344 16.8337 11.1639 16.8337 9.00033C16.8337 6.83673 16.1184 4.96627 14.6878 3.38894C13.4005 1.96952 11.8264 1.09268 9.96541 0.758409C9.63023 0.698204 9.33366 0.964784 9.33366 1.30532V1.84562C9.33366 2.13035 9.54395 2.36916 9.82271 2.42716C11.2862 2.73166 12.5155 3.45008 13.5107 4.5824C14.6149 5.83868 15.167 7.31132 15.167 9.00033C15.167 10.6893 14.6149 12.162 13.5107 13.4182C12.5155 14.5506 11.2862 15.269 9.82271 15.5735Z" + /> + <path d="M12.7189 8.47933C12.9012 8.47933 13.0835 8.66162 13.0835 8.84391V9.57308C13.0835 9.77816 12.9012 9.93766 12.7189 9.93766H9.43766V13.2189C9.43766 13.424 9.25537 13.5835 9.07308 13.5835H8.34391C8.13883 13.5835 7.97933 13.424 7.97933 13.2189V9.93766H4.69808C4.493 9.93766 4.3335 9.77816 4.3335 9.57308V8.84391C4.3335 8.66162 4.493 8.47933 4.69808 8.47933H7.97933V5.19808C7.97933 5.01579 8.13883 4.8335 8.34391 4.8335H9.07308C9.25537 4.8335 9.43766 5.01579 9.43766 5.19808V8.47933H12.7189Z" /> + </svg> +); -interface BackButtonProps { - pageId: 'story_page'; - componentId: '*'; +interface CreateStoryButtonProps { + pageId?: string; + componentId: string; onClick?: (e: React.MouseEvent) => void; - style?: React.CSSProperties; - 'data-qa-anchor'?: string; + defaultClassName?: string; } -export const CreateStoryButton = ({ - pageId = 'story_page', +export function CreateStoryButton({ + pageId = '*', componentId = '*', - onClick = () => {}, - style, -}: BackButtonProps) => { - const theme = useTheme(); - const elementId = 'create_new_story_button'; - const { getConfig, isExcluded } = useCustomization(); - const elementConfig = getConfig(`${pageId}/${componentId}/${elementId}`); - const backgroundColor = elementConfig?.background_color; - const createStoryIcon = elementConfig?.create_new_story_icon; - - const isElementExcluded = isExcluded(`${pageId}/${componentId}/${elementId}`); - - if (isElementExcluded) return null; - - const isRemoteImage = createStoryIcon && isValidHttpUrl(createStoryIcon); + onClick, + defaultClassName, +}: CreateStoryButtonProps) { + const elementId = 'create_story_button'; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityElement({ + pageId, + componentId, + elementId, + }); - return isRemoteImage ? ( - <RemoteImageButton - data-qa-anchor="create_story_icon" - src={createStoryIcon} - onClick={onClick} - style={{ - ...style, - backgroundColor: backgroundColor || theme.v4.colors.secondary.default, - }} - /> - ) : ( - <IconButton - data-qa-anchor="create_story_icon" - name={'AddIcon'} - style={{ - ...style, - backgroundColor: backgroundColor || theme.v4.colors.secondary.default, - }} - onClick={onClick} - /> + if (isExcluded) return null; + return ( + <div + className={styles.createStoryButton} + onClick={() => {}} //TODO : Add event create story + data-qa-anchor={accessibilityId} + style={themeStyles} + > + <IconComponent + defaultIcon={() => ( + <CreateStoryButtonSvg + className={clsx(styles.createStoryButton__icon, defaultClassName)} + /> + )} + imgIcon={() => <img src={config.image} alt={uiReference} />} + configIconName={config.image} + defaultIconName={defaultConfig.image} + /> + <Typography.Body className={styles.createStoryButton__text}>{config.text}</Typography.Body> + </div> ); -}; +} + +export default CreateStoryButton; diff --git a/src/v4/social/elements/CreateStoryButton/index.ts b/src/v4/social/elements/CreateStoryButton/index.ts deleted file mode 100644 index 701322549..000000000 --- a/src/v4/social/elements/CreateStoryButton/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { CreateStoryButton } from './CreateStoryButton'; diff --git a/src/v4/social/elements/CreateStoryButtons/index.tsx b/src/v4/social/elements/CreateStoryButton/index.tsx similarity index 100% rename from src/v4/social/elements/CreateStoryButtons/index.tsx rename to src/v4/social/elements/CreateStoryButton/index.tsx diff --git a/src/v4/social/elements/CreateStoryButton/styles.tsx b/src/v4/social/elements/CreateStoryButton/styles.tsx deleted file mode 100644 index 31fc6eeeb..000000000 --- a/src/v4/social/elements/CreateStoryButton/styles.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import styled from 'styled-components'; -import { Icon } from '~/v4/core/components'; - -export const IconButton = styled(Icon)` - width: 1rem; - height: 1rem; - position: absolute; - bottom: 0rem; - right: 0rem; - cursor: pointer; - border-radius: 50%; - z-index: 100; -`; - -export const RemoteImageButton = styled.img` - width: 1.5rem; - height: 1.5rem; - cursor: pointer; - border: none; - outline: none; - padding: 0; - margin: 0; - display: flex; - align-items: center; - justify-content: center; - - &:hover { - opacity: 0.8; - } - - &:active { - opacity: 0.6; - } -`; diff --git a/src/v4/social/elements/CreateStoryButtons/CreateStoryButton.tsx b/src/v4/social/elements/CreateStoryButtons/CreateStoryButton.tsx deleted file mode 100644 index 1078a7a76..000000000 --- a/src/v4/social/elements/CreateStoryButtons/CreateStoryButton.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import React from 'react'; -import { IconComponent } from '~/v4/core/IconComponent'; -import { useAmityElement } from '~/v4/core/hooks/uikit'; -import { Typography } from '~/v4/core/components'; -import styles from './CreateStoryButton.module.css'; -import clsx from 'clsx'; - -const CreateStoryButtonSvg = (props: React.SVGProps<SVGSVGElement>) => ( - <svg width="17" height="18" viewBox="0 0 17 18" xmlns="http://www.w3.org/2000/svg" {...props}> - <path - fillRule="evenodd" - clipRule="evenodd" - d="M0.771159 12.2248C1.00906 12.7861 1.30474 13.3088 1.65819 13.7927C1.85468 14.0618 2.2428 14.0749 2.47778 13.8387L2.85238 13.4622C3.05585 13.2578 3.07242 12.9352 2.91359 12.6944C2.68511 12.3479 2.4918 11.9785 2.33366 11.5862C2.17162 11.1841 2.04651 10.7654 1.95831 10.33C1.90156 10.0497 1.66209 9.83785 1.37619 9.83785H0.7981C0.460143 9.83785 0.194237 10.1301 0.252477 10.463C0.359316 11.0737 0.53221 11.661 0.771159 12.2248ZM0.791992 5.77587C0.565717 6.33406 0.392758 6.91522 0.273115 7.51935C0.206005 7.85821 0.473794 8.1628 0.819238 8.1628H1.37619C1.66209 8.1628 1.90156 7.95091 1.95831 7.6707C2.04651 7.23526 2.17162 6.81652 2.33366 6.41448C2.4918 6.0221 2.68511 5.6527 2.91359 5.30628C3.07242 5.06546 3.05585 4.7429 2.85238 4.53841L2.47778 4.16192C2.2428 3.92576 1.85456 3.93884 1.65906 4.2086C1.30847 4.69235 1.01944 5.21477 0.791992 5.77587ZM5.26074 16.7265C5.81526 16.966 6.39491 17.1396 6.9997 17.247C7.33267 17.3061 7.62533 17.0401 7.62533 16.7019V16.1571C7.62533 15.8715 7.41372 15.6325 7.13436 15.5731C6.70488 15.4817 6.29562 15.3497 5.90658 15.177C5.52127 15.0061 5.14859 14.8025 4.78852 14.5662C4.5475 14.4081 4.22544 14.4327 4.02574 14.6406L3.64108 15.041C3.40868 15.2829 3.43286 15.6735 3.7084 15.8649C4.19354 16.2017 4.71098 16.4889 5.26074 16.7265ZM3.71752 2.12947C3.43846 2.32272 3.41731 2.71924 3.65673 2.95986L4.05715 3.36229C4.26214 3.56831 4.58771 3.58533 4.82847 3.42255C5.1704 3.19137 5.53324 2.99173 5.91699 2.82361C6.3137 2.64981 6.73277 2.5172 7.17418 2.42577C7.45449 2.3677 7.66699 2.12833 7.66699 1.84207V1.29669C7.66699 0.959243 7.37557 0.693429 7.04294 0.750218C6.41529 0.857371 5.82122 1.03203 5.26074 1.27419C4.71443 1.51024 4.20002 1.79533 3.71752 2.12947ZM9.82271 15.5735C9.54395 15.6315 9.33366 15.8703 9.33366 16.155V16.6953C9.33366 17.0359 9.63023 17.3024 9.96541 17.2422C11.8264 16.908 13.4005 16.0311 14.6878 14.6117C16.1184 13.0344 16.8337 11.1639 16.8337 9.00033C16.8337 6.83673 16.1184 4.96627 14.6878 3.38894C13.4005 1.96952 11.8264 1.09268 9.96541 0.758409C9.63023 0.698204 9.33366 0.964784 9.33366 1.30532V1.84562C9.33366 2.13035 9.54395 2.36916 9.82271 2.42716C11.2862 2.73166 12.5155 3.45008 13.5107 4.5824C14.6149 5.83868 15.167 7.31132 15.167 9.00033C15.167 10.6893 14.6149 12.162 13.5107 13.4182C12.5155 14.5506 11.2862 15.269 9.82271 15.5735Z" - /> - <path d="M12.7189 8.47933C12.9012 8.47933 13.0835 8.66162 13.0835 8.84391V9.57308C13.0835 9.77816 12.9012 9.93766 12.7189 9.93766H9.43766V13.2189C9.43766 13.424 9.25537 13.5835 9.07308 13.5835H8.34391C8.13883 13.5835 7.97933 13.424 7.97933 13.2189V9.93766H4.69808C4.493 9.93766 4.3335 9.77816 4.3335 9.57308V8.84391C4.3335 8.66162 4.493 8.47933 4.69808 8.47933H7.97933V5.19808C7.97933 5.01579 8.13883 4.8335 8.34391 4.8335H9.07308C9.25537 4.8335 9.43766 5.01579 9.43766 5.19808V8.47933H12.7189Z" /> - </svg> -); - -interface CreateStoryButtonProps { - pageId?: string; - componentId: string; - onClick?: (e: React.MouseEvent) => void; - defaultClassName?: string; -} - -export function CreateStoryButton({ - pageId = '*', - componentId = '*', - onClick, - defaultClassName, -}: CreateStoryButtonProps) { - const elementId = 'create_story_button'; - const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = - useAmityElement({ - pageId, - componentId, - elementId, - }); - - if (isExcluded) return null; - return ( - <div - className={styles.createStoryButton} - onClick={() => {}} //TODO : Add event create story - data-qa-anchor={accessibilityId} - style={themeStyles} - > - <IconComponent - defaultIcon={() => ( - <CreateStoryButtonSvg - className={clsx(styles.createStoryButton__icon, defaultClassName)} - /> - )} - imgIcon={() => <img src={config.image} alt={uiReference} />} - configIconName={config.image} - defaultIconName={defaultConfig.image} - /> - <Typography.Body className={styles.createStoryButton__text}>{config.text}</Typography.Body> - </div> - ); -} - -export default CreateStoryButton; diff --git a/src/v4/social/elements/HyperLinkButton/HyperLinkButton.module.css b/src/v4/social/elements/HyperLinkButton/HyperLinkButton.module.css new file mode 100644 index 000000000..caaaa6686 --- /dev/null +++ b/src/v4/social/elements/HyperLinkButton/HyperLinkButton.module.css @@ -0,0 +1,3 @@ +.hyperLinkButton { + fill: var(--asc-color-white); +} diff --git a/src/v4/social/elements/HyperLinkButton/HyperLinkButton.tsx b/src/v4/social/elements/HyperLinkButton/HyperLinkButton.tsx index c7b005af1..006363fad 100644 --- a/src/v4/social/elements/HyperLinkButton/HyperLinkButton.tsx +++ b/src/v4/social/elements/HyperLinkButton/HyperLinkButton.tsx @@ -1,18 +1,42 @@ import React from 'react'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { IconComponent } from '~/v4/core/IconComponent'; +import clsx from 'clsx'; -import { ActionButton } from '../ActionButton'; -import { LinkIcon } from '~/icons'; -import { useAmityElement } from '~/v4/core/hooks/uikit/index'; +import styles from './HyperLinkButton.module.css'; + +const HyperLinkButtonSvg = (props: React.SVGProps<SVGSVGElement>) => { + return ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="32" + height="32" + fill="none" + viewBox="0 0 32 32" + {...props} + > + <circle cx="16" cy="16" r="16" fill="#000" fillOpacity="0.5"></circle> + <path + fill="currentColor" + d="M18.04 14.21c1.792 1.794 1.898 4.677.316 6.575-.212.281-.07.14-2.954 3.024-1.933 1.933-5.062 1.933-6.96 0-1.934-1.899-1.934-5.028 0-6.961l2.214-2.215c.246-.246.703-.07.703.281.036.387.106.984.176 1.336.035.14 0 .281-.105.387l-1.617 1.617a2.95 2.95 0 000 4.183 2.95 2.95 0 004.183 0l2.637-2.636a2.95 2.95 0 000-4.184c-.176-.21-.563-.457-.809-.562-.14-.106-.246-.282-.21-.457.034-.387.21-.774.527-1.055l.14-.14a.435.435 0 01.492-.106c.457.246.88.527 1.266.914zm5.483-5.483c1.934 1.898 1.934 5.027 0 6.96l-2.214 2.215c-.247.246-.704.07-.704-.28a15.366 15.366 0 00-.175-1.337.397.397 0 01.105-.387l1.617-1.617a2.95 2.95 0 000-4.183 2.95 2.95 0 00-4.183 0l-2.637 2.636a2.95 2.95 0 000 4.184c.176.21.563.457.809.563.14.105.246.28.21.456a1.607 1.607 0 01-.527 1.055l-.14.14a.435.435 0 01-.493.106 5.265 5.265 0 01-1.265-.914c-1.793-1.793-1.899-4.676-.317-6.574.211-.281.07-.14 2.954-3.023 1.933-1.934 5.062-1.934 6.96 0z" + ></path> + </svg> + ); +}; interface HyperLinkButtonProps { pageId?: string; componentId?: string; + defaultClassName?: string; + imgClassName?: string; onClick: (e: React.MouseEvent) => void; } export const HyperLinkButton = ({ pageId = '*', componentId = '*', + defaultClassName, + imgClassName, onClick = () => {}, }: HyperLinkButtonProps) => { const elementId = 'story_hyperlink_button'; @@ -26,9 +50,15 @@ export const HyperLinkButton = ({ if (isExcluded) return null; return ( - <ActionButton - icon={<LinkIcon fill={themeStyles?.color || 'var(--asc-color-white)'} />} + <IconComponent + data-qa-anchor={accessibilityId} + className={clsx(styles.hyperLinkButton, defaultClassName)} + defaultIcon={() => <HyperLinkButtonSvg />} + imgIcon={() => <img src={config.icon} alt={uiReference} className={clsx(imgClassName)} />} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} onClick={onClick} + style={themeStyles} /> ); }; diff --git a/src/v4/social/elements/ImpressionButton/ImpressionButton.module.css b/src/v4/social/elements/ImpressionButton/ImpressionButton.module.css new file mode 100644 index 000000000..1b198ad2c --- /dev/null +++ b/src/v4/social/elements/ImpressionButton/ImpressionButton.module.css @@ -0,0 +1,5 @@ +.impressionButton { + display: flex; + align-items: center; + gap: var(--asc-spacing-xxs2); +} diff --git a/src/v4/social/elements/ImpressionButton/ImpressionButton.tsx b/src/v4/social/elements/ImpressionButton/ImpressionButton.tsx index f3d77f4e0..3e1a5a173 100644 --- a/src/v4/social/elements/ImpressionButton/ImpressionButton.tsx +++ b/src/v4/social/elements/ImpressionButton/ImpressionButton.tsx @@ -1,39 +1,67 @@ import React from 'react'; +import clsx from 'clsx'; +import { Typography } from '~/v4/core/components'; +import { IconComponent } from '~/v4/core/IconComponent'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; -import { RemoteImageButton } from './styles'; -import { isValidHttpUrl } from '~/utils'; -import { EyeIcon } from '~/icons'; -import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; +import styles from './ImpressionButton.module.css'; -interface ImpressionButtonProps { - pageId: 'story_page'; - componentId: '*'; - onClick?: (e: React.MouseEvent) => void; - style?: React.CSSProperties; - 'data-qa-anchor'?: string; +const ImpressionSvg = (props: React.SVGProps<SVGSVGElement>) => ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="20" + height="20" + fill="none" + viewBox="0 0 20 20" + {...props} + > + <path + fill="#A5A9B5" + d="M10 6.5c1.938 0 3.5 1.594 3.5 3.5 0 1.938-1.563 3.5-3.5 3.5A3.494 3.494 0 016.5 10c0-.281.063-.688.156-.969.188.125.594.219.844.219.938 0 1.75-.781 1.75-1.75-.031-.219-.125-.625-.25-.844.281-.062.719-.125 1-.156zm8.875 3.063a1.142 1.142 0 010 .906C17.187 13.78 13.812 16 10 16c-3.844 0-7.219-2.219-8.906-5.531a1.142 1.142 0 010-.906C2.78 6.25 6.156 4 10 4c3.813 0 7.188 2.25 8.875 5.563zM10 14.5c3.063 0 5.906-1.719 7.406-4.5-1.5-2.781-4.343-4.5-7.406-4.5-3.094 0-5.938 1.719-7.438 4.5 1.5 2.781 4.344 4.5 7.438 4.5z" + ></path> + </svg> +); + +interface CommentButtonProps { + pageId?: string; + componentId?: string; + reach?: number; + defaultIconClassName?: string; + imgIconClassName?: string; } -export const ImpressionButton = ({ - pageId = 'story_page', +export function ImpressionButton({ + pageId = '*', componentId = '*', - onClick = () => {}, - style, -}: ImpressionButtonProps) => { + reach = 0, + defaultIconClassName, + imgIconClassName, +}: CommentButtonProps) { const elementId = 'story_impression_button'; - const { getConfig, isExcluded } = useCustomization(); - const elementConfig = getConfig(`${pageId}/${componentId}/${elementId}`); - - const impressionIcon = elementConfig?.impression_icon; - - const isElementExcluded = isExcluded(`${pageId}/${componentId}/${elementId}`); + const { accessibilityId, config, defaultConfig, isExcluded, uiReference } = useAmityElement({ + pageId, + componentId, + elementId, + }); - if (isElementExcluded) return null; + if (isExcluded) return null; - const isRemoteImage = impressionIcon && isValidHttpUrl(impressionIcon); - - return isRemoteImage ? ( - <RemoteImageButton data-qa-anchor="reach_button" src={impressionIcon} onClick={onClick} /> - ) : ( - <EyeIcon data-qa-anchor="reach_button" width={20} height={20} onClick={onClick} /> + return ( + <IconComponent + defaultIcon={() => ( + <div + className={clsx(styles.impressionButton, defaultIconClassName)} + data-qa-anchor={accessibilityId} + > + <ImpressionSvg /> + <Typography.BodyBold> + {typeof reach === 'number' ? reach : config.text} + </Typography.BodyBold> + </div> + )} + imgIcon={() => <img src={config.icon} alt={uiReference} className={imgIconClassName} />} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} + /> ); -}; +} diff --git a/src/v4/social/elements/ImpressionButton/styles.tsx b/src/v4/social/elements/ImpressionButton/styles.tsx deleted file mode 100644 index ea036d700..000000000 --- a/src/v4/social/elements/ImpressionButton/styles.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import styled from 'styled-components'; - -export const RemoteImageButton = styled.img` - width: 1.5rem; - height: 1.5rem; - border: none; - outline: none; - padding: 0; - margin: 0; - display: flex; - align-items: center; - justify-content: center; - - &:hover { - opacity: 0.8; - } - - &:active { - opacity: 0.6; - } -`; diff --git a/src/v4/social/elements/SaveButton/SaveButton.tsx b/src/v4/social/elements/SaveButton/SaveButton.tsx index 2eb105f85..5c051389a 100644 --- a/src/v4/social/elements/SaveButton/SaveButton.tsx +++ b/src/v4/social/elements/SaveButton/SaveButton.tsx @@ -1,57 +1,39 @@ import React from 'react'; -import { PrimaryButton } from '~/core/components/Button'; +import clsx from 'clsx'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; -import { RemoteImageButton } from './styles'; -import { isValidHttpUrl } from '~/utils'; -import { useTheme } from 'styled-components'; -import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; +import { Button } from '~/v4/core/components'; interface SaveButtonProps { - pageId: '*'; - componentId: 'edit_comment_component'; - onClick?: (e: React.MouseEvent) => void; - children?: React.ReactNode; - style?: React.CSSProperties; - 'data-qa-anchor'?: string; + pageId?: string; + componentId?: string; + className?: string; + onClick?: () => void; } -export const SaveButton = ({ +export function SaveButton({ pageId = '*', - componentId = 'edit_comment_component', + componentId = '*', onClick, - style, -}: SaveButtonProps) => { - const theme = useTheme(); + className, +}: SaveButtonProps) { const elementId = 'save_button'; - const { getConfig, isExcluded } = useCustomization(); - const elementConfig = getConfig(`${pageId}/${componentId}/${elementId}`); - const isElementExcluded = isExcluded(`${pageId}/${componentId}/${elementId}`); + const { accessibilityId, config, isExcluded } = useAmityElement({ + pageId, + componentId, + elementId, + }); - if (isElementExcluded) return null; + if (isExcluded) return null; - const saveIcon = elementConfig?.save_icon; - const isRemoteImage = saveIcon && isValidHttpUrl(saveIcon); - - return isRemoteImage ? ( - <RemoteImageButton - data-qa-anchor="edit_comment_component/save_button" - src={saveIcon} - onClick={onClick} - style={{ - ...style, - backgroundColor: elementConfig?.background_color || theme.v4.colors.secondary.default, - }} - /> - ) : ( - <PrimaryButton - data-qa-anchor="edit_comment_component/save_button" + return ( + <Button + variant="primary" + className={clsx(className)} + data-qa-anchor={accessibilityId} onClick={onClick} - style={{ - ...style, - backgroundColor: elementConfig?.background_color || theme.v4.colors.secondary.default, - }} > - {elementConfig?.save_button_text} - </PrimaryButton> + {config.text} + </Button> ); -}; +} diff --git a/src/v4/social/elements/SaveButton/styles.tsx b/src/v4/social/elements/SaveButton/styles.tsx deleted file mode 100644 index e43fd8e7c..000000000 --- a/src/v4/social/elements/SaveButton/styles.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import styled from 'styled-components'; - -export const RemoteImageButton = styled.img` - width: 24px; - height: 24px; - cursor: pointer; - background-color: transparent; - border: none; - outline: none; - padding: 0; - margin: 0; - display: flex; - align-items: center; - justify-content: center; - - &:hover { - opacity: 0.8; - } - - &:active { - opacity: 0.6; - } -`; diff --git a/src/v4/social/elements/ShareStoryButton/ShareStoryButton.module.css b/src/v4/social/elements/ShareStoryButton/ShareStoryButton.module.css index 19086651a..0992c06ed 100644 --- a/src/v4/social/elements/ShareStoryButton/ShareStoryButton.module.css +++ b/src/v4/social/elements/ShareStoryButton/ShareStoryButton.module.css @@ -19,7 +19,3 @@ .shareStoryIcon { margin-left: var(--asc-spacing-s1); } - -.hideAvatar { - display: none; -} diff --git a/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx b/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx index f93b57c2a..0eba1e071 100644 --- a/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx +++ b/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx @@ -1,11 +1,10 @@ import React from 'react'; import clsx from 'clsx'; -import { Avatar, Typography } from '~/v4/core/components'; +import { Typography } from '~/v4/core/components'; import { useAmityElement } from '~/v4/core/hooks/uikit/index'; -import Community from '~/v4/icons/Community'; -import { AVATAR_SIZE } from '~/v4/core/components/Avatar/Avatar'; import styles from './ShareStoryButton.module.css'; +import { CommunityAvatar } from '~/v4/social/elements/CommunityAvatar'; const ArrowRightIcon = (props: React.SVGProps<SVGSVGElement>) => { return ( @@ -27,18 +26,16 @@ const ArrowRightIcon = (props: React.SVGProps<SVGSVGElement>) => { interface ShareButtonProps { onClick: () => void; + community?: Amity.Community | null; pageId?: string; componentId?: string; - avatar?: string; - style?: React.CSSProperties; - 'data-qa-anchor'?: string; } export const ShareStoryButton = ({ pageId = '*', componentId = '*', + community, onClick, - avatar, }: ShareButtonProps) => { const elementId = 'share_story_button'; @@ -59,12 +56,7 @@ export const ShareStoryButton = ({ data-hideAvatar={config?.hide_avatar} > {!config?.hide_avatar && ( - <Avatar - data-qa-anchor="share_story_button_image_view" - size={AVATAR_SIZE.SMALL} - avatar={avatar} - defaultImage={<Community />} - /> + <CommunityAvatar pageId={pageId} componentId={componentId} community={community} /> )} <Typography.BodyBold>{config?.text || 'Share story'}</Typography.BodyBold> <ArrowRightIcon /> diff --git a/src/v4/social/elements/SpeakerButton/SpeakerButton.module.css b/src/v4/social/elements/SpeakerButton/SpeakerButton.module.css new file mode 100644 index 000000000..0bb2463b9 --- /dev/null +++ b/src/v4/social/elements/SpeakerButton/SpeakerButton.module.css @@ -0,0 +1,12 @@ +.speakerButton { + fill: var(--asc-color-white); + position: absolute; + top: 96px; + left: 24px; + z-index: 99999; +} + +.speakerButton__icon { + width: 1.5rem; + height: 1.5rem; +} diff --git a/src/v4/social/elements/SpeakerButton/SpeakerButton.tsx b/src/v4/social/elements/SpeakerButton/SpeakerButton.tsx index b8e3aa765..d2bc88edc 100644 --- a/src/v4/social/elements/SpeakerButton/SpeakerButton.tsx +++ b/src/v4/social/elements/SpeakerButton/SpeakerButton.tsx @@ -1,86 +1,81 @@ import React from 'react'; +import clsx from 'clsx'; +import { IconComponent } from '~/v4/core/IconComponent'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; -import { ActionButton, CustomActionButton } from './styles'; -import { isValidHttpUrl } from '~/utils'; -import { useTheme } from 'styled-components'; -import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; +import styles from './SpeakerButton.module.css'; + +const SpeakerMuteSvg = (props: React.SVGProps<SVGSVGElement>) => ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="32" + height="32" + fill="none" + viewBox="0 0 32 32" + {...props} + > + <circle cx="16" cy="16" r="16" fill="#000" fillOpacity="0.5"></circle> + <path + fill="#fff" + d="M15.121 9.781c.527-.527 1.441-.176 1.441.598v11.777c0 .774-.914 1.125-1.44.598l-3.13-3.129H8.406c-.492 0-.844-.352-.844-.844V13.72c0-.457.352-.844.844-.844h3.586l3.13-3.094zm9.316 6.469c0 2.25-1.16 4.29-3.023 5.52-.422.246-.95.105-1.16-.282a.845.845 0 01.246-1.16 4.807 4.807 0 002.25-4.078 4.792 4.792 0 00-2.25-4.043.845.845 0 01-.246-1.16.824.824 0 011.16-.281c1.863 1.23 3.023 3.27 3.023 5.484zm-4.992-2.672c.985.527 1.617 1.582 1.617 2.672 0 1.125-.632 2.18-1.617 2.707-.422.246-.914.105-1.16-.316a.869.869 0 01.352-1.16c.422-.247.738-.704.738-1.231 0-.492-.316-.95-.738-1.195a.87.87 0 01-.352-1.16c.246-.422.738-.563 1.16-.317z" + ></path> + </svg> +); + +const SpeakerUnmuteSvg = (props: React.SVGProps<SVGSVGElement>) => { + return ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="32" + height="32" + fill="none" + viewBox="0 0 32 32" + {...props} + > + <circle cx="16" cy="16" r="16" fill="#000" fillOpacity="0.5"></circle> + <path + fill="#fff" + d="M25.781 22.344a.467.467 0 01.094.687l-.594.782c-.187.218-.5.28-.718.093L6.188 9.688A.468.468 0 016.093 9l.625-.781a.468.468 0 01.687-.094l4.75 3.656 1.563-1.531c.469-.469 1.281-.156 1.281.531v3.188l2.469 1.937a1.234 1.234 0 00-.625-.969.773.773 0 01-.313-1.03c.219-.376.657-.5 1.032-.282A2.739 2.739 0 0119 16c0 .344-.094.625-.188.938l1.22.937c.28-.563.468-1.188.468-1.875a4.26 4.26 0 00-2-3.594.75.75 0 01-.219-1.031c.219-.344.688-.469 1.032-.25C20.968 12.188 22 14.031 22 16a5.727 5.727 0 01-.75 2.813l1.188.937c.656-1.125 1.03-2.406 1.03-3.719 0-2.468-1.218-4.781-3.312-6.125-.343-.219-.437-.687-.218-1.062A.795.795 0 0121 8.625c2.5 1.656 4 4.406 4 7.375 0 1.688-.5 3.281-1.375 4.656l2.156 1.688zM7 13.75c0-.25.125-.469.344-.625L15 19.031v2.219c0 .688-.813 1-1.281.531L10.938 19H7.75a.722.722 0 01-.75-.75v-4.5z" + ></path> + </svg> + ); +}; interface SpeakerButtonProps { - pageId: 'story_page'; - componentId: '*'; isMuted: boolean; - onClick?: (e: React.MouseEvent) => void; - style?: React.CSSProperties; - 'data-qa-anchor'?: string; + pageId?: string; + componentId?: string; + defaultIconClassName?: string; + imgIconClassName?: string; + onClick: () => void; } -export const SpeakerButton = ({ - pageId = 'story_page', +export function SpeakerButton({ + isMuted, + pageId = '*', componentId = '*', - isMuted = false, - onClick = () => {}, - style, - ...props -}: SpeakerButtonProps) => { - const theme = useTheme(); + defaultIconClassName, + imgIconClassName, + onClick, +}: SpeakerButtonProps) { const elementId = 'speaker_button'; - const { getConfig, isExcluded } = useCustomization(); - const elementConfig = getConfig(`${pageId}/${componentId}/${elementId}`); - const isElementExcluded = isExcluded(`${pageId}/${componentId}/${elementId}`); - - if (isElementExcluded) return null; - - const mutedIcon = elementConfig?.mute_icon; - const unmutedIcon = elementConfig?.unmute_icon; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference } = useAmityElement({ + pageId, + componentId, + elementId, + }); - const isMutedRemoteImage = mutedIcon && isValidHttpUrl(mutedIcon); - const isUnmutedRemoteImage = unmutedIcon && isValidHttpUrl(unmutedIcon); + if (isExcluded) return null; - return isMuted ? ( - isMutedRemoteImage ? ( - <CustomActionButton - data-qa-anchor="video_audio_button" - src={mutedIcon} - onClick={onClick} - style={{ - ...style, - backgroundColor: elementConfig?.background_color || theme.v4.colors.base.default, - }} - {...props} - /> - ) : ( - <ActionButton - data-qa-anchor="video_audio_button" - name={'UnmuteCircle'} - onClick={onClick} - style={{ - ...style, - backgroundColor: elementConfig?.background_color || theme.v4.colors.base.default, - }} - {...props} - /> - ) - ) : isUnmutedRemoteImage ? ( - <CustomActionButton - data-qa-anchor="video_audio_button" - src={unmutedIcon} + return ( + <IconComponent + data-qa-anchor={accessibilityId} + className={clsx(styles.speakerButton)} onClick={onClick} - style={{ - ...style, - backgroundColor: elementConfig?.background_color || theme.v4.colors.base.default, - }} - {...props} - /> - ) : ( - <ActionButton - data-qa-anchor="video_audio_button" - name={'MuteCircle'} - onClick={onClick} - style={{ - ...style, - backgroundColor: elementConfig?.background_color || theme.v4.colors.base.default, - }} - {...props} + defaultIcon={() => (isMuted ? <SpeakerMuteSvg /> : <SpeakerUnmuteSvg />)} + imgIcon={() => <img src={config.icon} alt={uiReference} className={clsx(imgIconClassName)} />} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} /> ); -}; +} diff --git a/src/v4/social/elements/SpeakerButton/styles.tsx b/src/v4/social/elements/SpeakerButton/styles.tsx deleted file mode 100644 index 401b4dcaa..000000000 --- a/src/v4/social/elements/SpeakerButton/styles.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import styled from 'styled-components'; -import { Icon } from '~/v4/core/components'; - -export const ActionButton = styled(Icon)` - width: 1.5rem; - height: 1.5rem; - padding: 0.25rem; - cursor: pointer; - border-radius: 50%; - position: absolute; - top: 96px; - left: 24px; - z-index: 99999; -`; - -export const CustomActionButton = styled.img` - width: 1.5rem; - height: 1.5rem; - cursor: pointer; - background-color: transparent; - border: none; - outline: none; - padding: 0; - margin: 0; - display: flex; - align-items: center; - justify-content: center; - - &:hover { - opacity: 0.8; - } - - &:active { - opacity: 0.6; - } -`; diff --git a/src/v4/social/elements/StoryCommentButton/StoryCommentButton.module.css b/src/v4/social/elements/StoryCommentButton/StoryCommentButton.module.css deleted file mode 100644 index 27a945877..000000000 --- a/src/v4/social/elements/StoryCommentButton/StoryCommentButton.module.css +++ /dev/null @@ -1,27 +0,0 @@ -.uiStoryCommentButton { - font-family: Inter, sans-serif; - font-weight: var(--asc-text-font-weight-bold); - color: var(--asc-color-white); - display: flex; - align-items: center; - justify-content: space-between; - gap: var(--asc-spacing-xxs2); - border-radius: 1.5rem; - padding: var(--asc-spacing-s1) var(--asc-spacing-s2); - background-color: var(--asc-color-base-default); - cursor: pointer; - border: none; -} - -.uiRemoteImageButton { - width: var(--asc-spacing-m3); - height: var(--asc-spacing-m3); - cursor: pointer; - border: none; - outline: none; - padding: var(--asc-spacing-none); - margin: var(--asc-spacing-none); - display: flex; - align-items: center; - justify-content: center; -} diff --git a/src/v4/social/elements/StoryCommentButton/StoryCommentButton.tsx b/src/v4/social/elements/StoryCommentButton/StoryCommentButton.tsx deleted file mode 100644 index cac07ee41..000000000 --- a/src/v4/social/elements/StoryCommentButton/StoryCommentButton.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import React from 'react'; -import { Icon } from '~/v4/core/components'; -import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; -import { isValidHttpUrl } from '~/utils'; -import { useTheme } from 'styled-components'; -import styles from './StoryCommentButton.module.css'; - -interface ReactButtonProps { - pageId: 'story_page'; - componentId: '*'; - onClick?: (e: React.MouseEvent) => void; - style?: React.CSSProperties; - children?: React.ReactNode; - 'data-qa-anchor'?: string; -} - -export const StoryCommentButton = ({ - pageId = 'story_page', - componentId = '*', - onClick = () => {}, - style, - children, - ...props -}: ReactButtonProps) => { - const theme = useTheme(); - const { getConfig, isExcluded } = useCustomization(); - const elementConfig = getConfig(`${pageId}/*/story_comment_button`); - const isElementExcluded = isExcluded(`${pageId}/*/story_comment_button`); - const backgroundColor = elementConfig?.background_color; - const commentIcon = elementConfig?.comment_icon; - - if (isElementExcluded) return null; - - const isRemoteImage = commentIcon && isValidHttpUrl(commentIcon); - - return isRemoteImage ? ( - <img - data-qa-anchor="comment_button" - src={commentIcon} - onClick={onClick} - style={{ - ...style, - backgroundColor: backgroundColor || theme.v4.colors.secondary.default, - }} - className={styles.uiRemoteImageButton} - {...props} - /> - ) : ( - <button - data-qa-anchor="comment_button" - onClick={onClick} - className={styles.uiStoryCommentButton} - style={{ - ...style, - backgroundColor: backgroundColor || theme.v4.colors.secondary.default, - }} - {...props} - > - <Icon name={commentIcon === 'comment' ? commentIcon : 'Comment2Icon'} /> - <span data-qa-anchor="comment_button_text_view">{children}</span> - </button> - ); -}; diff --git a/src/v4/social/elements/StoryCommentButton/index.ts b/src/v4/social/elements/StoryCommentButton/index.ts deleted file mode 100644 index 5ac4972a8..000000000 --- a/src/v4/social/elements/StoryCommentButton/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { StoryCommentButton } from './StoryCommentButton'; diff --git a/src/v4/social/elements/StoryRing/StoryRing.tsx b/src/v4/social/elements/StoryRing/StoryRing.tsx index 75b32d364..9a6c86826 100644 --- a/src/v4/social/elements/StoryRing/StoryRing.tsx +++ b/src/v4/social/elements/StoryRing/StoryRing.tsx @@ -154,7 +154,6 @@ const UploadingRingSvg = ({ fill="none" strokeDasharray={339} strokeDashoffset={339} - data-uploading={true} transform={`rotate(-90 ${size / 2} ${size / 2})`} /> </svg> diff --git a/src/v4/social/elements/index.ts b/src/v4/social/elements/index.ts index 2935b33c5..1d5637e63 100644 --- a/src/v4/social/elements/index.ts +++ b/src/v4/social/elements/index.ts @@ -2,7 +2,6 @@ export { AspectRatioButton } from './AspectRatioButton'; export { BackButton } from './BackButton/BackButton'; export { CloseButton } from './CloseButton'; export { CancelButton } from './CancelButton'; -export { StoryCommentButton } from './StoryCommentButton'; export { CreateStoryButton } from './CreateStoryButton/CreateStoryButton'; export { HyperLinkButton } from './HyperLinkButton/HyperLinkButton'; export { ImpressionButton } from './ImpressionButton'; @@ -12,3 +11,4 @@ export { SaveButton } from './SaveButton'; export { ShareStoryButton } from './ShareStoryButton/ShareStoryButton'; export { SpeakerButton } from './SpeakerButton'; export { HyperLink } from './HyperLink'; +export { CreateNewStoryButton } from './CreateNewStoryButton'; diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index ddb1fd7c1..344b5529f 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -1,35 +1,27 @@ import React, { useState, useEffect } from 'react'; -import { Tester } from 'react-insta-stories/dist/interfaces'; - -import styles from './Renderers.module.css'; - import { useIntl } from 'react-intl'; - import Truncate from 'react-truncate-markup'; - -import { CustomRenderer } from '~/v4/social/internal-components/StoryViewer/Renderers/types'; - +import { + CustomRenderer, + Tester, +} from '~/v4/social/internal-components/StoryViewer/Renderers/types'; import { CommentTray } from '~/v4/social/components'; import { HyperLink } from '~/v4/social/elements/HyperLink'; import Footer from '~/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer'; import Header from '~/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header'; import { motion, PanInfo, useAnimationControls } from 'framer-motion'; - import { BottomSheet } from '~/v4/core/components/BottomSheet'; import { Typography } from '~/v4/core/components'; import { Button } from '~/v4/core/components/Button'; - import useCommunityMembersCollection from '~/v4/social/hooks/collections/useCommunityMembersCollection'; - import useSDK from '~/v4/core/hooks/useSDK'; -import useImage from '~/v4/core/hooks/useImage'; import useUser from '~/v4/core/hooks/objects/useUser'; - import useCommunityStoriesSubscription from '~/v4/social/hooks/useCommunityStoriesSubscription'; import { LIKE_REACTION_KEY } from '~/v4/social/constants/reactions'; - import { checkStoryPermission, formatTimeAgo, isAdmin, isModerator } from '~/v4/social/utils'; +import styles from './Renderers.module.css'; + export const renderer: CustomRenderer = ({ story, action, @@ -42,9 +34,8 @@ export const renderer: CustomRenderer = ({ const [loaded, setLoaded] = useState(false); const [isOpenBottomSheet, setIsOpenBottomSheet] = useState(false); const [isOpenCommentSheet, setIsOpenCommentSheet] = useState(false); - const [isPaused, setIsPaused] = useState(false); - const { width, height, loader, storyStyles } = config; + const { loader, storyStyles } = config; const { client } = useSDK(); const isLiked = !!(story && story.myReactions && story.myReactions.includes(LIKE_REACTION_KEY)); @@ -68,23 +59,8 @@ export const renderer: CustomRenderer = ({ const member = members?.find((member) => member.userId === client?.userId); const isMember = member != null; - const avatarUrl = useImage({ - fileId: community?.avatarFileId || '', - imageSize: 'small', - }); - const { user } = useUser(client?.userId); - - const heading = <div data-qa-anchor="community_display_name">{community?.displayName}</div>; - const subheading = - createdAt && creator?.displayName ? ( - <span> - <span data-qa-anchor="created_at">{formatTimeAgo(createdAt as string)}</span> • By{' '} - <span data-qa-anchor="creator_display_name">{creator?.displayName}</span> - </span> - ) : ( - '' - ); + const controls = useAnimationControls(); const isOfficial = community?.isOfficial || false; const isCreator = creator?.userId === user?.userId; @@ -98,6 +74,18 @@ export const renderer: CustomRenderer = ({ ...(storyStyles || {}), }; + const heading = <div data-qa-anchor="community_display_name">{community?.displayName}</div>; + const subheading = + createdAt && creator?.displayName ? ( + <span> + <span data-qa-anchor="created_at">{formatTimeAgo(createdAt as string)}</span> • By{' '} + <span data-qa-anchor="creator_display_name">{creator?.displayName}</span> + </span> + ) : ( + '' + ); + const targetRootId = 'asc-uikit-stories-viewer'; + const imageLoaded = () => { setLoaded(true); if (isPaused) { @@ -114,10 +102,6 @@ export const renderer: CustomRenderer = ({ const openCommentSheet = () => setIsOpenCommentSheet(true); const closeCommentSheet = () => setIsOpenCommentSheet(false); - const targetRootId = 'asc-uikit-stories-viewer'; - - const controls = useAnimationControls(); - const handleSwipeDown = () => { controls .start({ @@ -156,6 +140,7 @@ export const renderer: CustomRenderer = ({ }, [isPaused, isOpenBottomSheet, isOpenCommentSheet]); useEffect(() => { + action('pause', true); if (fileInputRef.current) { fileInputRef.current.addEventListener('click', () => { action('pause', true); @@ -164,6 +149,7 @@ export const renderer: CustomRenderer = ({ action('play', true); }); } + return () => { if (fileInputRef.current) { fileInputRef.current.removeEventListener('cancel', () => { @@ -194,7 +180,7 @@ export const renderer: CustomRenderer = ({ whileDrag={{ scale: 0.95, borderRadius: '8px', cursor: 'grabbing' }} > <Header - avatar={avatarUrl} + community={community} heading={heading} subheading={subheading} isHaveActions={actions?.length > 0} @@ -219,11 +205,7 @@ export const renderer: CustomRenderer = ({ alt="Story Image" /> - {!loaded && ( - <div className={styles.loadingOverlay} style={{ width, height }}> - {loader || <div>loading...</div>} - </div> - )} + {!loaded && <div className={styles.loadingOverlay}>{loader || <div>loading...</div>}</div>} <BottomSheet rootId={targetRootId} diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css b/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css index 5d10ae2dd..5a67106f9 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css @@ -50,6 +50,8 @@ } .loadingOverlay { + width: 100%; + height: 100%; position: absolute; left: 0; top: 0; diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx index 6a30dd927..5bde45379 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx @@ -1,10 +1,12 @@ import React, { useEffect, useRef, useState } from 'react'; -import { Tester } from 'react-insta-stories/dist/interfaces'; import { useIntl } from 'react-intl'; import Truncate from 'react-truncate-markup'; -import { CustomRenderer } from '~/v4/social/internal-components/StoryViewer/Renderers/types'; +import { + CustomRenderer, + Tester, +} from '~/v4/social/internal-components/StoryViewer/Renderers/types'; import { SpeakerButton } from '~/v4/social/elements'; import { BottomSheet, Button, Typography } from '~/v4/core/components'; @@ -223,7 +225,7 @@ export const renderer: CustomRenderer = ({ onClick={muted ? unmute : mute} /> <Header - avatar={avatarUrl} + community={community} heading={heading} subheading={subheading} isHaveActions={actions?.length > 0} @@ -252,7 +254,6 @@ export const renderer: CustomRenderer = ({ onPlaying={onPlaying} muted={muted} autoPlay - webkit-playsinline="true" /> {!loaded && ( <div className={clsx(rendererStyles.loadingOverlay)}>{loader || <div>loading...</div>}</div> diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/Footer.module.css b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/Footer.module.css index cdd243bbf..a77ad0ad2 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/Footer.module.css +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/Footer.module.css @@ -56,3 +56,16 @@ justify-content: space-between; gap: var(--asc-spacing-s2); } + +.viewStoryCommentButton { + display: flex; + padding: 8px 10px; + align-items: center; + gap: 8px; + border-radius: 24px; + background: var(--asc-color-base-default); +} + +.viewStoryCommentIcon { + fill: var(--asc-color-white); +} diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx index dd598c3f3..b3b7f741a 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import styles from './Footer.module.css'; import { DotsIcon, ErrorIcon } from '~/icons'; import { useIntl } from 'react-intl'; @@ -7,8 +6,12 @@ import millify from 'millify'; import { ReactionRepository } from '@amityco/ts-sdk'; import { LIKE_REACTION_KEY } from '~/constants'; import Spinner from '~/social/components/Spinner'; -import { StoryCommentButton, ImpressionButton, ReactButton } from '~/v4/social/elements'; +import { ImpressionButton, ReactButton } from '~/v4/social/elements'; import { useNotifications } from '~/v4/core/providers/NotificationProvider'; +import { CommentButton } from '~/v4/social/elements/CommentButton/CommentButton'; + +import styles from './Footer.module.css'; +import clsx from 'clsx'; const Footer: React.FC< React.PropsWithChildren<{ @@ -82,15 +85,18 @@ const Footer: React.FC< <div> {showImpression && ( <div className={styles.viewStoryCompostBarViewIconContainer}> - <ImpressionButton pageId="story_page" componentId="*" /> - {millify(reach || 0)} + <ImpressionButton pageId="story_page" reach={reach} /> </div> )} </div> <div className={styles.viewStoryCompostBarEngagementContainer}> - <StoryCommentButton onClick={onClickComment} pageId="story_page" componentId="*"> - {millify(commentsCount) || 0} - </StoryCommentButton> + <CommentButton + className={clsx(styles.viewStoryCommentButton)} + defaultIconClassName={clsx(styles.viewStoryCommentIcon)} + pageId="story_page" + commentsCount={commentsCount} + onClick={onClickComment} + /> <ReactButton onClick={handleLike} pageId="story_page" isLiked={isLiked}> {millify(totalLikes || 0)} </ReactButton> diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/styles.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/styles.tsx deleted file mode 100644 index 3ab262511..000000000 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/styles.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import styled from 'styled-components'; -import { EyeIcon } from '~/icons'; - -export const ViewCountIcon = styled(EyeIcon)` - color: #a5a9b5; -`; - -export const StoryContainer = styled.div` - position: relative; - display: flex; - flex-direction: column; - align-items: center; -`; - -export const ViewStoryInfoContainer = styled.div` - display: flex; - flex-direction: column; - justify-content: flex-start; - width: 100%; -`; - -export const StoryTabBarContainer = styled.div` - display: flex; - flex-direction: column; - align-items: center; - padding: 0.5rem; - height: 5rem; -`; - -export const ViewStoryUploadingWrapper = styled.div` - display: flex; - align-items: center; - justify-content: flex-start; - gap: 0.5rem; -`; - -export const ViewStoryCompostBarContainer = styled.div` - position: absolute; - bottom: 0; - width: 100%; - height: 3.5rem; - display: flex; - justify-content: space-between; - align-items: center; - padding: 0.75rem; - background-color: #000; - color: ${({ theme }) => theme.v4.colors.baseInverse.default}; - z-index: 100; -`; - -export const ViewStoryFailedCompostBarContainer = styled.div` - position: absolute; - bottom: 0; - display: flex; - justify-content: space-between; - align-items: center; - width: 100%; - height: 3.5rem; - padding: 0.75rem; - background-color: ${({ theme }) => theme.palette.alert.main}; - color: #ffffff; - z-index: 0; -`; - -export const ViewStoryFailedCompostBarWrapper = styled.div` - display: flex; - align-items: center; - justify-content: flex-start; - gap: 0.5rem; - width: 100%; -`; - -export const ViewStoryCompostBarViewIconContainer = styled.div` - ${({ theme }) => theme.typography.bodyBold}; - color: #fff; - display: flex; - align-items: center; - justify-content: space-between; - gap: 0.25rem; -`; - -export const ViewStoryCompostBarEngagementContainer = styled.div` - ${({ theme }) => theme.typography.bodyBold}; - display: flex; - align-items: center; - justify-content: space-between; - gap: 0.75rem; -`; - -export const ViewStoryCompostBarEngagementButton = styled.button` - ${({ theme }) => theme.typography.bodyBold}; - color: #fff; - display: flex; - align-items: center; - justify-content: space-between; - gap: 0.25rem; - border-radius: 1.5rem; - padding: 0.5rem 0.625rem; - background-color: #292b32; - cursor: pointer; - border: none; -`; - -export const ViewStoryContainer = styled.div` - position: relative; - width: 100%; - height: 100%; - display: flex; - flex-direction: column; - background-color: black; -`; diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/Header.module.css b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/Header.module.css index d78052b01..e0ad752d7 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/Header.module.css +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/Header.module.css @@ -63,7 +63,7 @@ } .closeButton { - color: var(--asc-color-white); + fill: var(--asc-color-white); width: 1.25rem; height: 1.25rem; cursor: pointer; diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/index.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/index.tsx index 7ceef497d..1900d36f0 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/index.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/index.tsx @@ -1,12 +1,12 @@ import React from 'react'; -import styles from './Header.module.css'; import Truncate from 'react-truncate-markup'; -import Avatar from '~/core/components/Avatar'; -import { backgroundImage as communityBackgroundImage } from '~/icons/Community'; - import { PauseIcon, PlayIcon } from '~/icons'; import { CloseButton, OverflowMenuButton } from '~/v4/social/elements'; import Verified from '~/v4/social/icons/verified'; +import clsx from 'clsx'; +import { CommunityAvatar } from '~/v4/social/elements/CommunityAvatar'; + +import styles from './Header.module.css'; const Header: React.FC< React.PropsWithChildren<{ @@ -16,7 +16,7 @@ const Header: React.FC< onClose: () => void; onAddStory: (e: React.MouseEvent<Element, MouseEvent>) => void; onClickCommunity: () => void; - avatar?: string; + community?: Amity.Community | null; heading?: React.ReactNode; subheading?: React.ReactNode; isOfficial?: boolean; @@ -28,7 +28,7 @@ const Header: React.FC< addStoryButton?: React.ReactNode; }> > = ({ - avatar, + community, heading, subheading, isHaveActions, @@ -46,11 +46,7 @@ const Header: React.FC< <div className={styles.viewStoryHeaderContainer}> <div className={styles.viewStoryHeadingInfoContainer}> <div className={styles.avatarContainer}> - <Avatar - data-qa-anchor="community_avatar" - avatar={avatar} - backgroundImage={communityBackgroundImage} - /> + <CommunityAvatar pageId="story_page" community={community} /> {haveStoryPermission && addStoryButton} </div> @@ -85,7 +81,11 @@ const Header: React.FC< /> )} {isHaveActions && <OverflowMenuButton pageId="story_page" onClick={onAction} />} - <CloseButton pageId="story_page" onClick={onClose} /> + <CloseButton + defaultClassName={clsx(styles.closeButton)} + pageId="story_page" + onClick={onClose} + /> </div> </div> ); diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.module.css b/src/v4/social/pages/DraftsPage/DraftsPage.module.css index 76fccc90d..89b0f0b94 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.module.css +++ b/src/v4/social/pages/DraftsPage/DraftsPage.module.css @@ -2,7 +2,7 @@ display: flex; flex-direction: column; width: 100%; - height: 100vh; + height: 100%; position: relative; font-family: var(--asc-text-global-font-family); font-style: var(--asc-text-global-font-style); diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index 210255e27..7ca171cec 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -2,10 +2,7 @@ import React, { useEffect, useState } from 'react'; import { useIntl } from 'react-intl'; import { extractColors } from 'extract-colors'; import { readFileAsync } from '~/helpers'; - -import styles from './DraftsPage.module.css'; import { SubmitHandler } from 'react-hook-form'; - import { AspectRatioButton, BackButton, @@ -15,15 +12,16 @@ import { } from '~/v4/social/elements'; import { useStoryContext } from '~/v4/social/providers/StoryProvider'; import { StoryRepository } from '@amityco/ts-sdk'; - import { HyperLinkConfig } from '~/v4/social/components'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; import { useNotifications } from '~/v4/core/providers/NotificationProvider'; -import { VideoPreview } from '~/v4/social/internal-components/VideoPreview'; import { useCommunityInfo } from '~/social/components/CommunityInfo/hooks'; import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; import { useNavigation } from '~/v4/core/providers/NavigationProvider'; +import styles from './DraftsPage.module.css'; +import { VideoPreview } from '~/v4/social/internal-components/VideoPreview'; + export type AmityStoryMediaType = { type: 'image'; url: string } | { type: 'video'; url: string }; export type AmityDraftStoryPageProps = { @@ -54,6 +52,7 @@ export const PlainDraftStoryPage = ({ }) => { const pageId = 'create_story_page'; const { file, setFile } = useStoryContext(); + const { community } = useCommunityInfo(targetId); const [isHyperLinkBottomSheetOpen, setIsHyperLinkBottomSheetOpen] = useState(false); const { confirm } = useConfirmContext(); const notification = useNotifications(); @@ -77,8 +76,6 @@ export const PlainDraftStoryPage = ({ setIsHyperLinkBottomSheetOpen(false); }; - const community = useCommunityInfo(targetId); - const { formatMessage } = useIntl(); const [imageMode, setImageMode] = useState<'fit' | 'fill'>('fit'); @@ -258,7 +255,8 @@ export const PlainDraftStoryPage = ({ /> </div> ) : mediaType?.type === 'video' ? ( - <video + <VideoPreview + mediaFit="contain" className={styles.videoPreview} src={file ? URL.createObjectURL(file) : mediaType.url} autoPlay @@ -285,8 +283,8 @@ export const PlainDraftStoryPage = ({ <div className={styles.footer}> <ShareStoryButton + community={community} pageId={pageId} - avatar={community.avatarFileUrl} onClick={() => onCreateStory(file, imageMode, {}, hyperLink[0]?.data?.url ? hyperLink : []) } diff --git a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx index c9050f734..1bce079b3 100644 --- a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx @@ -1,58 +1,67 @@ import React, { useEffect, useRef, useState } from 'react'; import useStories from '~/social/hooks/useStories'; - import useSDK from '~/core/hooks/useSDK'; - -import { useMedia } from 'react-use'; import { useIntl } from 'react-intl'; - import { FinalColor } from 'extract-colors/lib/types/Color'; import { StoryRepository } from '@amityco/ts-sdk'; -import { CreateStoryButton } from '../../elements'; +import { CreateNewStoryButton } from '~/v4/social/elements/CreateNewStoryButton'; import { Trash2Icon } from '~/icons'; import { isNonNullable } from '~/v4/helpers/utils'; import { extractColors } from 'extract-colors'; -import { - HiddenInput, - StoryArrowLeftButton, - StoryArrowRightButton, - StoryWrapper, - ViewStoryContainer, - ViewStoryContent, - ViewStoryOverlay, -} from '../../internal-components/StoryViewer/styles'; - import Stories from 'react-insta-stories'; -import { renderers } from '../../internal-components/StoryViewer/Renderers'; -import { AmityDraftStoryPage } from '..'; +import { renderers } from '~/v4/social/internal-components/StoryViewer/Renderers'; import { checkStoryPermission } from '~/utils'; -import { useStoryContext } from '../../providers/StoryProvider'; +import { useStoryContext } from '~/v4/social/providers/StoryProvider'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; import { useNotifications } from '~/v4/core/providers/NotificationProvider'; import { RendererObject, CustomRendererProps, -} from '../../internal-components/StoryViewer/Renderers/types'; +} from '~/v4/social/internal-components/StoryViewer/Renderers/types'; + +import clsx from 'clsx'; +import { ArrowLeftButton } from '~/v4/social/elements/ArrowLeftButton'; +import { ArrowRightButton } from '~/v4/social/elements/ArrowRightButton'; + +import styles from './StoryPage.module.css'; +import { useAmityPage } from '~/v4/core/hooks/uikit/index'; interface CommunityFeedStoryProps { + pageId?: string; communityId: string; onBack: () => void; onClose: (communityId: string) => void; onSwipeDown: (communityId: string) => void; onClickCommunity: (communityId: string) => void; + goToDraftStoryPage: ({ + targetId, + targetType, + mediaType, + storyType, + }: { + targetId: string; + targetType: string; + mediaType: any; + storyType: 'communityFeed' | 'globalFeed'; + }) => void; } const DURATION = 5000; export const CommunityFeedStory = ({ + pageId = '*', communityId, onBack, onClose, onSwipeDown, onClickCommunity, + goToDraftStoryPage, }: CommunityFeedStoryProps) => { + const { accessibilityId } = useAmityPage({ + pageId, + }); const { confirm } = useConfirmContext(); const notification = useNotifications(); @@ -101,7 +110,6 @@ export const CommunityFeedStory = ({ const { client, currentUserId } = useSDK(); const { formatMessage } = useIntl(); - const isMobile = useMedia('(max-width: 768px)'); const [currentIndex, setCurrentIndex] = useState(0); const { file, setFile } = useStoryContext(); @@ -111,8 +119,8 @@ export const CommunityFeedStory = ({ const isModerator = checkStoryPermission(client, communityId); const confirmDeleteStory = (storyId: string) => { - const isLastStory = currentIndex === stories.length - 1; confirm({ + pageId, title: formatMessage({ id: 'storyViewer.action.confirmModal.title' }), content: formatMessage({ id: 'storyViewer.action.confirmModal.content' }), okText: formatMessage({ id: 'delete' }), @@ -121,11 +129,7 @@ export const CommunityFeedStory = ({ notification.success({ content: formatMessage({ id: 'storyViewer.notification.deleted' }), }); - if (isLastStory && stories.length > 1) { - setCurrentIndex(currentIndex - 1); - } else if (stories.length === 1) { - onBack(); - } + if (stories.length === 1) onClose(communityId); }, }); }; @@ -187,9 +191,7 @@ export const CommunityFeedStory = ({ setFile(null); }; - const addStoryButton = ( - <CreateStoryButton pageId="story_page" componentId="*" onClick={handleAddIconClick} /> - ); + const addStoryButton = <CreateNewStoryButton pageId={pageId} onClick={handleAddIconClick} />; const formattedStories = stories?.map((story) => { const isImage = story?.dataType === 'image'; @@ -250,6 +252,17 @@ export const CommunityFeedStory = ({ setCurrentIndex(currentIndex + 1); }; + if (file) { + goToDraftStoryPage({ + targetId: communityId, + targetType: 'community', + mediaType: file.type.includes('image') + ? { type: 'image', url: URL.createObjectURL(file) } + : { type: 'video', url: URL.createObjectURL(file) }, + storyType: 'communityFeed', + }); + } + useEffect(() => { if (stories[stories.length - 1]?.syncState === 'syncing') { setCurrentIndex(stories.length - 1); @@ -260,6 +273,16 @@ export const CommunityFeedStory = ({ }, [currentIndex, stories]); useEffect(() => { + if (stories.every((story) => story?.isSeen)) return; + const firstUnseenStoryIndex = stories.findIndex((story) => !story?.isSeen); + + if (firstUnseenStoryIndex !== -1) { + setCurrentIndex(firstUnseenStoryIndex); + } + }, [stories]); + + useEffect(() => { + if (!stories || !file) return; const extractColorsFromImage = async (url: string) => { const colorsFromImage = await extractColors(url, { crossOrigin: 'anonymous', @@ -275,41 +298,28 @@ export const CommunityFeedStory = ({ } }, [stories, file, currentIndex]); - if (file) { - return ( - <AmityDraftStoryPage - mediaType={ - file.type.includes('image') - ? { type: 'image', url: URL.createObjectURL(file) } - : { type: 'video', url: URL.createObjectURL(file) } - } - targetId={communityId} - targetType="community" - /> - ); - } - return ( - <StoryWrapper data-qa-anchor="story_page"> - {!isMobile && ( - <StoryArrowLeftButton data-qa-anchor="arrow_left_button" onClick={previousStory} /> - )} - <ViewStoryContainer id={targetRootId}> - <HiddenInput + <div className={clsx(styles.storyWrapper)} data-qa-anchor={accessibilityId}> + <ArrowLeftButton onClick={previousStory} /> + <div id={targetRootId} className={clsx(styles.viewStoryContainer)}> + <input + className={clsx(styles.hiddenInput)} ref={fileInputRef} type="file" accept="image/*,video/*" onChange={handleFileChange} /> - <ViewStoryContent> - <ViewStoryOverlay /> + <div className={clsx(styles.viewStoryContent)}> + <div className={clsx(styles.overlayLeft)} onClick={previousStory} /> + <div className={clsx(styles.overlayRight)} onClick={nextStory} /> + <div className={clsx(styles.viewStoryOverlay)} /> {formattedStories?.length > 0 ? ( // NOTE: Do not use isPaused prop, it will cause the first video story skipped <Stories width="100%" height="100%" storyStyles={storyStyles} - preventDefault={!isMobile} + preventDefault currentIndex={currentIndex} stories={formattedStories} renderers={communityFeedRenderers as RendererObject[]} @@ -321,11 +331,9 @@ export const CommunityFeedStory = ({ onAllStoriesEnd={onBack} /> ) : null} - </ViewStoryContent> - </ViewStoryContainer> - {!isMobile && ( - <StoryArrowRightButton data-qa-anchor="arrow_right_button" onClick={nextStory} /> - )} - </StoryWrapper> + </div> + </div> + <ArrowRightButton onClick={nextStory} /> + </div> ); }; diff --git a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx index 86049a960..b824eb224 100644 --- a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx @@ -1,36 +1,34 @@ import React, { useEffect, useRef, useState } from 'react'; - import { useIntl } from 'react-intl'; import { FinalColor } from 'extract-colors/lib/types/Color'; import { StoryRepository } from '@amityco/ts-sdk'; -import { CreateStoryButton } from '~/v4/social/elements'; import { isNonNullable } from '~/v4/helpers/utils'; import { extractColors } from 'extract-colors'; - import Stories from 'react-insta-stories'; import { renderers } from '~/v4/social/internal-components/StoryViewer/Renderers'; import { checkStoryPermission } from '~/utils'; import { useStoryContext } from '~/v4/social/providers/StoryProvider'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; import { useNotifications } from '~/v4/core/providers/NotificationProvider'; - import { useGetActiveStoriesByTarget } from '~/v4/social/hooks/useGetActiveStories'; import { ArrowLeftButton } from '~/v4/social/elements/ArrowLeftButton/ArrowLeftButton'; import clsx from 'clsx'; import { ArrowRightButton } from '~/v4/social/elements/ArrowRightButton/ArrowRightButton'; - -import styles from './StoryPage.module.css'; - import useSDK from '~/v4/core/hooks/useSDK'; import { CustomRendererProps, RendererObject, } from '~/v4/social/internal-components/StoryViewer/Renderers/types'; import { TrashIcon } from '~/v4/social/icons'; +import { CreateNewStoryButton } from '~/v4/social/elements/CreateNewStoryButton'; + +import styles from './StoryPage.module.css'; +import { useAmityPage } from '~/v4/core/hooks/uikit/index'; const DURATION = 5000; interface GlobalFeedStoryProps { + pageId?: string; targetId: string; targetIds: string[]; onChangePage: () => void; @@ -47,6 +45,7 @@ interface GlobalFeedStoryProps { } export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ + pageId = '*', targetId, targetIds, onChangePage, @@ -56,6 +55,9 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ onSwipeDown, onClickCommunity, }) => { + const { accessibilityId } = useAmityPage({ + pageId, + }); const { confirm } = useConfirmContext(); const notification = useNotifications(); @@ -117,6 +119,7 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ const confirmDeleteStory = (storyId: string) => { const isLastStory = currentIndex === 0; confirm({ + pageId, title: formatMessage({ id: 'storyViewer.action.confirmModal.title' }), content: formatMessage({ id: 'storyViewer.action.confirmModal.content' }), okText: formatMessage({ id: 'delete' }), @@ -195,9 +198,7 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ setFile(null); }; - const addStoryButton = ( - <CreateStoryButton pageId="story_page" componentId="*" onClick={handleAddIconClick} /> - ); + const addStoryButton = <CreateNewStoryButton pageId={pageId} onClick={handleAddIconClick} />; const formattedStories = stories?.map((story) => { const isImage = story?.dataType === 'image'; @@ -285,6 +286,17 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ setCurrentIndex(currentIndex + 1); }; + if (file) { + goToDraftStoryPage({ + targetId, + targetType: 'community', + mediaType: file.type.includes('image') + ? { type: 'image', url: URL.createObjectURL(file) } + : { type: 'video', url: URL.createObjectURL(file) }, + storyType: 'globalFeed', + }); + } + useEffect(() => { if (stories[stories.length - 1]?.syncState === 'syncing') { setCurrentIndex(stories.length - 1); @@ -295,6 +307,16 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ }, [currentIndex, stories]); useEffect(() => { + if (stories.every((story) => story?.isSeen)) return; + const firstUnseenStoryIndex = stories.findIndex((story) => !story?.isSeen); + + if (firstUnseenStoryIndex !== -1) { + setCurrentIndex(firstUnseenStoryIndex); + } + }, [stories]); + + useEffect(() => { + if (!file || !stories) return; const extractColorsFromImage = async (url: string) => { const colorsFromImage = await extractColors(url, { crossOrigin: 'anonymous', @@ -310,19 +332,8 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ } }, [stories, file, currentIndex]); - if (file) { - goToDraftStoryPage({ - targetId, - targetType: 'community', - mediaType: file.type.includes('image') - ? { type: 'image', url: URL.createObjectURL(file) } - : { type: 'video', url: URL.createObjectURL(file) }, - storyType: 'globalFeed', - }); - } - return ( - <div className={clsx(styles.storyWrapper)} data-qa-anchor="story_page"> + <div className={clsx(styles.storyWrapper)} data-qa-anchor={accessibilityId}> <ArrowLeftButton onClick={previousStory} /> <div id={targetRootId} className={clsx(styles.viewStoryContainer)}> <input diff --git a/src/v4/social/pages/StoryPage/ViewGlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/ViewGlobalFeedStory.tsx index 0c72600fa..989008154 100644 --- a/src/v4/social/pages/StoryPage/ViewGlobalFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/ViewGlobalFeedStory.tsx @@ -4,6 +4,7 @@ import { GlobalFeedStory } from '~/v4/social/pages/StoryPage/GlobalFeedStory'; import { AmityStoryMediaType } from '~/v4/social/pages/DraftsPage/DraftsPage'; export const ViewGlobalFeedStoryPage = ({ + pageId = '*', targetId, onChangePage, onClickStory, @@ -12,8 +13,9 @@ export const ViewGlobalFeedStoryPage = ({ onSwipeDown, onClickCommunity, }: { + pageId?: string; targetId: string; - onChangePage: () => void; + onChangePage?: () => void; onClickStory: (targetId: string) => void; goToDraftStoryPage: ({ targetId, @@ -34,8 +36,10 @@ export const ViewGlobalFeedStoryPage = ({ seenState: 'smart' as Amity.StorySeenQuery, limit: 10, }); + return ( <GlobalFeedStory + pageId={pageId} targetId={targetId} targetIds={stories.map((s) => s.targetId)} onChangePage={onChangePage} diff --git a/src/v4/social/pages/StoryPage/ViewStoryPage.tsx b/src/v4/social/pages/StoryPage/ViewStoryPage.tsx index c51bd4106..041f7a54e 100644 --- a/src/v4/social/pages/StoryPage/ViewStoryPage.tsx +++ b/src/v4/social/pages/StoryPage/ViewStoryPage.tsx @@ -3,6 +3,7 @@ import { CommunityFeedStory } from '~/v4/social/pages/StoryPage/CommunityFeedSto import { useNavigation } from '~/v4/core/providers/NavigationProvider'; import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; import { ViewGlobalFeedStoryPage } from './ViewGlobalFeedStory'; +import { useAmityPage } from '~/v4/core/hooks/uikit/index'; type ViewStoryPageType = 'communityFeed' | 'globalFeed'; @@ -12,22 +13,28 @@ interface AmityViewStoryPageProps { } const ViewStoryPage: React.FC<AmityViewStoryPageProps> = ({ type, targetId }) => { + const pageId = 'story_page'; const { AmityStoryViewPageBehavior } = usePageBehavior(); const { onBack, goToViewStoryPage, goToDraftStoryPage, onClickCommunity } = useNavigation(); if (type === 'communityFeed') return ( <CommunityFeedStory + pageId={pageId} communityId={targetId} onBack={onBack} onClose={(communityId) => onClickCommunity(communityId)} onSwipeDown={(communityId) => onClickCommunity(communityId)} onClickCommunity={(communityId) => onClickCommunity(communityId)} + goToDraftStoryPage={({ targetId, targetType, mediaType, storyType }) => + goToDraftStoryPage({ targetId, targetType, mediaType, storyType }) + } /> ); if (type === 'globalFeed') return ( <ViewGlobalFeedStoryPage + pageId={pageId} targetId={targetId} onChangePage={() => AmityStoryViewPageBehavior.onCloseAction()} onClose={() => AmityStoryViewPageBehavior.onCloseAction()} From 30d7b1b943de1c20e2eda09e4569f1e051d76b63 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Thu, 20 Jun 2024 21:42:26 +0700 Subject: [PATCH 151/300] fix: ASC-21809 - upload video story on mobile device (#423) * fix: story view page onClose * fix: to v4 * fix: overlay bug * fix: remove console.log * fix: remove styled component * fix: add story arrow left right to customization provider * fix: view story full width and height * fix: notification v4 * fix: delete first multiple stories go to next story * fix: story tab background color * fix: update navigate * fix: styled to css module * fix: remove console.log * fix: remove console.log * fix: story tab * fix: type * fix: remove console.log * fix: story tab type * fix: noti css * fix: story avatar * fix: remove unused * fix: remove unused * fix: share story button use amityElement * fix: icon * fix: add default communityProfileImageBackground * fix: story tab community * fix: story tab * fix: navigate * fix: story navigate * fix: remove unused * fix: red border story tab * fix: remove ts-sdk peer dep * fix: delete comment * fix: i18n * fix: add margin-bottom to container * fix: remove packagemanager * fix: story ring empty state color * fix: story tab should navigate to unseen * fix: type * fix: import and type * fix: type * fix: background image * fix: element * fix: official badge condition * fix: condition * chore: add react-aria-component * fix: hooks * fix: community story tab condition * fix: remove unused * fix: create story button * fix: community story tab render condition * fix: delete community story condition * fix: discard create story navigate condition * fix: add create new story button * fix: export create new story button * chore: add ui story for create new story button * fix: share story button * fix: remove unused * fix: close button color * fix: remove unused * fix: remove unused function * fix: remove unused * fix: flicker render * fix: loading overlay width height * fix: draft page container * fix: remove inline function * fix: story wrapper height * fix: community story tab condition * fix: aspect ratio button to use useAmityElement * fix: elements * fix: remove inline function * fix: elements * fix: import and type * fix: remove error icon z-index * fix: official badge condition * fix: prop * fix: import * fix: community avatar * fix: condition * fix: story tab condition * fix: type * fix: story tab * fix: community --- package.json | 1 + pnpm-lock.yaml | 11859 +++++++++------- src/i18n/en.json | 1 + .../useCommunityModeratorsCollection.ts | 1 - .../components/StoryTab/StoryTabCommunity.tsx | 80 +- .../components/StoryTab/StoryTabItem.tsx | 2 +- .../ShareStoryButton/ShareStoryButton.tsx | 1 - ...n.tsx => useCommunityMembersCollection.ts} | 0 .../useCommunityModeratorsCollection.ts | 18 + ...ryTargets.tsx => useGlobalStoryTargets.ts} | 0 ...llection.tsx => useReactionsCollection.ts} | 0 src/v4/social/hooks/index.ts | 7 + src/v4/social/hooks/useCategoriesByIds.ts | 23 + ...aggedByMe.tsx => useCommentFlaggedByMe.ts} | 7 +- src/v4/social/hooks/useCommunityInfo.ts | 55 + src/v4/social/hooks/useCommunityPermission.ts | 21 + ...tsx => useCommunityStoriesSubscription.ts} | 0 ...tiveStories.tsx => useGetActiveStories.ts} | 0 ...yByStoryId.tsx => useGetStoryByStoryId.ts} | 0 19 files changed, 6858 insertions(+), 5218 deletions(-) rename src/v4/social/hooks/collections/{useCommunityMembersCollection.tsx => useCommunityMembersCollection.ts} (100%) create mode 100644 src/v4/social/hooks/collections/useCommunityModeratorsCollection.ts rename src/v4/social/hooks/collections/{useGlobalStoryTargets.tsx => useGlobalStoryTargets.ts} (100%) rename src/v4/social/hooks/collections/{useReactionsCollection.tsx => useReactionsCollection.ts} (100%) create mode 100644 src/v4/social/hooks/useCategoriesByIds.ts rename src/v4/social/hooks/{useCommentFlaggedByMe.tsx => useCommentFlaggedByMe.ts} (89%) create mode 100644 src/v4/social/hooks/useCommunityInfo.ts create mode 100644 src/v4/social/hooks/useCommunityPermission.ts rename src/v4/social/hooks/{useCommunityStoriesSubscription.tsx => useCommunityStoriesSubscription.ts} (100%) rename src/v4/social/hooks/{useGetActiveStories.tsx => useGetActiveStories.ts} (100%) rename src/v4/social/hooks/{useGetStoryByStoryId.tsx => useGetStoryByStoryId.ts} (100%) diff --git a/package.json b/package.json index d6ef5019c..99a0348e7 100644 --- a/package.json +++ b/package.json @@ -120,6 +120,7 @@ "millify": "^6.1.0", "modern-normalize": "^2.0.0", "polished": "^4.3.1", + "react-aria-components": "^1.2.1", "react-hook-form": "^7.49.2", "react-infinite-scroll-component": "^6.1.0", "react-insta-stories": "^2.6.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 17e545f4f..86303c353 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,6 +65,9 @@ importers: polished: specifier: ^4.3.1 version: 4.3.1 + react-aria-components: + specifier: ^1.2.1 + version: 1.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-hook-form: specifier: ^7.49.2 version: 7.52.0(react@18.3.1) @@ -1386,6 +1389,18 @@ packages: resolution: {integrity: sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==} engines: {node: '>=6.9.0'} + '@internationalized/date@3.5.4': + resolution: {integrity: sha512-qoVJVro+O0rBaw+8HPjUB1iH8Ihf8oziEnqMnvhJUSuVIrHOuZ6eNLHNvzXJKUvAtaDiqMnRlg8Z2mgh09BlUw==} + + '@internationalized/message@3.1.4': + resolution: {integrity: sha512-Dygi9hH1s7V9nha07pggCkvmRfDd3q2lWnMGvrJyrOwYMe1yj4D2T9BoH9I6MGR7xz0biQrtLPsqUkqXzIrBOw==} + + '@internationalized/number@3.5.3': + resolution: {integrity: sha512-rd1wA3ebzlp0Mehj5YTuTI50AQEx80gWFyHcQu+u91/5NgdwBecO8BH6ipPfE+lmQ9d63vpB3H9SHoIUiupllw==} + + '@internationalized/string@3.2.3': + resolution: {integrity: sha512-9kpfLoA8HegiWTeCbR2livhdVeKobCnVv8tlJ6M2jF+4tcMqDo94ezwlnrUANBWPgd8U7OXIHCk2Ov2qhk4KXw==} + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -1907,6905 +1922,8326 @@ packages: '@radix-ui/rect@1.0.1': resolution: {integrity: sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==} - '@react-aria/ssr@3.9.4': - resolution: {integrity: sha512-4jmAigVq409qcJvQyuorsmBR4+9r3+JEC60wC+Y0MZV0HCtTmm8D9guYXlJMdx0SSkgj0hHAyFm/HvPNFofCoQ==} - engines: {node: '>= 12'} + '@react-aria/breadcrumbs@3.5.13': + resolution: {integrity: sha512-G1Gqf/P6kVdfs94ovwP18fTWuIxadIQgHsXS08JEVcFVYMjb9YjqnEBaohUxD1tq2WldMbYw53ahQblT4NTG+g==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@react-aria/utils@3.17.0': - resolution: {integrity: sha512-NEul0cQ6tQPdNSHYzNYD+EfFabeYNvDwEiHB82kK/Tsfhfm84SM+baben/at2N51K7iRrJPr5hC5fi4+P88lNg==} + '@react-aria/button@3.9.5': + resolution: {integrity: sha512-dgcYR6j8WDOMLKuVrtxzx4jIC05cVKDzc+HnPO8lNkBAOfjcuN5tkGRtIjLtqjMvpZHhQT5aDbgFpIaZzxgFIg==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@react-stately/utils@3.10.1': - resolution: {integrity: sha512-VS/EHRyicef25zDZcM/ClpzYMC5i2YGN6uegOeQawmgfGjb02yaCX0F0zR69Pod9m2Hr3wunTbtpgVXvYbZItg==} + '@react-aria/calendar@3.5.8': + resolution: {integrity: sha512-Whlp4CeAA5/ZkzrAHUv73kgIRYjw088eYGSc+cvSOCxfrc/2XkBm9rNrnSBv0DvhJ8AG0Fjz3vYakTmF3BgZBw==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@react-types/shared@3.23.1': - resolution: {integrity: sha512-5d+3HbFDxGZjhbMBeFHRQhexMFt4pUce3okyRtUVKbbedQFUrtXSBg9VszgF2RTeQDKDkMCIQDtz5ccP/Lk1gw==} + '@react-aria/checkbox@3.14.3': + resolution: {integrity: sha512-EtBJL6iu0gvrw3A4R7UeVLR6diaVk/mh4kFBc7c8hQjpEJweRr4hmJT3hrNg3MBcTWLxFiMEXPGgWEwXDBygtA==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@remix-run/router@1.16.1': - resolution: {integrity: sha512-es2g3dq6Nb07iFxGk5GuHN20RwBZOsuDQN7izWIisUcv9r+d2C5jQxqmgkdebXgReWfiyUabcki6Fg77mSNrig==} - engines: {node: '>=14.0.0'} + '@react-aria/color@3.0.0-beta.33': + resolution: {integrity: sha512-nhqnIHYm5p6MbuF3cC6lnqzG7MjwBsBd0DtpO+ByFYO+zxpMMbeC5R+1SFxvapR4uqmAzTotbtiUCGsG+SUaIg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@rollup/pluginutils@5.1.0': - resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==} - engines: {node: '>=14.0.0'} + '@react-aria/combobox@3.9.1': + resolution: {integrity: sha512-SpK92dCmT8qn8aEcUAihRQrBb5LZUhwIbDExFII8PvUvEFy/PoQHXIo3j1V29WkutDBDpMvBv/6XRCHGXPqrhQ==} peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@rollup/rollup-android-arm-eabi@4.18.0': - resolution: {integrity: sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==} - cpu: [arm] - os: [android] + '@react-aria/datepicker@3.10.1': + resolution: {integrity: sha512-4HZL593nrNMa1GjBmWEN/OTvNS6d3/16G1YJWlqiUlv11ADulSbqBIjMmkgwrJVFcjrgqtXFy+yyrTA/oq94Zw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@rollup/rollup-android-arm64@4.18.0': - resolution: {integrity: sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==} - cpu: [arm64] - os: [android] + '@react-aria/dialog@3.5.14': + resolution: {integrity: sha512-oqDCjQ8hxe3GStf48XWBf2CliEnxlR9GgSYPHJPUc69WBj68D9rVcCW3kogJnLAnwIyf3FnzbX4wSjvUa88sAQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@rollup/rollup-darwin-arm64@4.18.0': - resolution: {integrity: sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==} - cpu: [arm64] - os: [darwin] + '@react-aria/dnd@3.6.1': + resolution: {integrity: sha512-6WnujUTD+cIYZVF/B+uXdHyJ+WSpbYa8jH282epvY4FUAq1qLmen12/HHcoj/5dswKQe8X6EM3OhkQM89d9vFw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@rollup/rollup-darwin-x64@4.18.0': - resolution: {integrity: sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==} - cpu: [x64] - os: [darwin] + '@react-aria/focus@3.17.1': + resolution: {integrity: sha512-FLTySoSNqX++u0nWZJPPN5etXY0WBxaIe/YuL/GTEeuqUIuC/2bJSaw5hlsM6T2yjy6Y/VAxBcKSdAFUlU6njQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@rollup/rollup-linux-arm-gnueabihf@4.18.0': - resolution: {integrity: sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==} - cpu: [arm] - os: [linux] + '@react-aria/form@3.0.5': + resolution: {integrity: sha512-n290jRwrrRXO3fS82MyWR+OKN7yznVesy5Q10IclSTVYHHI3VI53xtAPr/WzNjJR1um8aLhOcDNFKwnNIUUCsQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@rollup/rollup-linux-arm-musleabihf@4.18.0': - resolution: {integrity: sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==} - cpu: [arm] - os: [linux] + '@react-aria/grid@3.9.1': + resolution: {integrity: sha512-fGEZqAEaS8mqzV/II3N4ndoNWegIcbh+L3PmKbXdpKKUP8VgMs/WY5rYl5WAF0f5RoFwXqx3ibDLeR9tKj/bOg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@rollup/rollup-linux-arm64-gnu@4.18.0': - resolution: {integrity: sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==} - cpu: [arm64] - os: [linux] + '@react-aria/gridlist@3.8.1': + resolution: {integrity: sha512-vVPkkA+Ct0NDcpnNm/tnYaBumg0fP9pXxsPLqL1rxvsTyj1PaIpFTZ4corabPTbTDExZwUSTS3LG1n+o1OvBtQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@rollup/rollup-linux-arm64-musl@4.18.0': - resolution: {integrity: sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==} - cpu: [arm64] - os: [linux] + '@react-aria/i18n@3.11.1': + resolution: {integrity: sha512-vuiBHw1kZruNMYeKkTGGnmPyMnM5T+gT8bz97H1FqIq1hQ6OPzmtBZ6W6l6OIMjeHI5oJo4utTwfZl495GALFQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@rollup/rollup-linux-powerpc64le-gnu@4.18.0': - resolution: {integrity: sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==} - cpu: [ppc64] - os: [linux] + '@react-aria/interactions@3.21.3': + resolution: {integrity: sha512-BWIuf4qCs5FreDJ9AguawLVS0lV9UU+sK4CCnbCNNmYqOWY+1+gRXCsnOM32K+oMESBxilAjdHW5n1hsMqYMpA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@rollup/rollup-linux-riscv64-gnu@4.18.0': - resolution: {integrity: sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==} - cpu: [riscv64] - os: [linux] + '@react-aria/label@3.7.8': + resolution: {integrity: sha512-MzgTm5+suPA3KX7Ug6ZBK2NX9cin/RFLsv1BdafJ6CZpmUSpWnGE/yQfYUB7csN7j31OsZrD3/P56eShYWAQfg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@rollup/rollup-linux-s390x-gnu@4.18.0': - resolution: {integrity: sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==} - cpu: [s390x] - os: [linux] + '@react-aria/link@3.7.1': + resolution: {integrity: sha512-a4IaV50P3fXc7DQvEIPYkJJv26JknFbRzFT5MJOMgtzuhyJoQdILEUK6XHYjcSSNCA7uLgzpojArVk5Hz3lCpw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@rollup/rollup-linux-x64-gnu@4.18.0': - resolution: {integrity: sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==} - cpu: [x64] - os: [linux] + '@react-aria/listbox@3.12.1': + resolution: {integrity: sha512-7JiUp0NGykbv/HgSpmTY1wqhuf/RmjFxs1HZcNaTv8A+DlzgJYc7yQqFjP3ZA/z5RvJFuuIxggIYmgIFjaRYdA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@rollup/rollup-linux-x64-musl@4.18.0': - resolution: {integrity: sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==} - cpu: [x64] - os: [linux] + '@react-aria/live-announcer@3.3.4': + resolution: {integrity: sha512-w8lxs35QrRrn6pBNzVfyGOeqWdxeVKf9U6bXIVwhq7rrTqRULL8jqy8RJIMfIs1s8G5FpwWYjyBOjl2g5Cu1iA==} - '@rollup/rollup-win32-arm64-msvc@4.18.0': - resolution: {integrity: sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==} - cpu: [arm64] - os: [win32] + '@react-aria/menu@3.14.1': + resolution: {integrity: sha512-BYliRb38uAzq05UOFcD5XkjA5foQoXRbcH3ZufBsc4kvh79BcP1PMW6KsXKGJ7dC/PJWUwCui6QL1kUg8PqMHA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@rollup/rollup-win32-ia32-msvc@4.18.0': - resolution: {integrity: sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==} - cpu: [ia32] - os: [win32] + '@react-aria/meter@3.4.13': + resolution: {integrity: sha512-oG6KvHQM3ri93XkYQkgEaMKSMO9KNDVpcW1MUqFfqyUXHFBRZRrJB4BTXMZ4nyjheFVQjVboU51fRwoLjOzThg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@rollup/rollup-win32-x64-msvc@4.18.0': - resolution: {integrity: sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==} - cpu: [x64] - os: [win32] + '@react-aria/numberfield@3.11.3': + resolution: {integrity: sha512-QQ9ZTzBbRI8d9ksaBWm6YVXbgv+5zzUsdxVxwzJVXLznvivoORB8rpdFJzUEWVCo25lzoBxluCEPYtLOxP1B0w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@sinclair/typebox@0.27.8': - resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + '@react-aria/overlays@3.22.1': + resolution: {integrity: sha512-GHiFMWO4EQ6+j6b5QCnNoOYiyx1Gk8ZiwLzzglCI4q1NY5AG2EAmfU4Z1+Gtrf2S5Y0zHbumC7rs9GnPoGLUYg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@sinonjs/commons@3.0.1': - resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + '@react-aria/progress@3.4.13': + resolution: {integrity: sha512-YBV9bOO5JzKvG8QCI0IAA00o6FczMgIDiK8Q9p5gKorFMatFUdRayxlbIPoYHMi+PguLil0jHgC7eOyaUcrZ0g==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@sinonjs/fake-timers@10.3.0': - resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + '@react-aria/radio@3.10.4': + resolution: {integrity: sha512-3fmoMcQtCpgjTwJReFjnvIE/C7zOZeCeWUn4JKDqz9s1ILYsC3Rk5zZ4q66tFn6v+IQnecrKT52wH6+hlVLwTA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/addon-a11y@7.6.19': - resolution: {integrity: sha512-92SOapbXukkO0RlrA0+8qa61NlCGSUFM7n5DfAA6tobIfhBAhfsEi7I9Q/0F95SCAgJoVVQdeIGKdvHjxO8DAg==} + '@react-aria/searchfield@3.7.5': + resolution: {integrity: sha512-h1sMUOWjhevaKKUHab/luHbM6yiyeN57L4RxZU0IIc9Ww0h5Rp2GUuKZA3pcdPiExHje0aijcImL3wBHEbKAzw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/addon-actions@7.6.19': - resolution: {integrity: sha512-ATLrA5QKFJt7tIAScRHz5T3eBQ+RG3jaZk08L7gChvyQZhei8knWwePElZ7GaWbCr9BgznQp1lQUUXq/UUblAQ==} + '@react-aria/select@3.14.5': + resolution: {integrity: sha512-s8jixBuTUNdKWRHe2tIJqp55ORHeUObGMw1s7PQRRVrrHPdNSYseAOI9B2W7qpl3hKhvjJg40UW+45mcb1WKbw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/addon-backgrounds@7.6.19': - resolution: {integrity: sha512-Nu3LAZODRSV2e5bOroKm/Jp6BIFzwu/nJxD5OvLWkkwNCh+vDXUFbbaVrZf5xRL+fHd9iLFPtWbJQpF/w7UsCw==} + '@react-aria/selection@3.18.1': + resolution: {integrity: sha512-GSqN2jX6lh7v+ldqhVjAXDcrWS3N4IsKXxO6L6Ygsye86Q9q9Mq9twWDWWu5IjHD6LoVZLUBCMO+ENGbOkyqeQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/addon-controls@7.6.19': - resolution: {integrity: sha512-cl6PCNEwihDjuWIUsKTyDNKk+/IE4J3oMbSY5AZV/9Z0jJbpMV2shVm5DMZm5LhCCVcu5obWcxCIa4FMIMJAMQ==} + '@react-aria/separator@3.3.13': + resolution: {integrity: sha512-hofA6JCPnAOqSE9vxnq7Dkazr7Kb2A0I5sR16fOG7ddjYRc/YEY5Nv7MWfKUGU0kNFHkgNjsDAILERtLechzeA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/addon-docs@7.6.19': - resolution: {integrity: sha512-nv+9SR/NOtM8Od2esOXHcg0NQT8Pk8BMUyGwZu5Q3MLI4JxNVEG65dY0IP2j6Knc4UtlvQTpM0f7m5xp4seHjQ==} + '@react-aria/slider@3.7.8': + resolution: {integrity: sha512-MYvPcM0K8jxEJJicUK2+WxUkBIM/mquBxOTOSSIL3CszA80nXIGVnLlCUnQV3LOUzpWtabbWaZokSPtGgOgQOw==} peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/addon-essentials@7.6.19': - resolution: {integrity: sha512-SC33ZEQ5YaOt9wDkrdZmwQgqPWo9om/gqnyif06eug3SwrTe9JjO5iq1PIBfQodLD9MAxr9cwBvO0NG505oszQ==} + '@react-aria/spinbutton@3.6.5': + resolution: {integrity: sha512-0aACBarF/Xr/7ixzjVBTQ0NBwwwsoGkf5v6AVFVMTC0uYMXHTALvRs+ULHjHMa5e/cX/aPlEvaVT7jfSs+Xy9Q==} peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/addon-highlight@7.6.19': - resolution: {integrity: sha512-/pApl0oiVU1CQ8xETRNDLDthMBjeTmvFnTRq8RJ9m0JYTrSsoyHDmj9zS4K1k9gReqijE7brslhP8d2tblBpNw==} + '@react-aria/ssr@3.9.4': + resolution: {integrity: sha512-4jmAigVq409qcJvQyuorsmBR4+9r3+JEC60wC+Y0MZV0HCtTmm8D9guYXlJMdx0SSkgj0hHAyFm/HvPNFofCoQ==} + engines: {node: '>= 12'} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/addon-measure@7.6.19': - resolution: {integrity: sha512-n+cfhVXXouBv9oQr3a77vvip5dTznaNoBDWMafP2ohauc8jBlAxeBwCjk5r3pyThMRIFCTG/ypZrhiJcSJT3bw==} + '@react-aria/switch@3.6.4': + resolution: {integrity: sha512-2nVqz4ZuJyof47IpGSt3oZRmp+EdS8wzeDYgf42WHQXrx4uEOk1mdLJ20+NnsYhj/2NHZsvXVrjBeKMjlMs+0w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/addon-outline@7.6.19': - resolution: {integrity: sha512-Tt4MrfjK5j/Mdh8nJ8ccVyh78Dy7aiEPxO31YVvr5XUkge0pDi1PX328mHRDPur0i56NM8ssVbekWBZr+9MxlA==} + '@react-aria/table@3.14.1': + resolution: {integrity: sha512-WaPgQe4zQF5OaluO5rm+Y2nEoFR63vsLd4BT4yjK1uaFhKhDY2Zk+1SCVQvBLLKS4WK9dhP05nrNzT0vp/ZPOw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/addon-toolbars@7.6.19': - resolution: {integrity: sha512-+qGbPP2Vo/HoPiS4EJopZ127HGculCV74Hkz6ot7ob6AkYdA1yLMPzWns/ZXNIWm6ab3jV+iq+mQCM/i1qJzvA==} + '@react-aria/tabs@3.9.1': + resolution: {integrity: sha512-S5v/0sRcOaSXaJYZuuy1ZVzYc7JD4sDyseG1133GjyuNjJOFHgoWMb+b4uxNIJbZxnLgynn/ZDBZSO+qU+fIxw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/addon-viewport@7.6.19': - resolution: {integrity: sha512-OQQtJ2kYwImbvE9QiC3I3yR0O0EBgNjq+XSaSS4ixJrvUyesfuB7Lm7RkubhEEiP4yANi9OlbzsqZelmPOnk6w==} + '@react-aria/tag@3.4.1': + resolution: {integrity: sha512-gcIGPYZ2OBwMT4IHnlczEezKlxr0KRPL/mSfm2Q91GE027ZGOJnqusH9az6DX1qxrQx8x3vRdqYT2KmuefkrBQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/blocks@7.6.19': - resolution: {integrity: sha512-/c/bVQRmyRPoviJhPrFdLfubRcrnZWTwkjxsCvrOTJ/UDOyEl0t/H8yY1mGq7KWWTdbIznnZWhAIofHnH4/Esw==} + '@react-aria/textfield@3.14.5': + resolution: {integrity: sha512-hj7H+66BjB1iTKKaFXwSZBZg88YT+wZboEXZ0DNdQB2ytzoz/g045wBItUuNi4ZjXI3P+0AOZznVMYadWBAmiA==} peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/builder-manager@7.6.19': - resolution: {integrity: sha512-Dt5OLh97xeWh4h2mk9uG0SbCxBKHPhIiHLHAKEIDzIZBdwUhuyncVNDPHW2NlXM+S7U0/iKs2tw05waqh2lHvg==} + '@react-aria/toggle@3.10.4': + resolution: {integrity: sha512-bRk+CdB8QzrSyGNjENXiTWxfzYKRw753iwQXsEAU7agPCUdB8cZJyrhbaUoD0rwczzTp2zDbZ9rRbUPdsBE2YQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/builder-vite@7.6.19': - resolution: {integrity: sha512-llYpfYCHQCD0nPy+5J+H67iKcOpBrexIFO13wXxHQyl27Z+1T2JJj4cHqZs5S3a2XLiwf4df44NBvvwV5cmJmQ==} + '@react-aria/toolbar@3.0.0-beta.5': + resolution: {integrity: sha512-c8spY7aeLI6L+ygdXvEbAzaT41vExsxZ1Ld0t7BB+6iEF3nyBNJHshjkgdR7nv8FLgNk0no4tj0GTq4Jj4UqHQ==} peerDependencies: - '@preact/preset-vite': '*' - typescript: '>= 4.3.x' - vite: ^3.0.0 || ^4.0.0 || ^5.0.0 - vite-plugin-glimmerx: '*' - peerDependenciesMeta: - '@preact/preset-vite': - optional: true - typescript: - optional: true - vite-plugin-glimmerx: - optional: true + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/channels@7.6.17': - resolution: {integrity: sha512-GFG40pzaSxk1hUr/J/TMqW5AFDDPUSu+HkeE/oqSWJbOodBOLJzHN6CReJS6y1DjYSZLNFt1jftPWZZInG/XUA==} + '@react-aria/tooltip@3.7.4': + resolution: {integrity: sha512-+XRx4HlLYqWY3fB8Z60bQi/rbWDIGlFUtXYbtoa1J+EyRWfhpvsYImP8qeeNO/vgjUtDy1j9oKa8p6App9mBMQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/channels@7.6.19': - resolution: {integrity: sha512-2JGh+i95GwjtjqWqhtEh15jM5ifwbRGmXeFqkY7dpdHH50EEWafYHr2mg3opK3heVDwg0rJ/VBptkmshloXuvA==} + '@react-aria/tree@3.0.0-alpha.1': + resolution: {integrity: sha512-CucyeJ4VeAvWO5UJHt/l9JO65CVtsOVUctMOVNCQS77Isqp3olX9pvfD3LXt8fD5Ph2g0Q/b7siVpX5ieVB32g==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/cli@7.6.19': - resolution: {integrity: sha512-7OVy7nPgkLfgivv6/dmvoyU6pKl9EzWFk+g9izyQHiM/jS8jOiEyn6akG8Ebj6k5pWslo5lgiXUSW+cEEZUnqQ==} - hasBin: true + '@react-aria/utils@3.17.0': + resolution: {integrity: sha512-NEul0cQ6tQPdNSHYzNYD+EfFabeYNvDwEiHB82kK/Tsfhfm84SM+baben/at2N51K7iRrJPr5hC5fi4+P88lNg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/client-api@7.6.17': - resolution: {integrity: sha512-rsxKBRLtUmBXbxG79Pf1GzUuMDMsFdhNR/a5k7kIA/mlEsvWD8are/aH/zk1oLr7+5QOqEkiXLL6+Erry7dzXA==} + '@react-aria/utils@3.24.1': + resolution: {integrity: sha512-O3s9qhPMd6n42x9sKeJ3lhu5V1Tlnzhu6Yk8QOvDuXf7UGuUjXf9mzfHJt1dYzID4l9Fwm8toczBzPM9t0jc8Q==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/client-logger@7.6.17': - resolution: {integrity: sha512-6WBYqixAXNAXlSaBWwgljWpAu10tPRBJrcFvx2gPUne58EeMM20Gi/iHYBz2kMCY+JLAgeIH7ZxInqwO8vDwiQ==} + '@react-aria/visually-hidden@3.8.12': + resolution: {integrity: sha512-Bawm+2Cmw3Xrlr7ARzl2RLtKh0lNUdJ0eNqzWcyx4c0VHUAWtThmH5l+HRqFUGzzutFZVo89SAy40BAbd0gjVw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/client-logger@7.6.19': - resolution: {integrity: sha512-oGzOxbmLmciSIfd5gsxDzPmX8DttWhoYdPKxjMuCuWLTO2TWpkCWp1FTUMWO72mm/6V/FswT/aqpJJBBvdZ3RQ==} + '@react-stately/calendar@3.5.1': + resolution: {integrity: sha512-7l7QhqGUJ5AzWHfvZzbTe3J4t72Ht5BmhW4hlVI7flQXtfrmYkVtl3ZdytEZkkHmWGYZRW9b4IQTQGZxhtlElA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/codemod@7.6.19': - resolution: {integrity: sha512-bmHE0iEEgWZ65dXCmasd+GreChjPiWkXu2FEa0cJmNz/PqY12GsXGls4ke1TkNTj4gdSZnbtJxbclPZZnib2tQ==} + '@react-stately/checkbox@3.6.5': + resolution: {integrity: sha512-IXV3f9k+LtmfQLE+DKIN41Q5QB/YBLDCB1YVx5PEdRp52S9+EACD5683rjVm8NVRDwjMi2SP6RnFRk7fVb5Azg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/components@7.6.19': - resolution: {integrity: sha512-8Zw/RQ4crzKkUR7ojxvRIj8vktKiBBO8Nq93qv4JfDqDWrcR7cro0hOlZgmZmrzbFunBBt6WlsNNO6nVP7R4Xw==} + '@react-stately/collections@3.10.7': + resolution: {integrity: sha512-KRo5O2MWVL8n3aiqb+XR3vP6akmHLhLWYZEmPKjIv0ghQaEebBTrN3wiEjtd6dzllv0QqcWvDLM1LntNfJ2TsA==} peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/core-client@7.6.19': - resolution: {integrity: sha512-F0V9nzcEnj6DIpnw2ilrxsV4d9ibyyQS+Wi2uQtXy+wCQQm9PeBVqrOywjXAY2F9pcoftXOaepfhp8jrxX4MXw==} + '@react-stately/color@3.6.1': + resolution: {integrity: sha512-iW0nAhl3+fUBegHMw5EcAbFVDpgwHBrivfC85pVoTM3pyzp66hqNN6R6xWxW6ETyljS8UOer59+/w4GDVGdPig==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/core-common@7.6.19': - resolution: {integrity: sha512-njwpGzFJrfbJr/AFxGP8KMrfPfxN85KOfSlxYnQwRm5Z0H1D/lT33LhEBf5m37gaGawHeG7KryxO6RvaioMt2Q==} + '@react-stately/combobox@3.8.4': + resolution: {integrity: sha512-iLVGvKRRz0TeJXZhZyK783hveHpYA6xovOSdzSD+WGYpiPXo1QrcrNoH3AE0Z2sHtorU+8nc0j58vh5PB+m2AA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/core-events@7.6.17': - resolution: {integrity: sha512-AriWMCm/k1cxlv10f+jZ1wavThTRpLaN3kY019kHWbYT9XgaSuLU67G7GPr3cGnJ6HuA6uhbzu8qtqVCd6OfXA==} + '@react-stately/data@3.11.4': + resolution: {integrity: sha512-PbnUQxeE6AznSuEWYnRmrYQ9t5z1Asx98Jtrl96EeA6Iapt9kOjTN9ySqCxtPxMKleb1NIqG3+uHU3veIqmLsg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/core-events@7.6.19': - resolution: {integrity: sha512-K/W6Uvum0ocZSgjbi8hiotpe+wDEHDZlvN+KlPqdh9ae9xDK8aBNBq9IelCoqM+uKO1Zj+dDfSQds7CD781DJg==} + '@react-stately/datepicker@3.9.4': + resolution: {integrity: sha512-yBdX01jn6gq4NIVvHIqdjBUPo+WN8Bujc4OnPw+ZnfA4jI0eIgq04pfZ84cp1LVXW0IB0VaCu1AlQ/kvtZjfGA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/core-server@7.6.19': - resolution: {integrity: sha512-7mKL73Wv5R2bEl0kJ6QJ9bOu5YY53Idu24QgvTnUdNsQazp2yUONBNwHIrNDnNEXm8SfCi4Mc9o0mmNRMIoiRA==} - - '@storybook/csf-plugin@7.6.19': - resolution: {integrity: sha512-yUP0xfJyR8e6fmCgKoEt4c1EvslF8dZ8wtwVLE5hnC3kfs7xt8RVDiKLB/9NhYjY3mD/oOesX60HqRXDgJQHwA==} + '@react-stately/dnd@3.3.1': + resolution: {integrity: sha512-I/Ci5xB8hSgAXzoWYWScfMM9UK1MX/eTlARBhiSlfudewweOtNJAI+cXJgU7uiUnGjh4B4v3qDBtlAH1dWDCsw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/csf-tools@7.6.19': - resolution: {integrity: sha512-8Vzia3cHhDdGHuS3XKXJReCRxmfRq3vmTm/Te9yKZnPSAsC58CCKcMh8FNEFJ44vxYF9itKTkRutjGs+DprKLQ==} + '@react-stately/flags@3.0.3': + resolution: {integrity: sha512-/ha7XFA0RZTQsbzSPwu3KkbNMgbvuM0GuMTYLTBWpgBrovBNTM+QqI/PfZTdHg8PwCYF4H5Y8gjdSpdulCvJFw==} - '@storybook/csf@0.1.8': - resolution: {integrity: sha512-Ntab9o7LjBCbFIao5l42itFiaSh/Qu+l16l/r/9qmV9LnYZkO+JQ7tzhdlwpgJfhs+B5xeejpdAtftDRyXNajw==} + '@react-stately/form@3.0.3': + resolution: {integrity: sha512-92YYBvlHEWUGUpXgIaQ48J50jU9XrxfjYIN8BTvvhBHdD63oWgm8DzQnyT/NIAMzdLnhkg7vP+fjG8LjHeyIAg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/docs-mdx@0.1.0': - resolution: {integrity: sha512-JDaBR9lwVY4eSH5W8EGHrhODjygPd6QImRbwjAuJNEnY0Vw4ie3bPkeGfnacB3OBW6u/agqPv2aRlR46JcAQLg==} + '@react-stately/grid@3.8.7': + resolution: {integrity: sha512-he3TXCLAhF5C5z1/G4ySzcwyt7PEiWcVIupxebJQqRyFrNWemSuv+7tolnStmG8maMVIyV3P/3j4eRBbdSlOIg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/docs-tools@7.6.19': - resolution: {integrity: sha512-JuwV6wtm7Hb7Kb5ValChfxy4J7XngfrSQNpvwsDCSBNVcQUv2y843hvclpa26Ptfr/c7zpUX8r9FGSaMDy+2aQ==} + '@react-stately/list@3.10.5': + resolution: {integrity: sha512-fV9plO+6QDHiewsYIhboxcDhF17GO95xepC5ki0bKXo44gr14g/LSo/BMmsaMnV+1BuGdBunB05bO4QOIaigXA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/global@5.0.0': - resolution: {integrity: sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==} + '@react-stately/menu@3.7.1': + resolution: {integrity: sha512-mX1w9HHzt+xal1WIT2xGrTQsoLvDwuB2R1Er1MBABs//MsJzccycatcgV/J/28m6tO5M9iuFQQvLV+i1dCtodg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/manager-api@7.6.19': - resolution: {integrity: sha512-dVCx1Q+HZEA4U08XqYljiG88BeS3I3ahnPAQLZAeWQXQRkoc9G2jMgLNPKYPIqEtq7Xrn6SRlFMIofhwWrwZpg==} + '@react-stately/numberfield@3.9.3': + resolution: {integrity: sha512-UlPTLSabhLEuHtgzM0PgfhtEaHy3yttbzcRb8yHNvGo4KbCHeHpTHd3QghKfTFm024Mug7+mVlWCmMtW0f5ttg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/manager@7.6.19': - resolution: {integrity: sha512-fZWQcf59x4P0iiBhrL74PZrqKJAPuk9sWjP8BIkGbf8wTZtUunbY5Sv4225fOL4NLJbuX9/RYLUPoxQ3nucGHA==} + '@react-stately/overlays@3.6.7': + resolution: {integrity: sha512-6zp8v/iNUm6YQap0loaFx6PlvN8C0DgWHNlrlzMtMmNuvjhjR0wYXVaTfNoUZBWj25tlDM81ukXOjpRXg9rLrw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/mdx2-csf@1.1.0': - resolution: {integrity: sha512-TXJJd5RAKakWx4BtpwvSNdgTDkKM6RkXU8GK34S/LhidQ5Pjz3wcnqb0TxEkfhK/ztbP8nKHqXFwLfa2CYkvQw==} + '@react-stately/radio@3.10.4': + resolution: {integrity: sha512-kCIc7tAl4L7Hu4Wt9l2jaa+MzYmAJm0qmC8G8yPMbExpWbLRu6J8Un80GZu+JxvzgDlqDyrVvyv9zFifwH/NkQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/node-logger@7.6.19': - resolution: {integrity: sha512-2g29QC44Zl1jKY37DmQ0/dO7+VSKnGgPI/x0mwVwQffypSapxH3rwLLT5Q5XLHeFyD+fhRu5w9Cj4vTGynJgpA==} + '@react-stately/searchfield@3.5.3': + resolution: {integrity: sha512-H0OvlgwPIFdc471ypw79MDjz3WXaVq9+THaY6JM4DIohEJNN5Dwei7O9g6r6m/GqPXJIn5TT3b74kJ2Osc00YQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/postinstall@7.6.19': - resolution: {integrity: sha512-s6p1vpgMfn+QGDfCK2YNdyyWKidUgb3nGicB81FANRyzYqGB//QlJlghEc2LKCIQbGIZQiwP3l8PdZQmczEJRw==} + '@react-stately/select@3.6.4': + resolution: {integrity: sha512-whZgF1N53D0/dS8tOFdrswB0alsk5Q5620HC3z+5f2Hpi8gwgAZ8TYa+2IcmMYRiT+bxVuvEc/NirU9yPmqGbA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/preview-api@7.6.17': - resolution: {integrity: sha512-wLfDdI9RWo1f2zzFe54yRhg+2YWyxLZvqdZnSQ45mTs4/7xXV5Wfbv3QNTtcdw8tT3U5KRTrN1mTfTCiRJc0Kw==} + '@react-stately/selection@3.15.1': + resolution: {integrity: sha512-6TQnN9L0UY9w19B7xzb1P6mbUVBtW840Cw1SjgNXCB3NPaCf59SwqClYzoj8O2ZFzMe8F/nUJtfU1NS65/OLlw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/preview-api@7.6.19': - resolution: {integrity: sha512-04hdMSQucroJT4dBjQzRd7ZwH2hij8yx2nm5qd4HYGkd1ORkvlH6GOLph4XewNJl5Um3xfzFQzBhvkqvG0WaCQ==} + '@react-stately/slider@3.5.4': + resolution: {integrity: sha512-Jsf7K17dr93lkNKL9ij8HUcoM1sPbq8TvmibD6DhrK9If2lje+OOL8y4n4qreUnfMT56HCAeS9wCO3fg3eMyrw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/preview@7.6.19': - resolution: {integrity: sha512-VqRPua2koOQTOteB+VvuKNXFYQ7IDEopaPpj9Nx+3kom+bqp0hWdAysWcm6CtKN2GGzBQm+5PvGibMNdawsaVg==} + '@react-stately/table@3.11.8': + resolution: {integrity: sha512-EdyRW3lT1/kAVDp5FkEIi1BQ7tvmD2YgniGdLuW/l9LADo0T+oxZqruv60qpUS6sQap+59Riaxl91ClDxrJnpg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/react-dom-shim@7.6.19': - resolution: {integrity: sha512-tpt2AC1428d1gF4fetMkpkeFZ1WdDr1CLKoLbSInWQZ7i96nbnIMIA9raR/W8ai1bo55KSz9Bq5ytC/1Pac2qQ==} + '@react-stately/tabs@3.6.6': + resolution: {integrity: sha512-sOLxorH2uqjAA+v1ppkMCc2YyjgqvSGeBDgtR/lyPSDd4CVMoTExszROX2dqG0c8il9RQvzFuufUtQWMY6PgSA==} peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/react-vite@7.6.19': - resolution: {integrity: sha512-TqKQvWi53vE0KbWrlNq61cTLpzfQ5QMZv42YiwEUhM7ysSmrrg6WjgfEnvEyiAuY8yyaRspPF6Y8pYTKGHM8Nw==} - engines: {node: '>=16'} + '@react-stately/toggle@3.7.4': + resolution: {integrity: sha512-CoYFe9WrhLkDP4HGDpJYQKwfiYCRBAeoBQHv+JWl5eyK61S8xSwoHsveYuEZ3bowx71zyCnNAqWRrmNOxJ4CKA==} peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - vite: ^3.0.0 || ^4.0.0 || ^5.0.0 + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/react@7.6.19': - resolution: {integrity: sha512-uKShAAp1/pRki1YnRjBveH/jAD3f8V0W2WP1LxTQqnKVFkl01mTbDZ/9ZIK6rVTSILUlmsk3fwsNyRbOKVgBGQ==} - engines: {node: '>=16.0.0'} + '@react-stately/tooltip@3.4.9': + resolution: {integrity: sha512-P7CDJsdoKarz32qFwf3VNS01lyC+63gXpDZG31pUu+EO5BeQd4WKN/AH1Beuswpr4GWzxzFc1aXQgERFGVzraA==} peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/router@7.6.19': - resolution: {integrity: sha512-q2/AvY8rG0znFEfbg50OIhkS5yQ6OmyzdCdztoEsDDdsbq87YPmsDj7k8Op1EkTa2T5CB8XhBOCQDtcj7gUUtg==} + '@react-stately/tree@3.8.1': + resolution: {integrity: sha512-LOdkkruJWch3W89h4B/bXhfr0t0t1aRfEp+IMrrwdRAl23NaPqwl5ILHs4Xu5XDHqqhg8co73pHrJwUyiTWEjw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/telemetry@7.6.19': - resolution: {integrity: sha512-rA5xum4I36M57iiD3uzmW0MOdpl0vEpHWBSAa5hK0a0ALPeY9TgAsQlI/0dSyNYJ/K7aczEEN6d4qm1NC4u10A==} + '@react-stately/utils@3.10.1': + resolution: {integrity: sha512-VS/EHRyicef25zDZcM/ClpzYMC5i2YGN6uegOeQawmgfGjb02yaCX0F0zR69Pod9m2Hr3wunTbtpgVXvYbZItg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/theming@7.6.19': - resolution: {integrity: sha512-sAho13MmtA80ctOaLn8lpkQBsPyiqSdLcOPH5BWFhatQzzBQCpTAKQk+q/xGju8bNiPZ+yQBaBzbN8SfX8ceCg==} + '@react-stately/virtualizer@3.7.1': + resolution: {integrity: sha512-voHgE6EQ+oZaLv6u2umKxakvIKNkCQuUihqKACTjdslp7SJh4Mvs3oLBI0hf0JOh+rCcFIKDvQtFwy1fXFRYBA==} peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/types@7.6.17': - resolution: {integrity: sha512-GRY0xEJQ0PrL7DY2qCNUdIfUOE0Gsue6N+GBJw9ku1IUDFLJRDOF+4Dx2BvYcVCPI5XPqdWKlEyZdMdKjiQN7Q==} + '@react-types/breadcrumbs@3.7.5': + resolution: {integrity: sha512-lV9IDYsMiu2TgdMIjEmsOE0YWwjb3jhUNK1DCZZfq6uWuiHLgyx2EncazJBUWSjHJ4ta32j7xTuXch+8Ai6u/A==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@storybook/types@7.6.19': - resolution: {integrity: sha512-DeGYrRPRMGTVfT7o2rEZtRzyLT2yKTI2exgpnxbwPWEFAduZCSfzBrcBXZ/nb5B0pjA9tUNWls1YzGkJGlkhpg==} + '@react-types/button@3.9.4': + resolution: {integrity: sha512-raeQBJUxBp0axNF74TXB8/H50GY8Q3eV6cEKMbZFP1+Dzr09Ngv0tJBeW0ewAxAguNH5DRoMUAUGIXtSXskVdA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@swc/helpers@0.4.14': - resolution: {integrity: sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==} + '@react-types/calendar@3.4.6': + resolution: {integrity: sha512-WSntZPwtvsIYWvBQRAPvuCn55UTJBZroTvX0vQvWykJRQnPAI20G1hMQ3dNsnAL+gLZUYxBXn66vphmjUuSYew==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@swc/helpers@0.4.36': - resolution: {integrity: sha512-5lxnyLEYFskErRPenYItLRSge5DjrJngYKdVjRSrWfza9G6KkgHEXi0vUZiyUeMU5JfXH1YnvXZzSp8ul88o2Q==} + '@react-types/checkbox@3.8.1': + resolution: {integrity: sha512-5/oVByPw4MbR/8QSdHCaalmyWC71H/QGgd4aduTJSaNi825o+v/hsN2/CH7Fq9atkLKsC8fvKD00Bj2VGaKriQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@swc/helpers@0.5.11': - resolution: {integrity: sha512-YNlnKRWF2sVojTpIyzwou9XoTNbzbzONwRhOoniEioF1AtaitTvVZblaQRrAzChWQ1bLYyYSWzM18y4WwgzJ+A==} + '@react-types/color@3.0.0-beta.25': + resolution: {integrity: sha512-D24ASvLeSWouBwOBi4ftUe4/BhrZj5AiHV7tXwrVeMGOy9Z9jyeK65Xysq+R3ecaSONLXsgai5CQMvj13cOacA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@tanstack/query-core@5.45.0': - resolution: {integrity: sha512-RVfIZQmFUTdjhSAAblvueimfngYyfN6HlwaJUPK71PKd7yi43Vs1S/rdimmZedPWX/WGppcq/U1HOj7O7FwYxw==} + '@react-types/combobox@3.11.1': + resolution: {integrity: sha512-UNc3OHt5cUt5gCTHqhQIqhaWwKCpaNciD8R7eQazmHiA9fq8ROlV+7l3gdNgdhJbTf5Bu/V5ISnN7Y1xwL3zqQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@tanstack/react-query@5.45.1': - resolution: {integrity: sha512-mYYfJujKg2kxmkRRjA6nn4YKG3ITsKuH22f1kteJ5IuVQqgKUgbaSQfYwVP0gBS05mhwxO03HVpD0t7BMN7WOA==} + '@react-types/datepicker@3.7.4': + resolution: {integrity: sha512-ZfvgscvNzBJpYyVWg3nstJtA/VlWLwErwSkd1ivZYam859N30w8yH+4qoYLa6FzWLCFlrsRHyvtxlEM7lUAt5A==} peerDependencies: - react: ^18.0.0 + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@types/babel__core@7.20.5': - resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + '@react-types/dialog@3.5.10': + resolution: {integrity: sha512-S9ga+edOLNLZw7/zVOnZdT5T40etpzUYBXEKdFPbxyPYnERvRxJAsC1/ASuBU9fQAXMRgLZzADWV+wJoGS/X9g==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@types/babel__generator@7.6.8': - resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} + '@react-types/form@3.7.4': + resolution: {integrity: sha512-HZojAWrb6feYnhDEOy3vBamDVAHDl0l2JQZ7aIDLHmeTAGQC3JNZcm2fLTxqLye46zz8w8l8OHgI+NdD4PHdOw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@types/babel__template@7.4.4': - resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + '@react-types/grid@3.2.6': + resolution: {integrity: sha512-XfHenL2jEBUYrhKiPdeM24mbLRXUn79wVzzMhrNYh24nBwhsPPpxF+gjFddT3Cy8dt6tRInfT6pMEu9nsXwaHw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@types/babel__traverse@7.20.6': - resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} + '@react-types/link@3.5.5': + resolution: {integrity: sha512-G6P5WagHDR87npN7sEuC5IIgL1GsoY4WFWKO4734i2CXRYx24G9P0Su3AX4GA3qpspz8sK1AWkaCzBMmvnunfw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@types/body-parser@1.19.5': - resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} + '@react-types/listbox@3.4.9': + resolution: {integrity: sha512-S5G+WmNKUIOPZxZ4svWwWQupP3C6LmVfnf8QQmPDvwYXGzVc0WovkqUWyhhjJirFDswTXRCO9p0yaTHHIlkdwQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@types/connect@3.4.38': - resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + '@react-types/menu@3.9.9': + resolution: {integrity: sha512-FamUaPVs1Fxr4KOMI0YcR2rYZHoN7ypGtgiEiJ11v/tEPjPPGgeKDxii0McCrdOkjheatLN1yd2jmMwYj6hTDg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@types/cross-spawn@6.0.6': - resolution: {integrity: sha512-fXRhhUkG4H3TQk5dBhQ7m/JDdSNHKwR2BBia62lhwEIq9xGiQKLxd6LymNhn47SjXhsUEPmxi+PKw2OkW4LLjA==} + '@react-types/meter@3.4.1': + resolution: {integrity: sha512-AIJV4NDFAqKH94s02c5Da4TH2qgJjfrw978zuFM0KUBFD85WRPKh7MvgWpomvUgmzqE6lMCzIdi1KPKqrRabdw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@types/detect-port@1.3.5': - resolution: {integrity: sha512-Rf3/lB9WkDfIL9eEKaSYKc+1L/rNVYBjThk22JTqQw0YozXarX8YljFAz+HCoC6h4B4KwCMsBPZHaFezwT4BNA==} + '@react-types/numberfield@3.8.3': + resolution: {integrity: sha512-z5fGfVj3oh5bmkw9zDvClA1nDBSFL9affOuyk2qZ/M2SRUmykDAPCksbfcMndft0XULWKbF4s2CYbVI+E/yrUA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@types/doctrine@0.0.3': - resolution: {integrity: sha512-w5jZ0ee+HaPOaX25X2/2oGR/7rgAQSYII7X7pp0m9KgBfMP7uKfMfTvcpl5Dj+eDBbpxKGiqE+flqDr6XTd2RA==} + '@react-types/overlays@3.8.7': + resolution: {integrity: sha512-zCOYvI4at2DkhVpviIClJ7bRrLXYhSg3Z3v9xymuPH3mkiuuP/dm8mUCtkyY4UhVeUTHmrQh1bzaOP00A+SSQA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@types/doctrine@0.0.9': - resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==} + '@react-types/progress@3.5.4': + resolution: {integrity: sha512-JNc246sTjasPyx5Dp7/s0rp3Bz4qlu4LrZTulZlxWyb53WgBNL7axc26CCi+I20rWL9+c7JjhrRxnLl/1cLN5g==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@types/ejs@3.1.5': - resolution: {integrity: sha512-nv+GSx77ZtXiJzwKdsASqi+YQ5Z7vwHsTP0JY2SiQgjGckkBRKZnk8nIM+7oUZ1VCtuTz0+By4qVR7fqzp/Dfg==} + '@react-types/radio@3.8.1': + resolution: {integrity: sha512-bK0gio/qj1+0Ldu/3k/s9BaOZvnnRgvFtL3u5ky479+aLG5qf1CmYed3SKz8ErZ70JkpuCSrSwSCFf0t1IHovw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@types/emscripten@1.39.13': - resolution: {integrity: sha512-cFq+fO/isvhvmuP/+Sl4K4jtU6E23DoivtbO4r50e3odaxAiVdbfSYRDdJ4gCdxx+3aRjhphS5ZMwIH4hFy/Cw==} + '@react-types/searchfield@3.5.5': + resolution: {integrity: sha512-T/NHg12+w23TxlXMdetogLDUldk1z5dDavzbnjKrLkajLb221bp8brlR/+O6C1CtFpuJGALqYHgTasU1qkQFSA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@types/escodegen@0.0.6': - resolution: {integrity: sha512-AjwI4MvWx3HAOaZqYsjKWyEObT9lcVV0Y0V8nXo6cXzN8ZiMxVhf6F3d/UNvXVGKrEzL/Dluc5p+y9GkzlTWig==} + '@react-types/select@3.9.4': + resolution: {integrity: sha512-xI7dnOW2st91fPPcv6hdtrTdcfetYiqZuuVPZ5TRobY7Q10/Zqqe/KqtOw1zFKUj9xqNJe4Ov3xP5GSdcO60Eg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@types/eslint-scope@3.7.7': - resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} + '@react-types/shared@3.23.1': + resolution: {integrity: sha512-5d+3HbFDxGZjhbMBeFHRQhexMFt4pUce3okyRtUVKbbedQFUrtXSBg9VszgF2RTeQDKDkMCIQDtz5ccP/Lk1gw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@types/eslint@8.56.10': - resolution: {integrity: sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==} + '@react-types/slider@3.7.3': + resolution: {integrity: sha512-F8qFQaD2mqug2D0XeWMmjGBikiwbdERFlhFzdvNGbypPLz3AZICBKp1ZLPWdl0DMuy03G/jy6Gl4mDobl7RT2g==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@types/estree@0.0.51': - resolution: {integrity: sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==} + '@react-types/switch@3.5.3': + resolution: {integrity: sha512-Nb6+J5MrPaFa8ZNFKGMzAsen/NNzl5UG/BbC65SLGPy7O0VDa/sUpn7dcu8V2xRpRwwIN/Oso4v63bt2sgdkgA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@types/estree@1.0.5': - resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + '@react-types/table@3.9.5': + resolution: {integrity: sha512-fgM2j9F/UR4Anmd28CueghCgBwOZoCVyN8fjaIFPd2MN4gCwUUfANwxLav65gZk4BpwUXGoQdsW+X50L3555mg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@types/express-serve-static-core@4.19.3': - resolution: {integrity: sha512-KOzM7MhcBFlmnlr/fzISFF5vGWVSvN6fTd4T+ExOt08bA/dA5kpSzY52nMsI1KDFmUREpJelPYyuslLRSjjgCg==} + '@react-types/tabs@3.3.7': + resolution: {integrity: sha512-ZdLe5xOcFX6+/ni45Dl2jO0jFATpTnoSqj6kLIS/BYv8oh0n817OjJkLf+DS3CLfNjApJWrHqAk34xNh6nRnEg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@types/express@4.17.21': - resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} + '@react-types/textfield@3.9.3': + resolution: {integrity: sha512-DoAY6cYOL0pJhgNGI1Rosni7g72GAt4OVr2ltEx2S9ARmFZ0DBvdhA9lL2nywcnKMf27PEJcKMXzXc10qaHsJw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@types/find-cache-dir@3.2.1': - resolution: {integrity: sha512-frsJrz2t/CeGifcu/6uRo4b+SzAwT4NYCVPu1GN8IB9XTzrpPkGuV0tmh9mN+/L0PklAlsC3u5Fxt0ju00LXIw==} + '@react-types/tooltip@3.4.9': + resolution: {integrity: sha512-wZ+uF1+Zc43qG+cOJzioBmLUNjRa7ApdcT0LI1VvaYvH5GdfjzUJOorLX9V/vAci0XMJ50UZ+qsh79aUlw2yqg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@types/glob@7.2.0': - resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} + '@remix-run/router@1.16.1': + resolution: {integrity: sha512-es2g3dq6Nb07iFxGk5GuHN20RwBZOsuDQN7izWIisUcv9r+d2C5jQxqmgkdebXgReWfiyUabcki6Fg77mSNrig==} + engines: {node: '>=14.0.0'} - '@types/graceful-fs@4.1.9': - resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} + '@rollup/pluginutils@5.1.0': + resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true - '@types/hoist-non-react-statics@3.3.5': - resolution: {integrity: sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==} + '@rollup/rollup-android-arm-eabi@4.18.0': + resolution: {integrity: sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==} + cpu: [arm] + os: [android] - '@types/http-errors@2.0.4': - resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} + '@rollup/rollup-android-arm64@4.18.0': + resolution: {integrity: sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==} + cpu: [arm64] + os: [android] - '@types/istanbul-lib-coverage@2.0.6': - resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + '@rollup/rollup-darwin-arm64@4.18.0': + resolution: {integrity: sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==} + cpu: [arm64] + os: [darwin] - '@types/istanbul-lib-report@3.0.3': - resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + '@rollup/rollup-darwin-x64@4.18.0': + resolution: {integrity: sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==} + cpu: [x64] + os: [darwin] - '@types/istanbul-reports@3.0.4': - resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + '@rollup/rollup-linux-arm-gnueabihf@4.18.0': + resolution: {integrity: sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==} + cpu: [arm] + os: [linux] - '@types/jest@29.5.12': - resolution: {integrity: sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==} + '@rollup/rollup-linux-arm-musleabihf@4.18.0': + resolution: {integrity: sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==} + cpu: [arm] + os: [linux] - '@types/js-cookie@2.2.7': - resolution: {integrity: sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA==} + '@rollup/rollup-linux-arm64-gnu@4.18.0': + resolution: {integrity: sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==} + cpu: [arm64] + os: [linux] - '@types/json-schema@7.0.15': - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@rollup/rollup-linux-arm64-musl@4.18.0': + resolution: {integrity: sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==} + cpu: [arm64] + os: [linux] - '@types/json5@0.0.29': - resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@rollup/rollup-linux-powerpc64le-gnu@4.18.0': + resolution: {integrity: sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==} + cpu: [ppc64] + os: [linux] - '@types/lodash@4.17.5': - resolution: {integrity: sha512-MBIOHVZqVqgfro1euRDWX7OO0fBVUUMrN6Pwm8LQsz8cWhEpihlvR70ENj3f40j58TNxZaWv2ndSkInykNBBJw==} + '@rollup/rollup-linux-riscv64-gnu@4.18.0': + resolution: {integrity: sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==} + cpu: [riscv64] + os: [linux] - '@types/mdx@2.0.13': - resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} + '@rollup/rollup-linux-s390x-gnu@4.18.0': + resolution: {integrity: sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==} + cpu: [s390x] + os: [linux] - '@types/mime-types@2.1.4': - resolution: {integrity: sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==} + '@rollup/rollup-linux-x64-gnu@4.18.0': + resolution: {integrity: sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==} + cpu: [x64] + os: [linux] - '@types/mime@1.3.5': - resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + '@rollup/rollup-linux-x64-musl@4.18.0': + resolution: {integrity: sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==} + cpu: [x64] + os: [linux] - '@types/minimatch@3.0.5': - resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==} + '@rollup/rollup-win32-arm64-msvc@4.18.0': + resolution: {integrity: sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==} + cpu: [arm64] + os: [win32] - '@types/minimatch@5.1.2': - resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} + '@rollup/rollup-win32-ia32-msvc@4.18.0': + resolution: {integrity: sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==} + cpu: [ia32] + os: [win32] - '@types/minimist@1.2.5': - resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} + '@rollup/rollup-win32-x64-msvc@4.18.0': + resolution: {integrity: sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==} + cpu: [x64] + os: [win32] - '@types/node-fetch@2.6.11': - resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==} + '@sinclair/typebox@0.27.8': + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - '@types/node@18.19.36': - resolution: {integrity: sha512-tX1BNmYSWEvViftB26VLNxT6mEr37M7+ldUtq7rlKnv4/2fKYsJIOmqJAjT6h1DNuwQjIKgw3VJ/Dtw3yiTIQw==} + '@sinonjs/commons@3.0.1': + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} - '@types/node@20.14.4': - resolution: {integrity: sha512-1ChboN+57suCT2t/f8lwtPY/k3qTpuD/qnqQuYoBg6OQOcPyaw7PiZVdGpaZYAvhDDtqrt0oAaM8+oSu1xsUGw==} + '@sinonjs/fake-timers@10.3.0': + resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} - '@types/normalize-package-data@2.4.4': - resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + '@storybook/addon-a11y@7.6.19': + resolution: {integrity: sha512-92SOapbXukkO0RlrA0+8qa61NlCGSUFM7n5DfAA6tobIfhBAhfsEi7I9Q/0F95SCAgJoVVQdeIGKdvHjxO8DAg==} - '@types/postcss-modules-local-by-default@4.0.2': - resolution: {integrity: sha512-CtYCcD+L+trB3reJPny+bKWKMzPfxEyQpKIwit7kErnOexf5/faaGpkFy4I5AwbV4hp1sk7/aTg0tt0B67VkLQ==} + '@storybook/addon-actions@7.6.19': + resolution: {integrity: sha512-ATLrA5QKFJt7tIAScRHz5T3eBQ+RG3jaZk08L7gChvyQZhei8knWwePElZ7GaWbCr9BgznQp1lQUUXq/UUblAQ==} - '@types/postcss-modules-scope@3.0.4': - resolution: {integrity: sha512-//ygSisVq9kVI0sqx3UPLzWIMCmtSVrzdljtuaAEJtGoGnpjBikZ2sXO5MpH9SnWX9HRfXxHifDAXcQjupWnIQ==} + '@storybook/addon-backgrounds@7.6.19': + resolution: {integrity: sha512-Nu3LAZODRSV2e5bOroKm/Jp6BIFzwu/nJxD5OvLWkkwNCh+vDXUFbbaVrZf5xRL+fHd9iLFPtWbJQpF/w7UsCw==} - '@types/pretty-hrtime@1.0.3': - resolution: {integrity: sha512-nj39q0wAIdhwn7DGUyT9irmsKK1tV0bd5WFEhgpqNTMFZ8cE+jieuTphCW0tfdm47S2zVT5mr09B28b1chmQMA==} + '@storybook/addon-controls@7.6.19': + resolution: {integrity: sha512-cl6PCNEwihDjuWIUsKTyDNKk+/IE4J3oMbSY5AZV/9Z0jJbpMV2shVm5DMZm5LhCCVcu5obWcxCIa4FMIMJAMQ==} - '@types/prop-types@15.7.12': - resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} + '@storybook/addon-docs@7.6.19': + resolution: {integrity: sha512-nv+9SR/NOtM8Od2esOXHcg0NQT8Pk8BMUyGwZu5Q3MLI4JxNVEG65dY0IP2j6Knc4UtlvQTpM0f7m5xp4seHjQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - '@types/qs@6.9.15': - resolution: {integrity: sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==} + '@storybook/addon-essentials@7.6.19': + resolution: {integrity: sha512-SC33ZEQ5YaOt9wDkrdZmwQgqPWo9om/gqnyif06eug3SwrTe9JjO5iq1PIBfQodLD9MAxr9cwBvO0NG505oszQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - '@types/range-parser@1.2.7': - resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + '@storybook/addon-highlight@7.6.19': + resolution: {integrity: sha512-/pApl0oiVU1CQ8xETRNDLDthMBjeTmvFnTRq8RJ9m0JYTrSsoyHDmj9zS4K1k9gReqijE7brslhP8d2tblBpNw==} - '@types/react-helmet@6.1.11': - resolution: {integrity: sha512-0QcdGLddTERotCXo3VFlUSWO3ztraw8nZ6e3zJSgG7apwV5xt+pJUS8ewPBqT4NYB1optGLprNQzFleIY84u/g==} + '@storybook/addon-measure@7.6.19': + resolution: {integrity: sha512-n+cfhVXXouBv9oQr3a77vvip5dTznaNoBDWMafP2ohauc8jBlAxeBwCjk5r3pyThMRIFCTG/ypZrhiJcSJT3bw==} - '@types/react-infinite-scroller@1.2.5': - resolution: {integrity: sha512-fJU1jhMgoL6NJFrqTM0Ob7tnd2sQWGxe2ESwiU6FZWbJK/VO/Er5+AOhc+e2zbT0dk5pLygqctsulOLJ8xnSzw==} + '@storybook/addon-outline@7.6.19': + resolution: {integrity: sha512-Tt4MrfjK5j/Mdh8nJ8ccVyh78Dy7aiEPxO31YVvr5XUkge0pDi1PX328mHRDPur0i56NM8ssVbekWBZr+9MxlA==} - '@types/react-mentions@4.1.13': - resolution: {integrity: sha512-kRulAAjlmhCtsJ9bapO0foocknaE/rEuFKpmFEU81fBfnXZmZNBaJ9J/DBjwigT3WDHjQVUmYoi5sxEXrcdzAw==} + '@storybook/addon-toolbars@7.6.19': + resolution: {integrity: sha512-+qGbPP2Vo/HoPiS4EJopZ127HGculCV74Hkz6ot7ob6AkYdA1yLMPzWns/ZXNIWm6ab3jV+iq+mQCM/i1qJzvA==} - '@types/react-timeago@4.1.7': - resolution: {integrity: sha512-ogD4Ror/hDG+pQggCX+TgPgJ8W2jeeUxsgNU485Qpm0Ma+E2TND2EJuKwK5+sxlkDXDEgsHradO0zWBkTgLzNg==} + '@storybook/addon-viewport@7.6.19': + resolution: {integrity: sha512-OQQtJ2kYwImbvE9QiC3I3yR0O0EBgNjq+XSaSS4ixJrvUyesfuB7Lm7RkubhEEiP4yANi9OlbzsqZelmPOnk6w==} - '@types/react@17.0.80': - resolution: {integrity: sha512-LrgHIu2lEtIo8M7d1FcI3BdwXWoRQwMoXOZ7+dPTW0lYREjmlHl3P0U1VD0i/9tppOuv8/sam7sOjx34TxSFbA==} + '@storybook/blocks@7.6.19': + resolution: {integrity: sha512-/c/bVQRmyRPoviJhPrFdLfubRcrnZWTwkjxsCvrOTJ/UDOyEl0t/H8yY1mGq7KWWTdbIznnZWhAIofHnH4/Esw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - '@types/resolve@1.20.6': - resolution: {integrity: sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==} + '@storybook/builder-manager@7.6.19': + resolution: {integrity: sha512-Dt5OLh97xeWh4h2mk9uG0SbCxBKHPhIiHLHAKEIDzIZBdwUhuyncVNDPHW2NlXM+S7U0/iKs2tw05waqh2lHvg==} - '@types/scheduler@0.16.8': - resolution: {integrity: sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==} + '@storybook/builder-vite@7.6.19': + resolution: {integrity: sha512-llYpfYCHQCD0nPy+5J+H67iKcOpBrexIFO13wXxHQyl27Z+1T2JJj4cHqZs5S3a2XLiwf4df44NBvvwV5cmJmQ==} + peerDependencies: + '@preact/preset-vite': '*' + typescript: '>= 4.3.x' + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 + vite-plugin-glimmerx: '*' + peerDependenciesMeta: + '@preact/preset-vite': + optional: true + typescript: + optional: true + vite-plugin-glimmerx: + optional: true - '@types/semver@7.5.8': - resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} + '@storybook/channels@7.6.17': + resolution: {integrity: sha512-GFG40pzaSxk1hUr/J/TMqW5AFDDPUSu+HkeE/oqSWJbOodBOLJzHN6CReJS6y1DjYSZLNFt1jftPWZZInG/XUA==} - '@types/send@0.17.4': - resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} + '@storybook/channels@7.6.19': + resolution: {integrity: sha512-2JGh+i95GwjtjqWqhtEh15jM5ifwbRGmXeFqkY7dpdHH50EEWafYHr2mg3opK3heVDwg0rJ/VBptkmshloXuvA==} - '@types/serve-static@1.15.7': - resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==} + '@storybook/cli@7.6.19': + resolution: {integrity: sha512-7OVy7nPgkLfgivv6/dmvoyU6pKl9EzWFk+g9izyQHiM/jS8jOiEyn6akG8Ebj6k5pWslo5lgiXUSW+cEEZUnqQ==} + hasBin: true - '@types/stack-utils@2.0.3': - resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + '@storybook/client-api@7.6.17': + resolution: {integrity: sha512-rsxKBRLtUmBXbxG79Pf1GzUuMDMsFdhNR/a5k7kIA/mlEsvWD8are/aH/zk1oLr7+5QOqEkiXLL6+Erry7dzXA==} - '@types/styled-components@5.1.34': - resolution: {integrity: sha512-mmiVvwpYklFIv9E8qfxuPyIt/OuyIrn6gMOAMOFUO3WJfSrSE+sGUoa4PiZj77Ut7bKZpaa6o1fBKS/4TOEvnA==} + '@storybook/client-logger@7.6.17': + resolution: {integrity: sha512-6WBYqixAXNAXlSaBWwgljWpAu10tPRBJrcFvx2gPUne58EeMM20Gi/iHYBz2kMCY+JLAgeIH7ZxInqwO8vDwiQ==} - '@types/stylis@4.2.5': - resolution: {integrity: sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==} + '@storybook/client-logger@7.6.19': + resolution: {integrity: sha512-oGzOxbmLmciSIfd5gsxDzPmX8DttWhoYdPKxjMuCuWLTO2TWpkCWp1FTUMWO72mm/6V/FswT/aqpJJBBvdZ3RQ==} - '@types/unist@2.0.10': - resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==} + '@storybook/codemod@7.6.19': + resolution: {integrity: sha512-bmHE0iEEgWZ65dXCmasd+GreChjPiWkXu2FEa0cJmNz/PqY12GsXGls4ke1TkNTj4gdSZnbtJxbclPZZnib2tQ==} - '@types/uuid@9.0.8': - resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==} + '@storybook/components@7.6.19': + resolution: {integrity: sha512-8Zw/RQ4crzKkUR7ojxvRIj8vktKiBBO8Nq93qv4JfDqDWrcR7cro0hOlZgmZmrzbFunBBt6WlsNNO6nVP7R4Xw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - '@types/yargs-parser@21.0.3': - resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + '@storybook/core-client@7.6.19': + resolution: {integrity: sha512-F0V9nzcEnj6DIpnw2ilrxsV4d9ibyyQS+Wi2uQtXy+wCQQm9PeBVqrOywjXAY2F9pcoftXOaepfhp8jrxX4MXw==} - '@types/yargs@17.0.32': - resolution: {integrity: sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==} + '@storybook/core-common@7.6.19': + resolution: {integrity: sha512-njwpGzFJrfbJr/AFxGP8KMrfPfxN85KOfSlxYnQwRm5Z0H1D/lT33LhEBf5m37gaGawHeG7KryxO6RvaioMt2Q==} - '@typescript-eslint/eslint-plugin@6.21.0': - resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@storybook/core-events@7.6.17': + resolution: {integrity: sha512-AriWMCm/k1cxlv10f+jZ1wavThTRpLaN3kY019kHWbYT9XgaSuLU67G7GPr3cGnJ6HuA6uhbzu8qtqVCd6OfXA==} - '@typescript-eslint/parser@6.21.0': - resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@storybook/core-events@7.6.19': + resolution: {integrity: sha512-K/W6Uvum0ocZSgjbi8hiotpe+wDEHDZlvN+KlPqdh9ae9xDK8aBNBq9IelCoqM+uKO1Zj+dDfSQds7CD781DJg==} - '@typescript-eslint/scope-manager@5.62.0': - resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@storybook/core-server@7.6.19': + resolution: {integrity: sha512-7mKL73Wv5R2bEl0kJ6QJ9bOu5YY53Idu24QgvTnUdNsQazp2yUONBNwHIrNDnNEXm8SfCi4Mc9o0mmNRMIoiRA==} - '@typescript-eslint/scope-manager@6.21.0': - resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} - engines: {node: ^16.0.0 || >=18.0.0} + '@storybook/csf-plugin@7.6.19': + resolution: {integrity: sha512-yUP0xfJyR8e6fmCgKoEt4c1EvslF8dZ8wtwVLE5hnC3kfs7xt8RVDiKLB/9NhYjY3mD/oOesX60HqRXDgJQHwA==} - '@typescript-eslint/type-utils@6.21.0': - resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@storybook/csf-tools@7.6.19': + resolution: {integrity: sha512-8Vzia3cHhDdGHuS3XKXJReCRxmfRq3vmTm/Te9yKZnPSAsC58CCKcMh8FNEFJ44vxYF9itKTkRutjGs+DprKLQ==} - '@typescript-eslint/types@5.62.0': - resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@storybook/csf@0.1.8': + resolution: {integrity: sha512-Ntab9o7LjBCbFIao5l42itFiaSh/Qu+l16l/r/9qmV9LnYZkO+JQ7tzhdlwpgJfhs+B5xeejpdAtftDRyXNajw==} - '@typescript-eslint/types@6.21.0': - resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} - engines: {node: ^16.0.0 || >=18.0.0} + '@storybook/docs-mdx@0.1.0': + resolution: {integrity: sha512-JDaBR9lwVY4eSH5W8EGHrhODjygPd6QImRbwjAuJNEnY0Vw4ie3bPkeGfnacB3OBW6u/agqPv2aRlR46JcAQLg==} - '@typescript-eslint/typescript-estree@5.62.0': - resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@storybook/docs-tools@7.6.19': + resolution: {integrity: sha512-JuwV6wtm7Hb7Kb5ValChfxy4J7XngfrSQNpvwsDCSBNVcQUv2y843hvclpa26Ptfr/c7zpUX8r9FGSaMDy+2aQ==} - '@typescript-eslint/typescript-estree@6.21.0': - resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@storybook/global@5.0.0': + resolution: {integrity: sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==} - '@typescript-eslint/utils@5.62.0': - resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + '@storybook/manager-api@7.6.19': + resolution: {integrity: sha512-dVCx1Q+HZEA4U08XqYljiG88BeS3I3ahnPAQLZAeWQXQRkoc9G2jMgLNPKYPIqEtq7Xrn6SRlFMIofhwWrwZpg==} - '@typescript-eslint/utils@6.21.0': - resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 + '@storybook/manager@7.6.19': + resolution: {integrity: sha512-fZWQcf59x4P0iiBhrL74PZrqKJAPuk9sWjP8BIkGbf8wTZtUunbY5Sv4225fOL4NLJbuX9/RYLUPoxQ3nucGHA==} - '@typescript-eslint/visitor-keys@5.62.0': - resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@storybook/mdx2-csf@1.1.0': + resolution: {integrity: sha512-TXJJd5RAKakWx4BtpwvSNdgTDkKM6RkXU8GK34S/LhidQ5Pjz3wcnqb0TxEkfhK/ztbP8nKHqXFwLfa2CYkvQw==} - '@typescript-eslint/visitor-keys@6.21.0': - resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} - engines: {node: ^16.0.0 || >=18.0.0} + '@storybook/node-logger@7.6.19': + resolution: {integrity: sha512-2g29QC44Zl1jKY37DmQ0/dO7+VSKnGgPI/x0mwVwQffypSapxH3rwLLT5Q5XLHeFyD+fhRu5w9Cj4vTGynJgpA==} - '@ungap/structured-clone@1.2.0': - resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + '@storybook/postinstall@7.6.19': + resolution: {integrity: sha512-s6p1vpgMfn+QGDfCK2YNdyyWKidUgb3nGicB81FANRyzYqGB//QlJlghEc2LKCIQbGIZQiwP3l8PdZQmczEJRw==} - '@vitejs/plugin-react@3.1.0': - resolution: {integrity: sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==} - engines: {node: ^14.18.0 || >=16.0.0} + '@storybook/preview-api@7.6.17': + resolution: {integrity: sha512-wLfDdI9RWo1f2zzFe54yRhg+2YWyxLZvqdZnSQ45mTs4/7xXV5Wfbv3QNTtcdw8tT3U5KRTrN1mTfTCiRJc0Kw==} + + '@storybook/preview-api@7.6.19': + resolution: {integrity: sha512-04hdMSQucroJT4dBjQzRd7ZwH2hij8yx2nm5qd4HYGkd1ORkvlH6GOLph4XewNJl5Um3xfzFQzBhvkqvG0WaCQ==} + + '@storybook/preview@7.6.19': + resolution: {integrity: sha512-VqRPua2koOQTOteB+VvuKNXFYQ7IDEopaPpj9Nx+3kom+bqp0hWdAysWcm6CtKN2GGzBQm+5PvGibMNdawsaVg==} + + '@storybook/react-dom-shim@7.6.19': + resolution: {integrity: sha512-tpt2AC1428d1gF4fetMkpkeFZ1WdDr1CLKoLbSInWQZ7i96nbnIMIA9raR/W8ai1bo55KSz9Bq5ytC/1Pac2qQ==} peerDependencies: - vite: ^4.1.0-beta.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - '@vitejs/plugin-react@4.3.1': - resolution: {integrity: sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==} - engines: {node: ^14.18.0 || >=16.0.0} + '@storybook/react-vite@7.6.19': + resolution: {integrity: sha512-TqKQvWi53vE0KbWrlNq61cTLpzfQ5QMZv42YiwEUhM7ysSmrrg6WjgfEnvEyiAuY8yyaRspPF6Y8pYTKGHM8Nw==} + engines: {node: '>=16'} peerDependencies: - vite: ^4.2.0 || ^5.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 - '@webassemblyjs/ast@1.12.1': - resolution: {integrity: sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==} + '@storybook/react@7.6.19': + resolution: {integrity: sha512-uKShAAp1/pRki1YnRjBveH/jAD3f8V0W2WP1LxTQqnKVFkl01mTbDZ/9ZIK6rVTSILUlmsk3fwsNyRbOKVgBGQ==} + engines: {node: '>=16.0.0'} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true - '@webassemblyjs/floating-point-hex-parser@1.11.6': - resolution: {integrity: sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==} + '@storybook/router@7.6.19': + resolution: {integrity: sha512-q2/AvY8rG0znFEfbg50OIhkS5yQ6OmyzdCdztoEsDDdsbq87YPmsDj7k8Op1EkTa2T5CB8XhBOCQDtcj7gUUtg==} - '@webassemblyjs/helper-api-error@1.11.6': - resolution: {integrity: sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==} + '@storybook/telemetry@7.6.19': + resolution: {integrity: sha512-rA5xum4I36M57iiD3uzmW0MOdpl0vEpHWBSAa5hK0a0ALPeY9TgAsQlI/0dSyNYJ/K7aczEEN6d4qm1NC4u10A==} - '@webassemblyjs/helper-buffer@1.12.1': - resolution: {integrity: sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==} + '@storybook/theming@7.6.19': + resolution: {integrity: sha512-sAho13MmtA80ctOaLn8lpkQBsPyiqSdLcOPH5BWFhatQzzBQCpTAKQk+q/xGju8bNiPZ+yQBaBzbN8SfX8ceCg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - '@webassemblyjs/helper-numbers@1.11.6': - resolution: {integrity: sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==} + '@storybook/types@7.6.17': + resolution: {integrity: sha512-GRY0xEJQ0PrL7DY2qCNUdIfUOE0Gsue6N+GBJw9ku1IUDFLJRDOF+4Dx2BvYcVCPI5XPqdWKlEyZdMdKjiQN7Q==} - '@webassemblyjs/helper-wasm-bytecode@1.11.6': - resolution: {integrity: sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==} + '@storybook/types@7.6.19': + resolution: {integrity: sha512-DeGYrRPRMGTVfT7o2rEZtRzyLT2yKTI2exgpnxbwPWEFAduZCSfzBrcBXZ/nb5B0pjA9tUNWls1YzGkJGlkhpg==} - '@webassemblyjs/helper-wasm-section@1.12.1': - resolution: {integrity: sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==} + '@swc/helpers@0.4.14': + resolution: {integrity: sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==} - '@webassemblyjs/ieee754@1.11.6': - resolution: {integrity: sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==} + '@swc/helpers@0.4.36': + resolution: {integrity: sha512-5lxnyLEYFskErRPenYItLRSge5DjrJngYKdVjRSrWfza9G6KkgHEXi0vUZiyUeMU5JfXH1YnvXZzSp8ul88o2Q==} - '@webassemblyjs/leb128@1.11.6': - resolution: {integrity: sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==} + '@swc/helpers@0.5.11': + resolution: {integrity: sha512-YNlnKRWF2sVojTpIyzwou9XoTNbzbzONwRhOoniEioF1AtaitTvVZblaQRrAzChWQ1bLYyYSWzM18y4WwgzJ+A==} - '@webassemblyjs/utf8@1.11.6': - resolution: {integrity: sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==} + '@tanstack/query-core@5.45.0': + resolution: {integrity: sha512-RVfIZQmFUTdjhSAAblvueimfngYyfN6HlwaJUPK71PKd7yi43Vs1S/rdimmZedPWX/WGppcq/U1HOj7O7FwYxw==} - '@webassemblyjs/wasm-edit@1.12.1': - resolution: {integrity: sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==} + '@tanstack/react-query@5.45.1': + resolution: {integrity: sha512-mYYfJujKg2kxmkRRjA6nn4YKG3ITsKuH22f1kteJ5IuVQqgKUgbaSQfYwVP0gBS05mhwxO03HVpD0t7BMN7WOA==} + peerDependencies: + react: ^18.0.0 - '@webassemblyjs/wasm-gen@1.12.1': - resolution: {integrity: sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==} + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} - '@webassemblyjs/wasm-opt@1.12.1': - resolution: {integrity: sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==} + '@types/babel__generator@7.6.8': + resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} - '@webassemblyjs/wasm-parser@1.12.1': - resolution: {integrity: sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==} + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} - '@webassemblyjs/wast-printer@1.12.1': - resolution: {integrity: sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==} + '@types/babel__traverse@7.20.6': + resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} - '@xobotyi/scrollbar-width@1.9.5': - resolution: {integrity: sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==} + '@types/body-parser@1.19.5': + resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} - '@xtuc/ieee754@1.2.0': - resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} - '@xtuc/long@4.2.2': - resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + '@types/cross-spawn@6.0.6': + resolution: {integrity: sha512-fXRhhUkG4H3TQk5dBhQ7m/JDdSNHKwR2BBia62lhwEIq9xGiQKLxd6LymNhn47SjXhsUEPmxi+PKw2OkW4LLjA==} - '@yarnpkg/esbuild-plugin-pnp@3.0.0-rc.15': - resolution: {integrity: sha512-kYzDJO5CA9sy+on/s2aIW0411AklfCi8Ck/4QDivOqsMKpStZA2SsR+X27VTggGwpStWaLrjJcDcdDMowtG8MA==} - engines: {node: '>=14.15.0'} - peerDependencies: - esbuild: '>=0.10.0' + '@types/detect-port@1.3.5': + resolution: {integrity: sha512-Rf3/lB9WkDfIL9eEKaSYKc+1L/rNVYBjThk22JTqQw0YozXarX8YljFAz+HCoC6h4B4KwCMsBPZHaFezwT4BNA==} - '@yarnpkg/fslib@2.10.3': - resolution: {integrity: sha512-41H+Ga78xT9sHvWLlFOZLIhtU6mTGZ20pZ29EiZa97vnxdohJD2AF42rCoAoWfqUz486xY6fhjMH+DYEM9r14A==} - engines: {node: '>=12 <14 || 14.2 - 14.9 || >14.10.0'} + '@types/doctrine@0.0.3': + resolution: {integrity: sha512-w5jZ0ee+HaPOaX25X2/2oGR/7rgAQSYII7X7pp0m9KgBfMP7uKfMfTvcpl5Dj+eDBbpxKGiqE+flqDr6XTd2RA==} - '@yarnpkg/libzip@2.3.0': - resolution: {integrity: sha512-6xm38yGVIa6mKm/DUCF2zFFJhERh/QWp1ufm4cNUvxsONBmfPg8uZ9pZBdOmF6qFGr/HlT6ABBkCSx/dlEtvWg==} - engines: {node: '>=12 <14 || 14.2 - 14.9 || >14.10.0'} + '@types/doctrine@0.0.9': + resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==} - JSONStream@1.3.5: - resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} - hasBin: true + '@types/ejs@3.1.5': + resolution: {integrity: sha512-nv+GSx77ZtXiJzwKdsASqi+YQ5Z7vwHsTP0JY2SiQgjGckkBRKZnk8nIM+7oUZ1VCtuTz0+By4qVR7fqzp/Dfg==} - accepts@1.3.8: - resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} - engines: {node: '>= 0.6'} + '@types/emscripten@1.39.13': + resolution: {integrity: sha512-cFq+fO/isvhvmuP/+Sl4K4jtU6E23DoivtbO4r50e3odaxAiVdbfSYRDdJ4gCdxx+3aRjhphS5ZMwIH4hFy/Cw==} - acorn-import-attributes@1.9.5: - resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} - peerDependencies: - acorn: ^8 + '@types/escodegen@0.0.6': + resolution: {integrity: sha512-AjwI4MvWx3HAOaZqYsjKWyEObT9lcVV0Y0V8nXo6cXzN8ZiMxVhf6F3d/UNvXVGKrEzL/Dluc5p+y9GkzlTWig==} - acorn-jsx@5.3.2: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + '@types/eslint-scope@3.7.7': + resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} - acorn-walk@7.2.0: - resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==} - engines: {node: '>=0.4.0'} + '@types/eslint@8.56.10': + resolution: {integrity: sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==} - acorn@7.4.1: - resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} - engines: {node: '>=0.4.0'} - hasBin: true + '@types/estree@0.0.51': + resolution: {integrity: sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==} - acorn@8.12.0: - resolution: {integrity: sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==} - engines: {node: '>=0.4.0'} - hasBin: true + '@types/estree@1.0.5': + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - add-stream@1.0.0: - resolution: {integrity: sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==} + '@types/express-serve-static-core@4.19.3': + resolution: {integrity: sha512-KOzM7MhcBFlmnlr/fzISFF5vGWVSvN6fTd4T+ExOt08bA/dA5kpSzY52nMsI1KDFmUREpJelPYyuslLRSjjgCg==} - address@1.2.2: - resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==} - engines: {node: '>= 10.0.0'} + '@types/express@4.17.21': + resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} - after@0.8.2: - resolution: {integrity: sha512-QbJ0NTQ/I9DI3uSJA4cbexiwQeRAfjPScqIbSjUDd9TOrcg6pTkdgziesOqxBMBzit8vFCTwrP27t13vFOORRA==} + '@types/find-cache-dir@3.2.1': + resolution: {integrity: sha512-frsJrz2t/CeGifcu/6uRo4b+SzAwT4NYCVPu1GN8IB9XTzrpPkGuV0tmh9mN+/L0PklAlsC3u5Fxt0ju00LXIw==} - agent-base@5.1.1: - resolution: {integrity: sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==} - engines: {node: '>= 6.0.0'} + '@types/glob@7.2.0': + resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} - agentkeepalive@4.5.0: - resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} - engines: {node: '>= 8.0.0'} + '@types/graceful-fs@4.1.9': + resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} - aggregate-error@3.1.0: - resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} - engines: {node: '>=8'} + '@types/hoist-non-react-statics@3.3.5': + resolution: {integrity: sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==} - ajv-keywords@3.5.2: - resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} - peerDependencies: - ajv: ^6.9.1 + '@types/http-errors@2.0.4': + resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} - ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} - ajv@8.16.0: - resolution: {integrity: sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw==} + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} - ansi-escapes@4.3.2: - resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} - engines: {node: '>=8'} + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} - ansi-escapes@5.0.0: - resolution: {integrity: sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==} - engines: {node: '>=12'} + '@types/jest@29.5.12': + resolution: {integrity: sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==} - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} + '@types/js-cookie@2.2.7': + resolution: {integrity: sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA==} - ansi-regex@6.0.1: - resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} - engines: {node: '>=12'} + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} + '@types/json5@0.0.29': + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} + '@types/lodash@4.17.5': + resolution: {integrity: sha512-MBIOHVZqVqgfro1euRDWX7OO0fBVUUMrN6Pwm8LQsz8cWhEpihlvR70ENj3f40j58TNxZaWv2ndSkInykNBBJw==} - ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} + '@types/mdx@2.0.13': + resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} - ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} - engines: {node: '>=12'} + '@types/mime-types@2.1.4': + resolution: {integrity: sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==} - any-promise@1.3.0: - resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + '@types/mime@1.3.5': + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} + '@types/minimatch@3.0.5': + resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==} - app-root-dir@1.0.2: - resolution: {integrity: sha512-jlpIfsOoNoafl92Sz//64uQHGSyMrD2vYG5d8o2a4qGvyNCvXur7bzIsWtAC/6flI2RYAp3kv8rsfBtaLm7w0g==} + '@types/minimatch@5.1.2': + resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} - argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + '@types/minimist@1.2.5': + resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} - argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + '@types/node-fetch@2.6.11': + resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==} - aria-hidden@1.2.4: - resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==} - engines: {node: '>=10'} + '@types/node@18.19.36': + resolution: {integrity: sha512-tX1BNmYSWEvViftB26VLNxT6mEr37M7+ldUtq7rlKnv4/2fKYsJIOmqJAjT6h1DNuwQjIKgw3VJ/Dtw3yiTIQw==} - array-buffer-byte-length@1.0.1: - resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} - engines: {node: '>= 0.4'} + '@types/node@20.14.4': + resolution: {integrity: sha512-1ChboN+57suCT2t/f8lwtPY/k3qTpuD/qnqQuYoBg6OQOcPyaw7PiZVdGpaZYAvhDDtqrt0oAaM8+oSu1xsUGw==} - array-differ@3.0.0: - resolution: {integrity: sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==} - engines: {node: '>=8'} + '@types/normalize-package-data@2.4.4': + resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} - array-flatten@1.1.1: - resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + '@types/postcss-modules-local-by-default@4.0.2': + resolution: {integrity: sha512-CtYCcD+L+trB3reJPny+bKWKMzPfxEyQpKIwit7kErnOexf5/faaGpkFy4I5AwbV4hp1sk7/aTg0tt0B67VkLQ==} - array-ify@1.0.0: - resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} + '@types/postcss-modules-scope@3.0.4': + resolution: {integrity: sha512-//ygSisVq9kVI0sqx3UPLzWIMCmtSVrzdljtuaAEJtGoGnpjBikZ2sXO5MpH9SnWX9HRfXxHifDAXcQjupWnIQ==} - array-includes@3.1.8: - resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} - engines: {node: '>= 0.4'} + '@types/pretty-hrtime@1.0.3': + resolution: {integrity: sha512-nj39q0wAIdhwn7DGUyT9irmsKK1tV0bd5WFEhgpqNTMFZ8cE+jieuTphCW0tfdm47S2zVT5mr09B28b1chmQMA==} - array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} + '@types/prop-types@15.7.12': + resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} - array.prototype.findlastindex@1.2.5: - resolution: {integrity: sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==} - engines: {node: '>= 0.4'} + '@types/qs@6.9.15': + resolution: {integrity: sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==} - array.prototype.flat@1.3.2: - resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} - engines: {node: '>= 0.4'} + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} - array.prototype.flatmap@1.3.2: - resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} - engines: {node: '>= 0.4'} + '@types/react-helmet@6.1.11': + resolution: {integrity: sha512-0QcdGLddTERotCXo3VFlUSWO3ztraw8nZ6e3zJSgG7apwV5xt+pJUS8ewPBqT4NYB1optGLprNQzFleIY84u/g==} - arraybuffer.prototype.slice@1.0.3: - resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} - engines: {node: '>= 0.4'} + '@types/react-infinite-scroller@1.2.5': + resolution: {integrity: sha512-fJU1jhMgoL6NJFrqTM0Ob7tnd2sQWGxe2ESwiU6FZWbJK/VO/Er5+AOhc+e2zbT0dk5pLygqctsulOLJ8xnSzw==} - arraybuffer.slice@0.0.7: - resolution: {integrity: sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==} + '@types/react-mentions@4.1.13': + resolution: {integrity: sha512-kRulAAjlmhCtsJ9bapO0foocknaE/rEuFKpmFEU81fBfnXZmZNBaJ9J/DBjwigT3WDHjQVUmYoi5sxEXrcdzAw==} - arrify@1.0.1: - resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} - engines: {node: '>=0.10.0'} + '@types/react-timeago@4.1.7': + resolution: {integrity: sha512-ogD4Ror/hDG+pQggCX+TgPgJ8W2jeeUxsgNU485Qpm0Ma+E2TND2EJuKwK5+sxlkDXDEgsHradO0zWBkTgLzNg==} - arrify@2.0.1: - resolution: {integrity: sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==} - engines: {node: '>=8'} + '@types/react@17.0.80': + resolution: {integrity: sha512-LrgHIu2lEtIo8M7d1FcI3BdwXWoRQwMoXOZ7+dPTW0lYREjmlHl3P0U1VD0i/9tppOuv8/sam7sOjx34TxSFbA==} - assert@2.1.0: - resolution: {integrity: sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==} + '@types/resolve@1.20.6': + resolution: {integrity: sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==} - ast-types@0.16.1: - resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} - engines: {node: '>=4'} + '@types/scheduler@0.16.8': + resolution: {integrity: sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==} - astral-regex@2.0.0: - resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} - engines: {node: '>=8'} + '@types/semver@7.5.8': + resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} - async-limiter@1.0.1: - resolution: {integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==} + '@types/send@0.17.4': + resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} - async@3.2.5: - resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==} + '@types/serve-static@1.15.7': + resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==} - asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} - autoprefixer@10.4.19: - resolution: {integrity: sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==} - engines: {node: ^10 || ^12 || >=14} - hasBin: true - peerDependencies: - postcss: ^8.1.0 + '@types/styled-components@5.1.34': + resolution: {integrity: sha512-mmiVvwpYklFIv9E8qfxuPyIt/OuyIrn6gMOAMOFUO3WJfSrSE+sGUoa4PiZj77Ut7bKZpaa6o1fBKS/4TOEvnA==} - available-typed-arrays@1.0.7: - resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} - engines: {node: '>= 0.4'} + '@types/stylis@4.2.5': + resolution: {integrity: sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==} - axe-core@4.9.1: - resolution: {integrity: sha512-QbUdXJVTpvUTHU7871ppZkdOLBeGUKBQWHkHrvN2V9IQWGMt61zf3B45BtzjxEJzYuj0JBjBZP/hmYS/R9pmAw==} - engines: {node: '>=4'} + '@types/unist@2.0.10': + resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==} - axios@1.7.2: - resolution: {integrity: sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==} + '@types/uuid@9.0.8': + resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==} - babel-core@7.0.0-bridge.0: - resolution: {integrity: sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==} + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.32': + resolution: {integrity: sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==} + + '@typescript-eslint/eslint-plugin@6.21.0': + resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==} + engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: - '@babel/core': ^7.0.0-0 + '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true - babel-jest@29.7.0: - resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@typescript-eslint/parser@6.21.0': + resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} + engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: - '@babel/core': ^7.8.0 + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true - babel-plugin-istanbul@6.1.1: - resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} - engines: {node: '>=8'} + '@typescript-eslint/scope-manager@5.62.0': + resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - babel-plugin-jest-hoist@29.6.3: - resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@typescript-eslint/scope-manager@6.21.0': + resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} + engines: {node: ^16.0.0 || >=18.0.0} - babel-plugin-polyfill-corejs2@0.4.11: - resolution: {integrity: sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==} + '@typescript-eslint/type-utils@6.21.0': + resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==} + engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true - babel-plugin-polyfill-corejs3@0.10.4: - resolution: {integrity: sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + '@typescript-eslint/types@5.62.0': + resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - babel-plugin-polyfill-regenerator@0.6.2: - resolution: {integrity: sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + '@typescript-eslint/types@6.21.0': + resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} + engines: {node: ^16.0.0 || >=18.0.0} - babel-preset-current-node-syntax@1.0.1: - resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} + '@typescript-eslint/typescript-estree@5.62.0': + resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: - '@babel/core': ^7.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true - babel-preset-jest@29.6.3: - resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@typescript-eslint/typescript-estree@6.21.0': + resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} + engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: - '@babel/core': ^7.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true - backo2@1.0.2: - resolution: {integrity: sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA==} - - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + '@typescript-eslint/utils@5.62.0': + resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - balanced-match@2.0.0: - resolution: {integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==} + '@typescript-eslint/utils@6.21.0': + resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 - base64-arraybuffer@0.1.5: - resolution: {integrity: sha512-437oANT9tP582zZMwSvZGy2nmSeAb8DW2me3y+Uv1Wp2Rulr8Mqlyrv3E7MLxmsiaPSMMDmiDVzgE+e8zlMx9g==} - engines: {node: '>= 0.6.0'} + '@typescript-eslint/visitor-keys@5.62.0': + resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + '@typescript-eslint/visitor-keys@6.21.0': + resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} + engines: {node: ^16.0.0 || >=18.0.0} - batch-processor@1.0.0: - resolution: {integrity: sha512-xoLQD8gmmR32MeuBHgH0Tzd5PuSZx71ZsbhVxOCRbgktZEPe4SQy7s9Z50uPp0F/f7iw2XmkHN2xkgbMfckMDA==} + '@ungap/structured-clone@1.2.0': + resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} - better-assert@1.0.2: - resolution: {integrity: sha512-bYeph2DFlpK1XmGs6fvlLRUN29QISM3GBuUwSFsMY2XRx4AvC0WNCS57j4c/xGrK2RS24C1w3YoBOsw9fT46tQ==} + '@vitejs/plugin-react@3.1.0': + resolution: {integrity: sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.1.0-beta.0 - better-opn@3.0.2: - resolution: {integrity: sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==} - engines: {node: '>=12.0.0'} + '@vitejs/plugin-react@4.3.1': + resolution: {integrity: sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 - big-integer@1.6.52: - resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} - engines: {node: '>=0.6'} + '@webassemblyjs/ast@1.12.1': + resolution: {integrity: sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==} - big.js@5.2.2: - resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==} + '@webassemblyjs/floating-point-hex-parser@1.11.6': + resolution: {integrity: sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==} - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} + '@webassemblyjs/helper-api-error@1.11.6': + resolution: {integrity: sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==} - bl@4.1.0: - resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + '@webassemblyjs/helper-buffer@1.12.1': + resolution: {integrity: sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==} - blob@0.0.5: - resolution: {integrity: sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==} + '@webassemblyjs/helper-numbers@1.11.6': + resolution: {integrity: sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==} - body-parser@1.20.2: - resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + '@webassemblyjs/helper-wasm-bytecode@1.11.6': + resolution: {integrity: sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==} - bplist-parser@0.2.0: - resolution: {integrity: sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==} - engines: {node: '>= 5.10.0'} + '@webassemblyjs/helper-wasm-section@1.12.1': + resolution: {integrity: sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==} - brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + '@webassemblyjs/ieee754@1.11.6': + resolution: {integrity: sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==} - brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + '@webassemblyjs/leb128@1.11.6': + resolution: {integrity: sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==} - braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} + '@webassemblyjs/utf8@1.11.6': + resolution: {integrity: sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==} - browser-assert@1.2.1: - resolution: {integrity: sha512-nfulgvOR6S4gt9UKCeGJOuSGBPGiFT6oQ/2UBnvTY/5aQ1PnksW72fhZkM30DzoRRv2WpwZf1vHHEr3mtuXIWQ==} + '@webassemblyjs/wasm-edit@1.12.1': + resolution: {integrity: sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==} - browserify-zlib@0.1.4: - resolution: {integrity: sha512-19OEpq7vWgsH6WkvkBJQDFvJS1uPcbFOQ4v9CU839dO+ZZXUZO6XpE6hNCqvlIIj+4fZvRiJ6DsAQ382GwiyTQ==} + '@webassemblyjs/wasm-gen@1.12.1': + resolution: {integrity: sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==} - browserslist@4.23.1: - resolution: {integrity: sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true + '@webassemblyjs/wasm-opt@1.12.1': + resolution: {integrity: sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==} - bs-logger@0.2.6: - resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} - engines: {node: '>= 6'} + '@webassemblyjs/wasm-parser@1.12.1': + resolution: {integrity: sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==} - bser@2.1.1: - resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + '@webassemblyjs/wast-printer@1.12.1': + resolution: {integrity: sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==} - buffer-crc32@0.2.13: - resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + '@xobotyi/scrollbar-width@1.9.5': + resolution: {integrity: sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==} - buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + '@xtuc/ieee754@1.2.0': + resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} - buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + '@xtuc/long@4.2.2': + resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} - bundle-require@4.2.1: - resolution: {integrity: sha512-7Q/6vkyYAwOmQNRw75x+4yRtZCZJXUDmHHlFdkiV0wgv/reNjtJwpu1jPJ0w2kbEpIM0uoKI3S4/f39dU7AjSA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + '@yarnpkg/esbuild-plugin-pnp@3.0.0-rc.15': + resolution: {integrity: sha512-kYzDJO5CA9sy+on/s2aIW0411AklfCi8Ck/4QDivOqsMKpStZA2SsR+X27VTggGwpStWaLrjJcDcdDMowtG8MA==} + engines: {node: '>=14.15.0'} peerDependencies: - esbuild: '>=0.17' - - bytes@3.0.0: - resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==} - engines: {node: '>= 0.8'} + esbuild: '>=0.10.0' - bytes@3.1.2: - resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} - engines: {node: '>= 0.8'} + '@yarnpkg/fslib@2.10.3': + resolution: {integrity: sha512-41H+Ga78xT9sHvWLlFOZLIhtU6mTGZ20pZ29EiZa97vnxdohJD2AF42rCoAoWfqUz486xY6fhjMH+DYEM9r14A==} + engines: {node: '>=12 <14 || 14.2 - 14.9 || >14.10.0'} - cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} + '@yarnpkg/libzip@2.3.0': + resolution: {integrity: sha512-6xm38yGVIa6mKm/DUCF2zFFJhERh/QWp1ufm4cNUvxsONBmfPg8uZ9pZBdOmF6qFGr/HlT6ABBkCSx/dlEtvWg==} + engines: {node: '>=12 <14 || 14.2 - 14.9 || >14.10.0'} - call-bind@1.0.7: - resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} - engines: {node: '>= 0.4'} + JSONStream@1.3.5: + resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} + hasBin: true - callsite@1.0.0: - resolution: {integrity: sha512-0vdNRFXn5q+dtOqjfFtmtlI9N2eVZ7LMyEV2iKC5mEEFvSg/69Ml6b/WU2qF8W1nLRa0wiSrDT3Y5jOHZCwKPQ==} + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} - callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} + acorn-import-attributes@1.9.5: + resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} + peerDependencies: + acorn: ^8 - camelcase-keys@6.2.2: - resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} - engines: {node: '>=8'} + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - camelcase@5.3.1: - resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} - engines: {node: '>=6'} + acorn-walk@7.2.0: + resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==} + engines: {node: '>=0.4.0'} - camelcase@6.3.0: - resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} - engines: {node: '>=10'} + acorn@7.4.1: + resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} + engines: {node: '>=0.4.0'} + hasBin: true - camelize@1.0.1: - resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} + acorn@8.12.0: + resolution: {integrity: sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==} + engines: {node: '>=0.4.0'} + hasBin: true - caniuse-lite@1.0.30001636: - resolution: {integrity: sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg==} + add-stream@1.0.0: + resolution: {integrity: sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==} - chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} + address@1.2.2: + resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==} + engines: {node: '>= 10.0.0'} - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} + after@0.8.2: + resolution: {integrity: sha512-QbJ0NTQ/I9DI3uSJA4cbexiwQeRAfjPScqIbSjUDd9TOrcg6pTkdgziesOqxBMBzit8vFCTwrP27t13vFOORRA==} - chalk@5.3.0: - resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + agent-base@5.1.1: + resolution: {integrity: sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==} + engines: {node: '>= 6.0.0'} - char-regex@1.0.2: - resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} - engines: {node: '>=10'} + agentkeepalive@4.5.0: + resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} + engines: {node: '>= 8.0.0'} - chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} + aggregate-error@3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} - chownr@1.1.4: - resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + ajv-keywords@3.5.2: + resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} + peerDependencies: + ajv: ^6.9.1 - chownr@2.0.0: - resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} - engines: {node: '>=10'} + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - chrome-trace-event@1.0.4: - resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} - engines: {node: '>=6.0'} + ajv@8.16.0: + resolution: {integrity: sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw==} - ci-info@3.9.0: - resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} - citty@0.1.6: - resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} - - cjs-module-lexer@1.3.1: - resolution: {integrity: sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==} + ansi-escapes@5.0.0: + resolution: {integrity: sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==} + engines: {node: '>=12'} - clean-stack@2.2.0: - resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} - engines: {node: '>=6'} - - cli-cursor@3.1.0: - resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - cli-cursor@4.0.0: - resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - cli-spinners@2.9.2: - resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} - engines: {node: '>=6'} + ansi-regex@6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} - cli-table3@0.6.5: - resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} - engines: {node: 10.* || >= 12.*} + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} - cli-truncate@3.1.0: - resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} - cliui@7.0.4: - resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} - cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} - clone-deep@4.0.1: - resolution: {integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==} - engines: {node: '>=6'} + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - clone@1.0.4: - resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} - engines: {node: '>=0.8'} + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} - clsx@1.2.1: - resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==} - engines: {node: '>=6'} + app-root-dir@1.0.2: + resolution: {integrity: sha512-jlpIfsOoNoafl92Sz//64uQHGSyMrD2vYG5d8o2a4qGvyNCvXur7bzIsWtAC/6flI2RYAp3kv8rsfBtaLm7w0g==} - clsx@2.1.1: - resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} - engines: {node: '>=6'} + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} - co@4.6.0: - resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} - engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - collect-v8-coverage@1.0.2: - resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} + aria-hidden@1.2.4: + resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==} + engines: {node: '>=10'} - color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + array-buffer-byte-length@1.0.1: + resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} + engines: {node: '>= 0.4'} - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} + array-differ@3.0.0: + resolution: {integrity: sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==} + engines: {node: '>=8'} - color-name@1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + array-ify@1.0.0: + resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} - colord@2.9.3: - resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} + array-includes@3.1.8: + resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} + engines: {node: '>= 0.4'} - colorette@2.0.20: - resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} - combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} + array.prototype.findlastindex@1.2.5: + resolution: {integrity: sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==} + engines: {node: '>= 0.4'} - commander@11.0.0: - resolution: {integrity: sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==} - engines: {node: '>=16'} + array.prototype.flat@1.3.2: + resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} + engines: {node: '>= 0.4'} - commander@2.20.3: - resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + array.prototype.flatmap@1.3.2: + resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} + engines: {node: '>= 0.4'} - commander@4.1.1: - resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} - engines: {node: '>= 6'} + arraybuffer.prototype.slice@1.0.3: + resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} + engines: {node: '>= 0.4'} - commander@6.2.1: - resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} - engines: {node: '>= 6'} + arraybuffer.slice@0.0.7: + resolution: {integrity: sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==} - commist@1.1.0: - resolution: {integrity: sha512-rraC8NXWOEjhADbZe9QBNzLAN5Q3fsTPQtBV+fEVj6xKIgDgNiEVE6ZNfHpZOqfQ21YUzfVNUXLOEZquYvQPPg==} + arrify@1.0.1: + resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} + engines: {node: '>=0.10.0'} - commondir@1.0.1: - resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + arrify@2.0.1: + resolution: {integrity: sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==} + engines: {node: '>=8'} - compare-func@2.0.0: - resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} + assert@2.1.0: + resolution: {integrity: sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==} - component-bind@1.0.0: - resolution: {integrity: sha512-WZveuKPeKAG9qY+FkYDeADzdHyTYdIboXS59ixDeRJL5ZhxpqUnxSOwop4FQjMsiYm3/Or8cegVbpAHNA7pHxw==} + ast-types@0.16.1: + resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} + engines: {node: '>=4'} - component-emitter@1.2.1: - resolution: {integrity: sha512-jPatnhd33viNplKjqXKRkGU345p263OIWzDL2wH3LGIGp5Kojo+uXizHmOADRvhGFFTnJqX3jBAKP6vvmSDKcA==} + astral-regex@2.0.0: + resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} + engines: {node: '>=8'} - component-emitter@1.3.1: - resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} + async-limiter@1.0.1: + resolution: {integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==} - component-inherit@0.0.3: - resolution: {integrity: sha512-w+LhYREhatpVqTESyGFg3NlP6Iu0kEKUHETY9GoZP/pQyW4mHFZuFWRUCIqVPZ36ueVLtoOEZaAqbCF2RDndaA==} + async@3.2.5: + resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==} - compressible@2.0.18: - resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} - engines: {node: '>= 0.6'} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - compression@1.7.4: - resolution: {integrity: sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==} - engines: {node: '>= 0.8.0'} + autoprefixer@10.4.19: + resolution: {integrity: sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 - computed-style@0.1.4: - resolution: {integrity: sha512-WpAmaKbMNmS3OProfHIdJiNleNJdgUrJfbKArXua28QF7+0CoZjlLn0lp6vlc+dl5r2/X9GQiQRQQU4BzSa69w==} + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + axe-core@4.9.1: + resolution: {integrity: sha512-QbUdXJVTpvUTHU7871ppZkdOLBeGUKBQWHkHrvN2V9IQWGMt61zf3B45BtzjxEJzYuj0JBjBZP/hmYS/R9pmAw==} + engines: {node: '>=4'} - concat-stream@1.6.2: - resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} - engines: {'0': node >= 0.8} + axios@1.7.2: + resolution: {integrity: sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==} - concat-stream@2.0.0: - resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} - engines: {'0': node >= 6.0} + babel-core@7.0.0-bridge.0: + resolution: {integrity: sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==} + peerDependencies: + '@babel/core': ^7.0.0-0 - consola@3.2.3: - resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==} - engines: {node: ^14.18.0 || >=16.10.0} + babel-jest@29.7.0: + resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.8.0 - content-disposition@0.5.4: - resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} - engines: {node: '>= 0.6'} + babel-plugin-istanbul@6.1.1: + resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} + engines: {node: '>=8'} - content-type@1.0.5: - resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} - engines: {node: '>= 0.6'} + babel-plugin-jest-hoist@29.6.3: + resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - conventional-changelog-angular@5.0.13: - resolution: {integrity: sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==} - engines: {node: '>=10'} + babel-plugin-polyfill-corejs2@0.4.11: + resolution: {integrity: sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - conventional-changelog-atom@2.0.8: - resolution: {integrity: sha512-xo6v46icsFTK3bb7dY/8m2qvc8sZemRgdqLb/bjpBsH2UyOS8rKNTgcb5025Hri6IpANPApbXMg15QLb1LJpBw==} - engines: {node: '>=10'} + babel-plugin-polyfill-corejs3@0.10.4: + resolution: {integrity: sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - conventional-changelog-codemirror@2.0.8: - resolution: {integrity: sha512-z5DAsn3uj1Vfp7po3gpt2Boc+Bdwmw2++ZHa5Ak9k0UKsYAO5mH1UBTN0qSCuJZREIhX6WU4E1p3IW2oRCNzQw==} - engines: {node: '>=10'} + babel-plugin-polyfill-regenerator@0.6.2: + resolution: {integrity: sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - conventional-changelog-config-spec@2.1.0: - resolution: {integrity: sha512-IpVePh16EbbB02V+UA+HQnnPIohgXvJRxHcS5+Uwk4AT5LjzCZJm5sp/yqs5C6KZJ1jMsV4paEV13BN1pvDuxQ==} + babel-preset-current-node-syntax@1.0.1: + resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} + peerDependencies: + '@babel/core': ^7.0.0 - conventional-changelog-conventionalcommits@4.6.3: - resolution: {integrity: sha512-LTTQV4fwOM4oLPad317V/QNQ1FY4Hju5qeBIM1uTHbrnCE+Eg4CdRZ3gO2pUeR+tzWdp80M2j3qFFEDWVqOV4g==} - engines: {node: '>=10'} + babel-preset-jest@29.6.3: + resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.0.0 - conventional-changelog-core@4.2.4: - resolution: {integrity: sha512-gDVS+zVJHE2v4SLc6B0sLsPiloR0ygU7HaDW14aNJE1v4SlqJPILPl/aJC7YdtRE4CybBf8gDwObBvKha8Xlyg==} - engines: {node: '>=10'} + backo2@1.0.2: + resolution: {integrity: sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA==} - conventional-changelog-ember@2.0.9: - resolution: {integrity: sha512-ulzIReoZEvZCBDhcNYfDIsLTHzYHc7awh+eI44ZtV5cx6LVxLlVtEmcO+2/kGIHGtw+qVabJYjdI5cJOQgXh1A==} - engines: {node: '>=10'} + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - conventional-changelog-eslint@3.0.9: - resolution: {integrity: sha512-6NpUCMgU8qmWmyAMSZO5NrRd7rTgErjrm4VASam2u5jrZS0n38V7Y9CzTtLT2qwz5xEChDR4BduoWIr8TfwvXA==} - engines: {node: '>=10'} + balanced-match@2.0.0: + resolution: {integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==} - conventional-changelog-express@2.0.6: - resolution: {integrity: sha512-SDez2f3iVJw6V563O3pRtNwXtQaSmEfTCaTBPCqn0oG0mfkq0rX4hHBq5P7De2MncoRixrALj3u3oQsNK+Q0pQ==} - engines: {node: '>=10'} + base64-arraybuffer@0.1.5: + resolution: {integrity: sha512-437oANT9tP582zZMwSvZGy2nmSeAb8DW2me3y+Uv1Wp2Rulr8Mqlyrv3E7MLxmsiaPSMMDmiDVzgE+e8zlMx9g==} + engines: {node: '>= 0.6.0'} - conventional-changelog-jquery@3.0.11: - resolution: {integrity: sha512-x8AWz5/Td55F7+o/9LQ6cQIPwrCjfJQ5Zmfqi8thwUEKHstEn4kTIofXub7plf1xvFA2TqhZlq7fy5OmV6BOMw==} - engines: {node: '>=10'} + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - conventional-changelog-jshint@2.0.9: - resolution: {integrity: sha512-wMLdaIzq6TNnMHMy31hql02OEQ8nCQfExw1SE0hYL5KvU+JCTuPaDO+7JiogGT2gJAxiUGATdtYYfh+nT+6riA==} - engines: {node: '>=10'} + batch-processor@1.0.0: + resolution: {integrity: sha512-xoLQD8gmmR32MeuBHgH0Tzd5PuSZx71ZsbhVxOCRbgktZEPe4SQy7s9Z50uPp0F/f7iw2XmkHN2xkgbMfckMDA==} - conventional-changelog-preset-loader@2.3.4: - resolution: {integrity: sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g==} - engines: {node: '>=10'} + better-assert@1.0.2: + resolution: {integrity: sha512-bYeph2DFlpK1XmGs6fvlLRUN29QISM3GBuUwSFsMY2XRx4AvC0WNCS57j4c/xGrK2RS24C1w3YoBOsw9fT46tQ==} - conventional-changelog-writer@5.0.1: - resolution: {integrity: sha512-5WsuKUfxW7suLblAbFnxAcrvf6r+0b7GvNaWUwUIk0bXMnENP/PEieGKVUQrjPqwPT4o3EPAASBXiY6iHooLOQ==} - engines: {node: '>=10'} - hasBin: true + better-opn@3.0.2: + resolution: {integrity: sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==} + engines: {node: '>=12.0.0'} - conventional-changelog@3.1.25: - resolution: {integrity: sha512-ryhi3fd1mKf3fSjbLXOfK2D06YwKNic1nC9mWqybBHdObPd8KJ2vjaXZfYj1U23t+V8T8n0d7gwnc9XbIdFbyQ==} - engines: {node: '>=10'} + big-integer@1.6.52: + resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} + engines: {node: '>=0.6'} - conventional-commits-filter@2.0.7: - resolution: {integrity: sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA==} - engines: {node: '>=10'} + big.js@5.2.2: + resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==} - conventional-commits-parser@3.2.4: - resolution: {integrity: sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==} - engines: {node: '>=10'} - hasBin: true + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} - conventional-recommended-bump@6.1.0: - resolution: {integrity: sha512-uiApbSiNGM/kkdL9GTOLAqC4hbptObFo4wW2QRyHsKciGAfQuLU1ShZ1BIVI/+K2BE/W1AWYQMCXAsv4dyKPaw==} - engines: {node: '>=10'} - hasBin: true + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + blob@0.0.5: + resolution: {integrity: sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==} - cookie-signature@1.0.6: - resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + body-parser@1.20.2: + resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - cookie@0.6.0: - resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} - engines: {node: '>= 0.6'} + bplist-parser@0.2.0: + resolution: {integrity: sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==} + engines: {node: '>= 5.10.0'} - copy-anything@2.0.6: - resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==} + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} - copy-to-clipboard@3.3.3: - resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==} + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} - core-js-compat@3.37.1: - resolution: {integrity: sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==} + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} - core-util-is@1.0.3: - resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + browser-assert@1.2.1: + resolution: {integrity: sha512-nfulgvOR6S4gt9UKCeGJOuSGBPGiFT6oQ/2UBnvTY/5aQ1PnksW72fhZkM30DzoRRv2WpwZf1vHHEr3mtuXIWQ==} - cosmiconfig@9.0.0: - resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} - engines: {node: '>=14'} - peerDependencies: - typescript: '>=4.9.5' - peerDependenciesMeta: - typescript: - optional: true + browserify-zlib@0.1.4: + resolution: {integrity: sha512-19OEpq7vWgsH6WkvkBJQDFvJS1uPcbFOQ4v9CU839dO+ZZXUZO6XpE6hNCqvlIIj+4fZvRiJ6DsAQ382GwiyTQ==} - create-jest@29.7.0: - resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + browserslist@4.23.1: + resolution: {integrity: sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} - engines: {node: '>= 8'} - - crypto-random-string@2.0.0: - resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} - engines: {node: '>=8'} - - css-color-keywords@1.0.0: - resolution: {integrity: sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==} - engines: {node: '>=4'} - - css-functions-list@3.2.2: - resolution: {integrity: sha512-c+N0v6wbKVxTu5gOBBFkr9BEdBWaqqjQeiJ8QvSRIJOf+UxlJh930m8e6/WNeODIK0mYLFkoONrnj16i2EcvfQ==} - engines: {node: '>=12 || >=16'} + bs-logger@0.2.6: + resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} + engines: {node: '>= 6'} - css-in-js-utils@3.1.0: - resolution: {integrity: sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A==} + bser@2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} - css-to-react-native@3.2.0: - resolution: {integrity: sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==} + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} - css-tokenize@1.0.1: - resolution: {integrity: sha512-gLmmbJdwH9HLY4bcA17lnZ8GgPwEXRbvxBJGHnkiB6gLhRpTzjkjtMIvz7YORGW/Ptv2oMk8b5g+u7mRD6Dd7A==} + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - css-tree@1.1.3: - resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} - engines: {node: '>=8.0.0'} + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - css-tree@2.3.1: - resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} - engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + bundle-require@4.2.1: + resolution: {integrity: sha512-7Q/6vkyYAwOmQNRw75x+4yRtZCZJXUDmHHlFdkiV0wgv/reNjtJwpu1jPJ0w2kbEpIM0uoKI3S4/f39dU7AjSA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.17' - cssesc@3.0.0: - resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} - engines: {node: '>=4'} - hasBin: true + bytes@3.0.0: + resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==} + engines: {node: '>= 0.8'} - csstype@3.1.3: - resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} - dargs@7.0.0: - resolution: {integrity: sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==} + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} - data-view-buffer@1.0.1: - resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} + call-bind@1.0.7: + resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} engines: {node: '>= 0.4'} - data-view-byte-length@1.0.1: - resolution: {integrity: sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==} - engines: {node: '>= 0.4'} + callsite@1.0.0: + resolution: {integrity: sha512-0vdNRFXn5q+dtOqjfFtmtlI9N2eVZ7LMyEV2iKC5mEEFvSg/69Ml6b/WU2qF8W1nLRa0wiSrDT3Y5jOHZCwKPQ==} - data-view-byte-offset@1.0.0: - resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} - engines: {node: '>= 0.4'} + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} - dateformat@3.0.3: - resolution: {integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==} + camelcase-keys@6.2.2: + resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} + engines: {node: '>=8'} - dayjs@1.11.11: - resolution: {integrity: sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==} + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} - debug@2.6.9: - resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} - debug@3.1.0: - resolution: {integrity: sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true + camelize@1.0.1: + resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} - debug@3.2.7: - resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true + caniuse-lite@1.0.30001636: + resolution: {integrity: sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg==} - debug@4.3.4: - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true + chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} - debug@4.3.5: - resolution: {integrity: sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} - decamelize-keys@1.1.1: - resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} - engines: {node: '>=0.10.0'} + chalk@5.3.0: + resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - decamelize@1.2.0: - resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} - engines: {node: '>=0.10.0'} + char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} - dedent@1.5.3: - resolution: {integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==} - peerDependencies: - babel-plugin-macros: ^3.1.0 - peerDependenciesMeta: - babel-plugin-macros: - optional: true - - deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - - deepmerge@4.3.1: - resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} - engines: {node: '>=0.10.0'} + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} - default-browser-id@3.0.0: - resolution: {integrity: sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==} - engines: {node: '>=12'} + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} - defaults@1.0.4: - resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} - define-data-property@1.1.4: - resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} - engines: {node: '>= 0.4'} + chrome-trace-event@1.0.4: + resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} + engines: {node: '>=6.0'} - define-lazy-prop@2.0.0: - resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} - define-properties@1.2.1: - resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} - engines: {node: '>= 0.4'} + citty@0.1.6: + resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} - defu@6.1.4: - resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + cjs-module-lexer@1.3.1: + resolution: {integrity: sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==} - del@6.1.1: - resolution: {integrity: sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==} - engines: {node: '>=10'} + clean-stack@2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} - delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} + cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} - depd@2.0.0: - resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} - engines: {node: '>= 0.8'} + cli-cursor@4.0.0: + resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dequal@2.0.3: - resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} engines: {node: '>=6'} - destroy@1.2.0: - resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + cli-table3@0.6.5: + resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} + engines: {node: 10.* || >= 12.*} - detect-indent@6.1.0: - resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} - engines: {node: '>=8'} + cli-truncate@3.1.0: + resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - detect-newline@3.1.0: - resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} - engines: {node: '>=8'} + client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} - detect-node-es@1.1.0: - resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} - detect-package-manager@2.0.1: - resolution: {integrity: sha512-j/lJHyoLlWi6G1LDdLgvUtz60Zo5GEj+sVYtTVXnYLDPuzgC3llMxonXym9zIwhhUII8vjdw0LXxavpLqTbl1A==} + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} - detect-port@1.6.1: - resolution: {integrity: sha512-CmnVc+Hek2egPx1PeTFVta2W78xy2K/9Rkf6cC4T59S50tVnzKj+tnx5mmx5lwvCkujZ4uRrpRSuV+IVs3f90Q==} - engines: {node: '>= 4.0.0'} - hasBin: true - - diff-sequences@29.6.3: - resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + clone-deep@4.0.1: + resolution: {integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==} + engines: {node: '>=6'} - dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} + clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} - doctrine@2.1.0: - resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} - engines: {node: '>=0.10.0'} + clsx@1.2.1: + resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==} + engines: {node: '>=6'} - doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} - doiuse@6.0.2: - resolution: {integrity: sha512-eBTs23NOX+EAYPr4RbCR6J4DRW/TML3uMo37y0X1whlkersDYFCk9HmCl09KX98cis22VKsV1QaxfVNauJ3NBw==} - engines: {node: '>=16'} - hasBin: true + co@4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} - dot-prop@5.3.0: - resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} - engines: {node: '>=8'} + collect-v8-coverage@1.0.2: + resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} - dotenv-expand@10.0.0: - resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==} - engines: {node: '>=12'} + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} - dotenv@16.4.5: - resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} - engines: {node: '>=12'} + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} - dotgitignore@2.1.0: - resolution: {integrity: sha512-sCm11ak2oY6DglEPpCB8TixLjWAxd3kJTs6UIcSasNYxXdFPV+YKlye92c8H4kKFqV5qYMIh7d+cYecEg0dIkA==} - engines: {node: '>=6'} + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - duplexify@3.7.1: - resolution: {integrity: sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==} + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - duplexify@4.1.3: - resolution: {integrity: sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==} + colord@2.9.3: + resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} - ee-first@1.1.1: - resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} - ejs@3.1.10: - resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} - engines: {node: '>=0.10.0'} - hasBin: true + commander@11.0.0: + resolution: {integrity: sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==} + engines: {node: '>=16'} - electron-to-chromium@1.4.805: - resolution: {integrity: sha512-8W4UJwX/w9T0QSzINJckTKG6CYpAUTqsaWcWIsdud3I1FYJcMgW9QqT1/4CBff/pP/TihWh13OmiyY8neto6vw==} + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - element-resize-detector@1.2.4: - resolution: {integrity: sha512-Fl5Ftk6WwXE0wqCgNoseKWndjzZlDCwuPTcoVZfCP9R3EHQF8qUtr3YUPNETegRBOKqQKPW3n4kiIWngGi8tKg==} + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} - emittery@0.13.1: - resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} - engines: {node: '>=12'} + commander@6.2.1: + resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} + engines: {node: '>= 6'} - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + commist@1.1.0: + resolution: {integrity: sha512-rraC8NXWOEjhADbZe9QBNzLAN5Q3fsTPQtBV+fEVj6xKIgDgNiEVE6ZNfHpZOqfQ21YUzfVNUXLOEZquYvQPPg==} - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + commondir@1.0.1: + resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} - emojis-list@3.0.0: - resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==} - engines: {node: '>= 4'} + compare-func@2.0.0: + resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} - encodeurl@1.0.2: - resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} - engines: {node: '>= 0.8'} + component-bind@1.0.0: + resolution: {integrity: sha512-WZveuKPeKAG9qY+FkYDeADzdHyTYdIboXS59ixDeRJL5ZhxpqUnxSOwop4FQjMsiYm3/Or8cegVbpAHNA7pHxw==} - end-of-stream@1.4.4: - resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + component-emitter@1.2.1: + resolution: {integrity: sha512-jPatnhd33viNplKjqXKRkGU345p263OIWzDL2wH3LGIGp5Kojo+uXizHmOADRvhGFFTnJqX3jBAKP6vvmSDKcA==} - engine.io-client@3.3.3: - resolution: {integrity: sha512-PXIgpzb1brtBzh8Q6vCjzCMeu4nfEPmaDm+L3Qb2sVHwLkxC1qRiBMSjOB0NJNjZ0hbPNUKQa+s8J2XxLOIEeQ==} + component-emitter@1.3.1: + resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} - engine.io-parser@2.1.3: - resolution: {integrity: sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==} + component-inherit@0.0.3: + resolution: {integrity: sha512-w+LhYREhatpVqTESyGFg3NlP6Iu0kEKUHETY9GoZP/pQyW4mHFZuFWRUCIqVPZ36ueVLtoOEZaAqbCF2RDndaA==} - enhanced-resolve@5.17.0: - resolution: {integrity: sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==} - engines: {node: '>=10.13.0'} + compressible@2.0.18: + resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} + engines: {node: '>= 0.6'} - env-paths@2.2.1: - resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} - engines: {node: '>=6'} + compression@1.7.4: + resolution: {integrity: sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==} + engines: {node: '>= 0.8.0'} - envinfo@7.13.0: - resolution: {integrity: sha512-cvcaMr7KqXVh4nyzGTVqTum+gAiL265x5jUWQIDLq//zOGbW+gSW/C+OWLleY/rs9Qole6AZLMXPbtIFQbqu+Q==} - engines: {node: '>=4'} - hasBin: true + computed-style@0.1.4: + resolution: {integrity: sha512-WpAmaKbMNmS3OProfHIdJiNleNJdgUrJfbKArXua28QF7+0CoZjlLn0lp6vlc+dl5r2/X9GQiQRQQU4BzSa69w==} - errno@0.1.8: - resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==} - hasBin: true + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - error-ex@1.3.2: - resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + concat-stream@1.6.2: + resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} + engines: {'0': node >= 0.8} - error-stack-parser@2.1.4: - resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==} + concat-stream@2.0.0: + resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} + engines: {'0': node >= 6.0} - es-abstract@1.23.3: - resolution: {integrity: sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==} - engines: {node: '>= 0.4'} + consola@3.2.3: + resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==} + engines: {node: ^14.18.0 || >=16.10.0} - es-define-property@1.0.0: - resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} - engines: {node: '>= 0.4'} - - es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} + content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} - es-module-lexer@0.9.3: - resolution: {integrity: sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==} + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} - es-module-lexer@1.5.3: - resolution: {integrity: sha512-i1gCgmR9dCl6Vil6UKPI/trA69s08g/syhiDK9TG0Nf1RJjjFI+AzoWW7sPufzkgYAn861skuCwJa0pIIHYxvg==} + conventional-changelog-angular@5.0.13: + resolution: {integrity: sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==} + engines: {node: '>=10'} - es-object-atoms@1.0.0: - resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} - engines: {node: '>= 0.4'} + conventional-changelog-atom@2.0.8: + resolution: {integrity: sha512-xo6v46icsFTK3bb7dY/8m2qvc8sZemRgdqLb/bjpBsH2UyOS8rKNTgcb5025Hri6IpANPApbXMg15QLb1LJpBw==} + engines: {node: '>=10'} - es-set-tostringtag@2.0.3: - resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} - engines: {node: '>= 0.4'} + conventional-changelog-codemirror@2.0.8: + resolution: {integrity: sha512-z5DAsn3uj1Vfp7po3gpt2Boc+Bdwmw2++ZHa5Ak9k0UKsYAO5mH1UBTN0qSCuJZREIhX6WU4E1p3IW2oRCNzQw==} + engines: {node: '>=10'} - es-shim-unscopables@1.0.2: - resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} + conventional-changelog-config-spec@2.1.0: + resolution: {integrity: sha512-IpVePh16EbbB02V+UA+HQnnPIohgXvJRxHcS5+Uwk4AT5LjzCZJm5sp/yqs5C6KZJ1jMsV4paEV13BN1pvDuxQ==} - es-to-primitive@1.2.1: - resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} - engines: {node: '>= 0.4'} + conventional-changelog-conventionalcommits@4.6.3: + resolution: {integrity: sha512-LTTQV4fwOM4oLPad317V/QNQ1FY4Hju5qeBIM1uTHbrnCE+Eg4CdRZ3gO2pUeR+tzWdp80M2j3qFFEDWVqOV4g==} + engines: {node: '>=10'} - esbuild-plugin-alias@0.2.1: - resolution: {integrity: sha512-jyfL/pwPqaFXyKnj8lP8iLk6Z0m099uXR45aSN8Av1XD4vhvQutxxPzgA2bTcAwQpa1zCXDcWOlhFgyP3GKqhQ==} + conventional-changelog-core@4.2.4: + resolution: {integrity: sha512-gDVS+zVJHE2v4SLc6B0sLsPiloR0ygU7HaDW14aNJE1v4SlqJPILPl/aJC7YdtRE4CybBf8gDwObBvKha8Xlyg==} + engines: {node: '>=10'} - esbuild-plugin-replace@1.4.0: - resolution: {integrity: sha512-lP3ZAyzyRa5JXoOd59lJbRKNObtK8pJ/RO7o6vdjwLi71GfbL32NR22ZuS7/cLZkr10/L1lutoLma8E4DLngYg==} + conventional-changelog-ember@2.0.9: + resolution: {integrity: sha512-ulzIReoZEvZCBDhcNYfDIsLTHzYHc7awh+eI44ZtV5cx6LVxLlVtEmcO+2/kGIHGtw+qVabJYjdI5cJOQgXh1A==} + engines: {node: '>=10'} - esbuild-register@3.5.0: - resolution: {integrity: sha512-+4G/XmakeBAsvJuDugJvtyF1x+XJT4FMocynNpxrvEBViirpfUn2PgNpCHedfWhF4WokNsO/OvMKrmJOIJsI5A==} - peerDependencies: - esbuild: '>=0.12 <1' + conventional-changelog-eslint@3.0.9: + resolution: {integrity: sha512-6NpUCMgU8qmWmyAMSZO5NrRd7rTgErjrm4VASam2u5jrZS0n38V7Y9CzTtLT2qwz5xEChDR4BduoWIr8TfwvXA==} + engines: {node: '>=10'} - esbuild@0.18.20: - resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} - engines: {node: '>=12'} - hasBin: true + conventional-changelog-express@2.0.6: + resolution: {integrity: sha512-SDez2f3iVJw6V563O3pRtNwXtQaSmEfTCaTBPCqn0oG0mfkq0rX4hHBq5P7De2MncoRixrALj3u3oQsNK+Q0pQ==} + engines: {node: '>=10'} - esbuild@0.19.12: - resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} - engines: {node: '>=12'} - hasBin: true + conventional-changelog-jquery@3.0.11: + resolution: {integrity: sha512-x8AWz5/Td55F7+o/9LQ6cQIPwrCjfJQ5Zmfqi8thwUEKHstEn4kTIofXub7plf1xvFA2TqhZlq7fy5OmV6BOMw==} + engines: {node: '>=10'} - escalade@3.1.2: - resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} - engines: {node: '>=6'} + conventional-changelog-jshint@2.0.9: + resolution: {integrity: sha512-wMLdaIzq6TNnMHMy31hql02OEQ8nCQfExw1SE0hYL5KvU+JCTuPaDO+7JiogGT2gJAxiUGATdtYYfh+nT+6riA==} + engines: {node: '>=10'} - escape-html@1.0.3: - resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + conventional-changelog-preset-loader@2.3.4: + resolution: {integrity: sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g==} + engines: {node: '>=10'} - escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} + conventional-changelog-writer@5.0.1: + resolution: {integrity: sha512-5WsuKUfxW7suLblAbFnxAcrvf6r+0b7GvNaWUwUIk0bXMnENP/PEieGKVUQrjPqwPT4o3EPAASBXiY6iHooLOQ==} + engines: {node: '>=10'} + hasBin: true - escape-string-regexp@2.0.0: - resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} - engines: {node: '>=8'} + conventional-changelog@3.1.25: + resolution: {integrity: sha512-ryhi3fd1mKf3fSjbLXOfK2D06YwKNic1nC9mWqybBHdObPd8KJ2vjaXZfYj1U23t+V8T8n0d7gwnc9XbIdFbyQ==} + engines: {node: '>=10'} - escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + conventional-commits-filter@2.0.7: + resolution: {integrity: sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA==} engines: {node: '>=10'} - escodegen@2.1.0: - resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} - engines: {node: '>=6.0'} + conventional-commits-parser@3.2.4: + resolution: {integrity: sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==} + engines: {node: '>=10'} hasBin: true - eslint-config-prettier@9.1.0: - resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} + conventional-recommended-bump@6.1.0: + resolution: {integrity: sha512-uiApbSiNGM/kkdL9GTOLAqC4hbptObFo4wW2QRyHsKciGAfQuLU1ShZ1BIVI/+K2BE/W1AWYQMCXAsv4dyKPaw==} + engines: {node: '>=10'} hasBin: true - peerDependencies: - eslint: '>=7.0.0' - eslint-import-resolver-node@0.3.9: - resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - eslint-import-resolver-typescript@3.6.1: - resolution: {integrity: sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - eslint: '*' - eslint-plugin-import: '*' + cookie-signature@1.0.6: + resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} - eslint-module-utils@2.8.1: - resolution: {integrity: sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: '*' - eslint-import-resolver-node: '*' - eslint-import-resolver-typescript: '*' - eslint-import-resolver-webpack: '*' - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - eslint: - optional: true - eslint-import-resolver-node: - optional: true - eslint-import-resolver-typescript: - optional: true - eslint-import-resolver-webpack: - optional: true + cookie@0.6.0: + resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} + engines: {node: '>= 0.6'} - eslint-plugin-import@2.29.1: - resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==} - engines: {node: '>=4'} + copy-anything@2.0.6: + resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==} + + copy-to-clipboard@3.3.3: + resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==} + + core-js-compat@3.37.1: + resolution: {integrity: sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + cosmiconfig@9.0.0: + resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} + engines: {node: '>=14'} peerDependencies: - '@typescript-eslint/parser': '*' - eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 + typescript: '>=4.9.5' peerDependenciesMeta: - '@typescript-eslint/parser': + typescript: optional: true - eslint-plugin-jest@27.9.0: - resolution: {integrity: sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug==} + create-jest@29.7.0: + resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@typescript-eslint/eslint-plugin': ^5.0.0 || ^6.0.0 || ^7.0.0 - eslint: ^7.0.0 || ^8.0.0 - jest: '*' - peerDependenciesMeta: - '@typescript-eslint/eslint-plugin': - optional: true - jest: - optional: true + hasBin: true - eslint-scope@5.1.1: - resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} - engines: {node: '>=8.0.0'} + cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} - eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + crypto-random-string@2.0.0: + resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} + engines: {node: '>=8'} - eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + css-color-keywords@1.0.0: + resolution: {integrity: sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==} + engines: {node: '>=4'} - eslint@8.57.0: - resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - hasBin: true + css-functions-list@3.2.2: + resolution: {integrity: sha512-c+N0v6wbKVxTu5gOBBFkr9BEdBWaqqjQeiJ8QvSRIJOf+UxlJh930m8e6/WNeODIK0mYLFkoONrnj16i2EcvfQ==} + engines: {node: '>=12 || >=16'} - espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + css-in-js-utils@3.1.0: + resolution: {integrity: sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A==} - esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true + css-to-react-native@3.2.0: + resolution: {integrity: sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==} - esquery@1.5.0: - resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} - engines: {node: '>=0.10'} + css-tokenize@1.0.1: + resolution: {integrity: sha512-gLmmbJdwH9HLY4bcA17lnZ8GgPwEXRbvxBJGHnkiB6gLhRpTzjkjtMIvz7YORGW/Ptv2oMk8b5g+u7mRD6Dd7A==} - esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} + css-tree@1.1.3: + resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} + engines: {node: '>=8.0.0'} - estraverse@4.3.0: - resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} - engines: {node: '>=4.0'} + css-tree@2.3.1: + resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} - estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true - estree-walker@2.0.2: - resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} + dargs@7.0.0: + resolution: {integrity: sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==} + engines: {node: '>=8'} - etag@1.8.1: - resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} - engines: {node: '>= 0.6'} + data-view-buffer@1.0.1: + resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} + engines: {node: '>= 0.4'} - eventemitter3@5.0.1: - resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + data-view-byte-length@1.0.1: + resolution: {integrity: sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==} + engines: {node: '>= 0.4'} - events@3.3.0: - resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} - engines: {node: '>=0.8.x'} + data-view-byte-offset@1.0.0: + resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} + engines: {node: '>= 0.4'} - execa@5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} - engines: {node: '>=10'} + dateformat@3.0.3: + resolution: {integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==} - execa@7.2.0: - resolution: {integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==} - engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0} + dayjs@1.11.11: + resolution: {integrity: sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==} - execa@8.0.1: - resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} - engines: {node: '>=16.17'} + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true - exit@0.1.2: - resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} - engines: {node: '>= 0.8.0'} + debug@3.1.0: + resolution: {integrity: sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true - expect@29.7.0: - resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true - express@4.19.2: - resolution: {integrity: sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==} - engines: {node: '>= 0.10.0'} + debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true - extend@3.0.2: - resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + debug@4.3.5: + resolution: {integrity: sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true - extract-colors@4.0.6: - resolution: {integrity: sha512-U+pYyQKXCSHOmtZPIEJBGLJjLDiqS+oOub2ILA3a7UGt9+IvZvwAN3hOPFjUgT+gX/apSBwP5vBgnKMlV0fy8Q==} + decamelize-keys@1.1.1: + resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} + engines: {node: '>=0.10.0'} - extract-zip@1.7.0: - resolution: {integrity: sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==} - hasBin: true + decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + dedent@1.5.3: + resolution: {integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true - fast-glob@3.3.2: - resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} - engines: {node: '>=8.6.0'} + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} - fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + default-browser-id@3.0.0: + resolution: {integrity: sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==} + engines: {node: '>=12'} - fast-loops@1.1.3: - resolution: {integrity: sha512-8EZzEP0eKkEEVX+drtd9mtuQ+/QrlfW/5MlwcwK5Nds6EkZ/tRzEexkzUY2mIssnAyVLT+TKHuRXmFNNXYUd6g==} + defaults@1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} - fast-shallow-equal@1.0.0: - resolution: {integrity: sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==} + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} - fastest-levenshtein@1.0.16: - resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} - engines: {node: '>= 4.9.1'} + define-lazy-prop@2.0.0: + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + engines: {node: '>=8'} - fastest-stable-stringify@2.0.2: - resolution: {integrity: sha512-bijHueCGd0LqqNK9b5oCMHc0MluJAx0cwqASgbWMvkO01lCYgIhacVRLcaDz3QnyYIRNJRDwMb41VuT6pHJ91Q==} + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} - fastq@1.17.1: - resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} - fb-watchman@2.0.2: - resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + del@6.1.1: + resolution: {integrity: sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==} + engines: {node: '>=10'} - fd-slicer@1.1.0: - resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} - fetch-retry@5.0.6: - resolution: {integrity: sha512-3yurQZ2hD9VISAhJJP9bpYFNQrHHBXE2JxxjY5aLEcDi46RmAzJE2OC9FAde0yis5ElW0jTTzs0zfg/Cca4XqQ==} + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} - figures@3.2.0: - resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} - engines: {node: '>=8'} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} - file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - file-entry-cache@9.0.0: - resolution: {integrity: sha512-6MgEugi8p2tiUhqO7GnPsmbCCzj0YRCwwaTbpGRyKZesjRSzkqkAE9fPp7V2yMs5hwfgbQLgdvSSkGNg1s5Uvw==} - engines: {node: '>=18'} + detect-indent@6.1.0: + resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} + engines: {node: '>=8'} - file-loader@6.2.0: - resolution: {integrity: sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==} - engines: {node: '>= 10.13.0'} - peerDependencies: - webpack: ^4.0.0 || ^5.0.0 + detect-newline@3.1.0: + resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} + engines: {node: '>=8'} - file-system-cache@2.3.0: - resolution: {integrity: sha512-l4DMNdsIPsVnKrgEXbJwDJsA5mB8rGwHYERMgqQx/xAUtChPJMre1bXBzDEqqVbWv9AIbFezXMxeEkZDSrXUOQ==} + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} - filelist@1.0.4: - resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} + detect-package-manager@2.0.1: + resolution: {integrity: sha512-j/lJHyoLlWi6G1LDdLgvUtz60Zo5GEj+sVYtTVXnYLDPuzgC3llMxonXym9zIwhhUII8vjdw0LXxavpLqTbl1A==} + engines: {node: '>=12'} - filesize@9.0.11: - resolution: {integrity: sha512-gTAiTtI0STpKa5xesyTA9hA3LX4ga8sm2nWRcffEa1L/5vQwb4mj2MdzMkoHoGv4QzfDshQZuYscQSf8c4TKOA==} - engines: {node: '>= 0.4.0'} + detect-port@1.6.1: + resolution: {integrity: sha512-CmnVc+Hek2egPx1PeTFVta2W78xy2K/9Rkf6cC4T59S50tVnzKj+tnx5mmx5lwvCkujZ4uRrpRSuV+IVs3f90Q==} + engines: {node: '>= 4.0.0'} + hasBin: true - fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} - finalhandler@1.2.0: - resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} - engines: {node: '>= 0.8'} + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} - find-cache-dir@2.1.0: - resolution: {integrity: sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==} - engines: {node: '>=6'} + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} - find-cache-dir@3.3.2: - resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==} + doiuse@6.0.2: + resolution: {integrity: sha512-eBTs23NOX+EAYPr4RbCR6J4DRW/TML3uMo37y0X1whlkersDYFCk9HmCl09KX98cis22VKsV1QaxfVNauJ3NBw==} + engines: {node: '>=16'} + hasBin: true + + dot-prop@5.3.0: + resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} engines: {node: '>=8'} - find-up@2.1.0: - resolution: {integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==} - engines: {node: '>=4'} + dotenv-expand@10.0.0: + resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==} + engines: {node: '>=12'} - find-up@3.0.0: - resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} + dotenv@16.4.5: + resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} + engines: {node: '>=12'} + + dotgitignore@2.1.0: + resolution: {integrity: sha512-sCm11ak2oY6DglEPpCB8TixLjWAxd3kJTs6UIcSasNYxXdFPV+YKlye92c8H4kKFqV5qYMIh7d+cYecEg0dIkA==} engines: {node: '>=6'} - find-up@4.1.0: - resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} - engines: {node: '>=8'} + duplexify@3.7.1: + resolution: {integrity: sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==} - find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} + duplexify@4.1.3: + resolution: {integrity: sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==} - flat-cache@3.2.0: - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} - engines: {node: ^10.12.0 || >=12.0.0} + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - flat-cache@5.0.0: - resolution: {integrity: sha512-JrqFmyUl2PnPi1OvLyTVHnQvwQ0S+e6lGSwu8OkAZlSaNIZciTY2H/cOOROxsBA1m/LZNHDsqAgDZt6akWcjsQ==} - engines: {node: '>=18'} + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - flatted@3.3.1: - resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + ejs@3.1.10: + resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} + engines: {node: '>=0.10.0'} + hasBin: true - flow-parser@0.238.0: - resolution: {integrity: sha512-VE7XSv1epljsIN2YeBnxCmGJihpNIAnLLu/pPOdA+Gkso7qDltJwUi6vfHjgxdBbjSdAuPGnhuOHJUQG+yYwIg==} - engines: {node: '>=0.4.0'} + electron-to-chromium@1.4.805: + resolution: {integrity: sha512-8W4UJwX/w9T0QSzINJckTKG6CYpAUTqsaWcWIsdud3I1FYJcMgW9QqT1/4CBff/pP/TihWh13OmiyY8neto6vw==} - follow-redirects@1.15.6: - resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true + element-resize-detector@1.2.4: + resolution: {integrity: sha512-Fl5Ftk6WwXE0wqCgNoseKWndjzZlDCwuPTcoVZfCP9R3EHQF8qUtr3YUPNETegRBOKqQKPW3n4kiIWngGi8tKg==} - for-each@0.3.3: - resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + emittery@0.13.1: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} + engines: {node: '>=12'} - foreground-child@3.2.1: - resolution: {integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==} - engines: {node: '>=14'} + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - form-data@4.0.0: - resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} - engines: {node: '>= 6'} + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - forwarded@0.2.0: - resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} - engines: {node: '>= 0.6'} + emojis-list@3.0.0: + resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==} + engines: {node: '>= 4'} - fraction.js@4.3.7: - resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} - framer-motion@11.2.10: - resolution: {integrity: sha512-/gr3PLZUVFCc86a9MqCUboVrALscrdluzTb3yew+2/qKBU8CX6nzs918/SRBRCqaPbx0TZP10CB6yFgK2C5cYQ==} - peerDependencies: - '@emotion/is-prop-valid': '*' - react: ^18.0.0 - react-dom: ^18.0.0 - peerDependenciesMeta: - '@emotion/is-prop-valid': - optional: true - react: - optional: true - react-dom: - optional: true + end-of-stream@1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} - fresh@0.5.2: - resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} - engines: {node: '>= 0.6'} + engine.io-client@3.3.3: + resolution: {integrity: sha512-PXIgpzb1brtBzh8Q6vCjzCMeu4nfEPmaDm+L3Qb2sVHwLkxC1qRiBMSjOB0NJNjZ0hbPNUKQa+s8J2XxLOIEeQ==} - fs-constants@1.0.0: - resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + engine.io-parser@2.1.3: + resolution: {integrity: sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==} - fs-extra@11.1.1: - resolution: {integrity: sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==} - engines: {node: '>=14.14'} + enhanced-resolve@5.17.0: + resolution: {integrity: sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==} + engines: {node: '>=10.13.0'} - fs-extra@11.2.0: - resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==} - engines: {node: '>=14.14'} + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} - fs-minipass@2.1.0: - resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} - engines: {node: '>= 8'} + envinfo@7.13.0: + resolution: {integrity: sha512-cvcaMr7KqXVh4nyzGTVqTum+gAiL265x5jUWQIDLq//zOGbW+gSW/C+OWLleY/rs9Qole6AZLMXPbtIFQbqu+Q==} + engines: {node: '>=4'} + hasBin: true - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + errno@0.1.8: + resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==} + hasBin: true - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] + error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + error-stack-parser@2.1.4: + resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==} - function.prototype.name@1.1.6: - resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} + es-abstract@1.23.3: + resolution: {integrity: sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==} engines: {node: '>= 0.4'} - functions-have-names@1.2.3: - resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - - gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - - get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - - get-intrinsic@1.2.4: - resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} + es-define-property@1.0.0: + resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} engines: {node: '>= 0.4'} - get-nonce@1.0.1: - resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} - engines: {node: '>=6'} - - get-npm-tarball-url@2.1.0: - resolution: {integrity: sha512-ro+DiMu5DXgRBabqXupW38h7WPZ9+Ad8UjwhvsmmN8w1sU7ab0nzAXvVZ4kqYg57OrqomRtJvepX5/xvFKNtjA==} - engines: {node: '>=12.17'} + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} - get-package-type@0.1.0: - resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} - engines: {node: '>=8.0.0'} + es-module-lexer@0.9.3: + resolution: {integrity: sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==} - get-pkg-repo@4.2.1: - resolution: {integrity: sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA==} - engines: {node: '>=6.9.0'} - hasBin: true + es-module-lexer@1.5.3: + resolution: {integrity: sha512-i1gCgmR9dCl6Vil6UKPI/trA69s08g/syhiDK9TG0Nf1RJjjFI+AzoWW7sPufzkgYAn861skuCwJa0pIIHYxvg==} - get-port@5.1.1: - resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} - engines: {node: '>=8'} + es-object-atoms@1.0.0: + resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} + engines: {node: '>= 0.4'} - get-stream@6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} + es-set-tostringtag@2.0.3: + resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} + engines: {node: '>= 0.4'} - get-stream@8.0.1: - resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} - engines: {node: '>=16'} + es-shim-unscopables@1.0.2: + resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} - get-symbol-description@1.0.2: - resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} + es-to-primitive@1.2.1: + resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} engines: {node: '>= 0.4'} - get-tsconfig@4.7.5: - resolution: {integrity: sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==} - - giget@1.2.3: - resolution: {integrity: sha512-8EHPljDvs7qKykr6uw8b+lqLiUc/vUg+KVTI0uND4s63TdsZM2Xus3mflvF0DDG9SiM4RlCkFGL+7aAjRmV7KA==} - hasBin: true + esbuild-plugin-alias@0.2.1: + resolution: {integrity: sha512-jyfL/pwPqaFXyKnj8lP8iLk6Z0m099uXR45aSN8Av1XD4vhvQutxxPzgA2bTcAwQpa1zCXDcWOlhFgyP3GKqhQ==} - git-raw-commits@2.0.11: - resolution: {integrity: sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==} - engines: {node: '>=10'} - hasBin: true + esbuild-plugin-replace@1.4.0: + resolution: {integrity: sha512-lP3ZAyzyRa5JXoOd59lJbRKNObtK8pJ/RO7o6vdjwLi71GfbL32NR22ZuS7/cLZkr10/L1lutoLma8E4DLngYg==} - git-remote-origin-url@2.0.0: - resolution: {integrity: sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw==} - engines: {node: '>=4'} + esbuild-register@3.5.0: + resolution: {integrity: sha512-+4G/XmakeBAsvJuDugJvtyF1x+XJT4FMocynNpxrvEBViirpfUn2PgNpCHedfWhF4WokNsO/OvMKrmJOIJsI5A==} + peerDependencies: + esbuild: '>=0.12 <1' - git-semver-tags@4.1.1: - resolution: {integrity: sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA==} - engines: {node: '>=10'} + esbuild@0.18.20: + resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} + engines: {node: '>=12'} hasBin: true - gitconfiglocal@1.0.0: - resolution: {integrity: sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ==} - - github-slugger@1.5.0: - resolution: {integrity: sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==} - - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - - glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} - - glob-promise@4.2.2: - resolution: {integrity: sha512-xcUzJ8NWN5bktoTIX7eOclO1Npxd/dyVqUJxlLIDasT4C7KZyqlPIwkdJ0Ypiy3p2ZKahTjK4M9uC3sNSfNMzw==} + esbuild@0.19.12: + resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} engines: {node: '>=12'} - peerDependencies: - glob: ^7.1.6 - - glob-to-regexp@0.4.1: - resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} - - glob@10.4.1: - resolution: {integrity: sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==} - engines: {node: '>=16 || 14 >=14.18'} hasBin: true - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported - - global-modules@2.0.0: - resolution: {integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==} + escalade@3.1.2: + resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} engines: {node: '>=6'} - global-prefix@3.0.0: - resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==} - engines: {node: '>=6'} + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - globals@11.12.0: - resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} - engines: {node: '>=4'} + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} - globals@13.24.0: - resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} engines: {node: '>=8'} - globalthis@1.0.4: - resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} - engines: {node: '>= 0.4'} - - globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - globjoin@0.1.4: - resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==} - - globrex@0.1.2: - resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + escodegen@2.1.0: + resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} + engines: {node: '>=6.0'} + hasBin: true - gopd@1.0.1: - resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + eslint-config-prettier@9.1.0: + resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + eslint-import-resolver-node@0.3.9: + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} - graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + eslint-import-resolver-typescript@3.6.1: + resolution: {integrity: sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '*' + eslint-plugin-import: '*' - gunzip-maybe@1.4.2: - resolution: {integrity: sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==} - hasBin: true + eslint-module-utils@2.8.1: + resolution: {integrity: sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true - handlebars@4.7.8: - resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} - engines: {node: '>=0.4.7'} - hasBin: true + eslint-plugin-import@2.29.1: + resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true - hard-rejection@2.1.0: - resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} - engines: {node: '>=6'} + eslint-plugin-jest@27.9.0: + resolution: {integrity: sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@typescript-eslint/eslint-plugin': ^5.0.0 || ^6.0.0 || ^7.0.0 + eslint: ^7.0.0 || ^8.0.0 + jest: '*' + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + jest: + optional: true - has-bigints@1.0.2: - resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} - has-binary2@1.0.3: - resolution: {integrity: sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==} + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - has-cors@1.1.0: - resolution: {integrity: sha512-g5VNKdkFuUuVCP9gYfDJHjK2nqdQJ7aDLTnycnc2+RvsOQbuLdF5pm7vuE5J76SEBIQjs4kQY/BWq74JUmjbXA==} + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - has-flag@3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} + eslint@8.57.0: + resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - has-property-descriptors@1.0.2: - resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true - has-proto@1.0.3: - resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} - engines: {node: '>= 0.4'} + esquery@1.5.0: + resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} + engines: {node: '>=0.10'} - has-symbols@1.0.3: - resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} - engines: {node: '>= 0.4'} + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} - has-tostringtag@1.0.2: - resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} - engines: {node: '>= 0.4'} + estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} - help-me@3.0.0: - resolution: {integrity: sha512-hx73jClhyk910sidBB7ERlnhMlFsJJIBqSVMFDwPN8o2v9nmp5KgLq1Xz1Bf1fCMMZ6mPrX159iG0VLy/fPMtQ==} + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - hls.js@1.5.11: - resolution: {integrity: sha512-q3We1izi2+qkOO+TvZdHv+dx6aFzdtk3xc1/Qesrvto4thLTT/x/1FK85c5h1qZE4MmMBNgKg+MIW8nxQfxwBw==} + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} - hoist-non-react-statics@3.3.2: - resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} - hosted-git-info@2.8.9: - resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} - hosted-git-info@4.1.0: - resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} - engines: {node: '>=10'} + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} - html-escaper@2.0.2: - resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} - html-tags@3.3.1: - resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==} - engines: {node: '>=8'} + execa@7.2.0: + resolution: {integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==} + engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0} - http-errors@2.0.0: - resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} - engines: {node: '>= 0.8'} + execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} - https-proxy-agent@4.0.0: - resolution: {integrity: sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==} - engines: {node: '>= 6.0.0'} + exit@0.1.2: + resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} + engines: {node: '>= 0.8.0'} - human-signals@2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} - engines: {node: '>=10.17.0'} + expect@29.7.0: + resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - human-signals@4.3.1: - resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==} - engines: {node: '>=14.18.0'} + express@4.19.2: + resolution: {integrity: sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==} + engines: {node: '>= 0.10.0'} - human-signals@5.0.0: - resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} - engines: {node: '>=16.17.0'} + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - humanize-ms@1.2.1: - resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + extract-colors@4.0.6: + resolution: {integrity: sha512-U+pYyQKXCSHOmtZPIEJBGLJjLDiqS+oOub2ILA3a7UGt9+IvZvwAN3hOPFjUgT+gX/apSBwP5vBgnKMlV0fy8Q==} - husky@8.0.3: - resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==} - engines: {node: '>=14'} + extract-zip@1.7.0: + resolution: {integrity: sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==} hasBin: true - hyphenate-style-name@1.1.0: - resolution: {integrity: sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==} + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - iconv-lite@0.4.24: - resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} - engines: {node: '>=0.10.0'} + fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} - iconv-lite@0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} - engines: {node: '>=0.10.0'} + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - icss-utils@5.1.0: - resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + fast-loops@1.1.3: + resolution: {integrity: sha512-8EZzEP0eKkEEVX+drtd9mtuQ+/QrlfW/5MlwcwK5Nds6EkZ/tRzEexkzUY2mIssnAyVLT+TKHuRXmFNNXYUd6g==} - ignore@5.3.1: - resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} - engines: {node: '>= 4'} + fast-shallow-equal@1.0.0: + resolution: {integrity: sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==} - image-size@0.5.5: - resolution: {integrity: sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==} - engines: {node: '>=0.10.0'} - hasBin: true + fastest-levenshtein@1.0.16: + resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} + engines: {node: '>= 4.9.1'} - immutable@4.3.6: - resolution: {integrity: sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==} + fastest-stable-stringify@2.0.2: + resolution: {integrity: sha512-bijHueCGd0LqqNK9b5oCMHc0MluJAx0cwqASgbWMvkO01lCYgIhacVRLcaDz3QnyYIRNJRDwMb41VuT6pHJ91Q==} - import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} - engines: {node: '>=6'} + fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} - import-local@3.1.0: - resolution: {integrity: sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==} - engines: {node: '>=8'} - hasBin: true + fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} - imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} + fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} - indent-string@4.0.0: - resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + fetch-retry@5.0.6: + resolution: {integrity: sha512-3yurQZ2hD9VISAhJJP9bpYFNQrHHBXE2JxxjY5aLEcDi46RmAzJE2OC9FAde0yis5ElW0jTTzs0zfg/Cca4XqQ==} + + figures@3.2.0: + resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} - indexof@0.0.1: - resolution: {integrity: sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg==} + file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} - inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + file-entry-cache@9.0.0: + resolution: {integrity: sha512-6MgEugi8p2tiUhqO7GnPsmbCCzj0YRCwwaTbpGRyKZesjRSzkqkAE9fPp7V2yMs5hwfgbQLgdvSSkGNg1s5Uvw==} + engines: {node: '>=18'} - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + file-loader@6.2.0: + resolution: {integrity: sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==} + engines: {node: '>= 10.13.0'} + peerDependencies: + webpack: ^4.0.0 || ^5.0.0 - ini@1.3.8: - resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + file-system-cache@2.3.0: + resolution: {integrity: sha512-l4DMNdsIPsVnKrgEXbJwDJsA5mB8rGwHYERMgqQx/xAUtChPJMre1bXBzDEqqVbWv9AIbFezXMxeEkZDSrXUOQ==} - inline-style-prefixer@7.0.0: - resolution: {integrity: sha512-I7GEdScunP1dQ6IM2mQWh6v0mOYdYmH3Bp31UecKdrcUgcURTcctSe1IECdUznSHKSmsHtjrT3CwCPI1pyxfUQ==} + filelist@1.0.4: + resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} - internal-slot@1.0.7: - resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} - engines: {node: '>= 0.4'} + filesize@9.0.11: + resolution: {integrity: sha512-gTAiTtI0STpKa5xesyTA9hA3LX4ga8sm2nWRcffEa1L/5vQwb4mj2MdzMkoHoGv4QzfDshQZuYscQSf8c4TKOA==} + engines: {node: '>= 0.4.0'} - intl-messageformat@10.5.14: - resolution: {integrity: sha512-IjC6sI0X7YRjjyVH9aUgdftcmZK7WXdHeil4KwbjDnRWjnVitKpAx3rr6t6di1joFp5188VqKcobOPA6mCLG/w==} + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} - invariant@2.2.4: - resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} + finalhandler@1.2.0: + resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} + engines: {node: '>= 0.8'} - ip@2.0.1: - resolution: {integrity: sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==} + find-cache-dir@2.1.0: + resolution: {integrity: sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==} + engines: {node: '>=6'} - ipaddr.js@1.9.1: - resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} - engines: {node: '>= 0.10'} + find-cache-dir@3.3.2: + resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==} + engines: {node: '>=8'} - is-absolute-url@3.0.3: - resolution: {integrity: sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==} + find-up@2.1.0: + resolution: {integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==} + engines: {node: '>=4'} + + find-up@3.0.0: + resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} + engines: {node: '>=6'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} - is-arguments@1.1.1: - resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} - engines: {node: '>= 0.4'} + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} - is-array-buffer@3.0.4: - resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} - engines: {node: '>= 0.4'} + flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} - is-arrayish@0.2.1: - resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + flat-cache@5.0.0: + resolution: {integrity: sha512-JrqFmyUl2PnPi1OvLyTVHnQvwQ0S+e6lGSwu8OkAZlSaNIZciTY2H/cOOROxsBA1m/LZNHDsqAgDZt6akWcjsQ==} + engines: {node: '>=18'} - is-bigint@1.0.4: - resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + flatted@3.3.1: + resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} + flow-parser@0.238.0: + resolution: {integrity: sha512-VE7XSv1epljsIN2YeBnxCmGJihpNIAnLLu/pPOdA+Gkso7qDltJwUi6vfHjgxdBbjSdAuPGnhuOHJUQG+yYwIg==} + engines: {node: '>=0.4.0'} - is-boolean-object@1.1.2: - resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} - engines: {node: '>= 0.4'} + follow-redirects@1.15.6: + resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true - is-callable@1.2.7: - resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} - engines: {node: '>= 0.4'} + for-each@0.3.3: + resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} - is-core-module@2.13.1: - resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} + foreground-child@3.2.1: + resolution: {integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==} + engines: {node: '>=14'} - is-data-view@1.0.1: - resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==} - engines: {node: '>= 0.4'} + form-data@4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} - is-date-object@1.0.5: - resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} - engines: {node: '>= 0.4'} + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} - is-deflate@1.0.0: - resolution: {integrity: sha512-YDoFpuZWu1VRXlsnlYMzKyVRITXj7Ej/V9gXQ2/pAe7X1J7M/RNOqaIYi6qUn+B7nGyB9pDXrv02dsB58d2ZAQ==} + fraction.js@4.3.7: + resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} - is-docker@2.2.1: - resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} - engines: {node: '>=8'} - hasBin: true + framer-motion@11.2.10: + resolution: {integrity: sha512-/gr3PLZUVFCc86a9MqCUboVrALscrdluzTb3yew+2/qKBU8CX6nzs918/SRBRCqaPbx0TZP10CB6yFgK2C5cYQ==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 + react-dom: ^18.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} - is-fullwidth-code-point@4.0.0: - resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} - engines: {node: '>=12'} + fs-extra@11.1.1: + resolution: {integrity: sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==} + engines: {node: '>=14.14'} - is-generator-fn@2.1.0: - resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} - engines: {node: '>=6'} + fs-extra@11.2.0: + resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==} + engines: {node: '>=14.14'} - is-generator-function@1.0.10: - resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} - engines: {node: '>= 0.4'} + fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - is-gzip@1.0.0: - resolution: {integrity: sha512-rcfALRIb1YewtnksfRIHGcIY93QnK8BIQ/2c9yDYcG/Y6+vRoJuTWBmmSEbyLLYtXm7q35pHOHbZFQBaLrhlWQ==} - engines: {node: '>=0.10.0'} + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] - is-interactive@1.0.0: - resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} - engines: {node: '>=8'} + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - is-nan@1.3.2: - resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==} + function.prototype.name@1.1.6: + resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} engines: {node: '>= 0.4'} - is-negative-zero@2.0.3: - resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} - engines: {node: '>= 0.4'} + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - is-number-object@1.0.7: - resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} - engines: {node: '>= 0.4'} + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} - is-obj@2.0.0: - resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} - engines: {node: '>=8'} + get-intrinsic@1.2.4: + resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} + engines: {node: '>= 0.4'} - is-path-cwd@2.2.0: - resolution: {integrity: sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==} + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} engines: {node: '>=6'} - is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} + get-npm-tarball-url@2.1.0: + resolution: {integrity: sha512-ro+DiMu5DXgRBabqXupW38h7WPZ9+Ad8UjwhvsmmN8w1sU7ab0nzAXvVZ4kqYg57OrqomRtJvepX5/xvFKNtjA==} + engines: {node: '>=12.17'} - is-plain-obj@1.1.0: - resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} - engines: {node: '>=0.10.0'} + get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} - is-plain-object@2.0.4: - resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} - engines: {node: '>=0.10.0'} + get-pkg-repo@4.2.1: + resolution: {integrity: sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA==} + engines: {node: '>=6.9.0'} + hasBin: true - is-plain-object@5.0.0: - resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} - engines: {node: '>=0.10.0'} - - is-regex@1.1.4: - resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} - engines: {node: '>= 0.4'} - - is-shared-array-buffer@1.0.3: - resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} - engines: {node: '>= 0.4'} - - is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + get-port@5.1.1: + resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} engines: {node: '>=8'} - is-stream@3.0.0: - resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} - is-string@1.0.7: - resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} - engines: {node: '>= 0.4'} + get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} - is-symbol@1.0.4: - resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + get-symbol-description@1.0.2: + resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} engines: {node: '>= 0.4'} - is-text-path@1.0.1: - resolution: {integrity: sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==} - engines: {node: '>=0.10.0'} + get-tsconfig@4.7.5: + resolution: {integrity: sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==} - is-typed-array@1.1.13: - resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} - engines: {node: '>= 0.4'} + giget@1.2.3: + resolution: {integrity: sha512-8EHPljDvs7qKykr6uw8b+lqLiUc/vUg+KVTI0uND4s63TdsZM2Xus3mflvF0DDG9SiM4RlCkFGL+7aAjRmV7KA==} + hasBin: true - is-unicode-supported@0.1.0: - resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + git-raw-commits@2.0.11: + resolution: {integrity: sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==} engines: {node: '>=10'} + hasBin: true - is-weakref@1.0.2: - resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + git-remote-origin-url@2.0.0: + resolution: {integrity: sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw==} + engines: {node: '>=4'} - is-what@3.14.1: - resolution: {integrity: sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==} + git-semver-tags@4.1.1: + resolution: {integrity: sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA==} + engines: {node: '>=10'} + hasBin: true - is-wsl@2.2.0: - resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} - engines: {node: '>=8'} + gitconfiglocal@1.0.0: + resolution: {integrity: sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ==} - isarray@0.0.1: - resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==} + github-slugger@1.5.0: + resolution: {integrity: sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==} - isarray@1.0.0: - resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} - isarray@2.0.1: - resolution: {integrity: sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ==} + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} - isarray@2.0.5: - resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + glob-promise@4.2.2: + resolution: {integrity: sha512-xcUzJ8NWN5bktoTIX7eOclO1Npxd/dyVqUJxlLIDasT4C7KZyqlPIwkdJ0Ypiy3p2ZKahTjK4M9uC3sNSfNMzw==} + engines: {node: '>=12'} + peerDependencies: + glob: ^7.1.6 - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + glob-to-regexp@0.4.1: + resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} - isobject@3.0.1: - resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} - engines: {node: '>=0.10.0'} + glob@10.4.1: + resolution: {integrity: sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==} + engines: {node: '>=16 || 14 >=14.18'} + hasBin: true - istanbul-lib-coverage@3.2.2: - resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} - engines: {node: '>=8'} + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported - istanbul-lib-instrument@5.2.1: - resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} + global-modules@2.0.0: + resolution: {integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==} + engines: {node: '>=6'} + + global-prefix@3.0.0: + resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==} + engines: {node: '>=6'} + + globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} engines: {node: '>=8'} - istanbul-lib-instrument@6.0.2: - resolution: {integrity: sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw==} - engines: {node: '>=10'} + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} - istanbul-lib-report@3.0.1: - resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} - istanbul-lib-source-maps@4.0.1: - resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} - engines: {node: '>=10'} + globjoin@0.1.4: + resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==} - istanbul-reports@3.1.7: - resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} - engines: {node: '>=8'} + globrex@0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} - jackspeak@3.4.0: - resolution: {integrity: sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==} - engines: {node: '>=14'} + gopd@1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} - jake@10.9.1: - resolution: {integrity: sha512-61btcOHNnLnsOdtLgA5efqQWjnSi/vow5HbI7HMdKKWqvrKR1bLK3BPlJn9gcSaP2ewuamUSMB5XEy76KUIS2w==} - engines: {node: '>=10'} - hasBin: true + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - jest-changed-files@29.7.0: - resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - jest-circus@29.7.0: - resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + gunzip-maybe@1.4.2: + resolution: {integrity: sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==} + hasBin: true - jest-cli@29.7.0: - resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + handlebars@4.7.8: + resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} + engines: {node: '>=0.4.7'} hasBin: true - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - jest-config@29.7.0: - resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@types/node': '*' - ts-node: '>=9.0.0' - peerDependenciesMeta: - '@types/node': - optional: true - ts-node: - optional: true + hard-rejection@2.1.0: + resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} + engines: {node: '>=6'} - jest-diff@29.7.0: - resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + has-bigints@1.0.2: + resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} - jest-docblock@29.7.0: - resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + has-binary2@1.0.3: + resolution: {integrity: sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==} - jest-each@29.7.0: - resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + has-cors@1.1.0: + resolution: {integrity: sha512-g5VNKdkFuUuVCP9gYfDJHjK2nqdQJ7aDLTnycnc2+RvsOQbuLdF5pm7vuE5J76SEBIQjs4kQY/BWq74JUmjbXA==} - jest-environment-node@29.7.0: - resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} - jest-get-type@29.6.3: - resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} - jest-haste-map@29.7.0: - resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} - jest-leak-detector@29.7.0: - resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + has-proto@1.0.3: + resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} + engines: {node: '>= 0.4'} - jest-matcher-utils@29.7.0: - resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + has-symbols@1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} - jest-message-util@29.7.0: - resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} - jest-mock@29.7.0: - resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} - jest-pnp-resolver@1.2.3: - resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} - engines: {node: '>=6'} - peerDependencies: - jest-resolve: '*' - peerDependenciesMeta: - jest-resolve: - optional: true + help-me@3.0.0: + resolution: {integrity: sha512-hx73jClhyk910sidBB7ERlnhMlFsJJIBqSVMFDwPN8o2v9nmp5KgLq1Xz1Bf1fCMMZ6mPrX159iG0VLy/fPMtQ==} - jest-regex-util@29.6.3: - resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hls.js@1.5.11: + resolution: {integrity: sha512-q3We1izi2+qkOO+TvZdHv+dx6aFzdtk3xc1/Qesrvto4thLTT/x/1FK85c5h1qZE4MmMBNgKg+MIW8nxQfxwBw==} - jest-resolve-dependencies@29.7.0: - resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} - jest-resolve@29.7.0: - resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hosted-git-info@2.8.9: + resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} - jest-runner@29.7.0: - resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hosted-git-info@4.1.0: + resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} + engines: {node: '>=10'} - jest-runtime@29.7.0: - resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} - jest-snapshot@29.7.0: - resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + html-tags@3.3.1: + resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==} + engines: {node: '>=8'} - jest-util@29.7.0: - resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} - jest-validate@29.7.0: - resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + https-proxy-agent@4.0.0: + resolution: {integrity: sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==} + engines: {node: '>= 6.0.0'} - jest-watcher@29.7.0: - resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} - jest-worker@27.5.1: - resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} - engines: {node: '>= 10.13.0'} + human-signals@4.3.1: + resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==} + engines: {node: '>=14.18.0'} - jest-worker@29.7.0: - resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} - jest@29.7.0: - resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + + husky@8.0.3: + resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==} + engines: {node: '>=14'} hasBin: true - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - joycon@3.1.1: - resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} - engines: {node: '>=10'} + hyphenate-style-name@1.1.0: + resolution: {integrity: sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==} - js-base64@3.7.7: - resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==} + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} - js-cookie@2.2.1: - resolution: {integrity: sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==} + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} - js-sdsl@4.3.0: - resolution: {integrity: sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==} + icss-utils@5.1.0: + resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - js-yaml@3.14.1: - resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} - hasBin: true + ignore@5.3.1: + resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} + engines: {node: '>= 4'} - js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + image-size@0.5.5: + resolution: {integrity: sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==} + engines: {node: '>=0.10.0'} hasBin: true - jscodeshift@0.15.2: - resolution: {integrity: sha512-FquR7Okgmc4Sd0aEDwqho3rEiKR3BdvuG9jfdHjLJ6JQoWSMpavug3AoIfnfWhxFlf+5pzQh8qjqz0DWFrNQzA==} - hasBin: true - peerDependencies: - '@babel/preset-env': ^7.1.6 - peerDependenciesMeta: - '@babel/preset-env': - optional: true + immutable@4.3.6: + resolution: {integrity: sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==} - jsesc@0.5.0: - resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} - hasBin: true + import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} - jsesc@2.5.2: - resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} - engines: {node: '>=4'} + import-local@3.1.0: + resolution: {integrity: sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==} + engines: {node: '>=8'} hasBin: true - json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} - json-parse-better-errors@1.0.2: - resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} - json-parse-even-better-errors@2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + indexof@0.0.1: + resolution: {integrity: sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg==} - json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - json-schema-traverse@1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - json-stringify-safe@5.0.1: - resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + inline-style-prefixer@7.0.0: + resolution: {integrity: sha512-I7GEdScunP1dQ6IM2mQWh6v0mOYdYmH3Bp31UecKdrcUgcURTcctSe1IECdUznSHKSmsHtjrT3CwCPI1pyxfUQ==} - json5@1.0.2: - resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} - hasBin: true + internal-slot@1.0.7: + resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} + engines: {node: '>= 0.4'} - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true + intl-messageformat@10.5.14: + resolution: {integrity: sha512-IjC6sI0X7YRjjyVH9aUgdftcmZK7WXdHeil4KwbjDnRWjnVitKpAx3rr6t6di1joFp5188VqKcobOPA6mCLG/w==} - jsonfile@6.1.0: - resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + invariant@2.2.4: + resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} - jsonparse@1.3.1: - resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} - engines: {'0': node >= 0.2.0} + ip@2.0.1: + resolution: {integrity: sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==} - keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} - kind-of@6.0.3: - resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} - engines: {node: '>=0.10.0'} + is-absolute-url@3.0.3: + resolution: {integrity: sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==} + engines: {node: '>=8'} - kleur@3.0.3: - resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} - engines: {node: '>=6'} + is-arguments@1.1.1: + resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} + engines: {node: '>= 0.4'} - known-css-properties@0.31.0: - resolution: {integrity: sha512-sBPIUGTNF0czz0mwGGUoKKJC8Q7On1GPbCSFPfyEsfHb2DyBG0Y4QtV+EVWpINSaiGKZblDNuF5AezxSgOhesQ==} + is-array-buffer@3.0.4: + resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} + engines: {node: '>= 0.4'} - lazy-universal-dotenv@4.0.0: - resolution: {integrity: sha512-aXpZJRnTkpK6gQ/z4nk+ZBLd/Qdp118cvPruLSIQzQNRhKwEcdXCOzXuF55VDqIiuAaY3UGZ10DJtvZzDcvsxg==} - engines: {node: '>=14.0.0'} + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - ldjson-stream@1.2.1: - resolution: {integrity: sha512-xw/nNEXafuPSLu8NjjG3+atVVw+8U1APZAQylmwQn19Hgw6rC7QjHvP6MupnHWCrzSm9m0xs5QWkCLuRvBPjgQ==} + is-bigint@1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} - less@4.2.0: - resolution: {integrity: sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==} - engines: {node: '>=6'} - hasBin: true + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} - leven@2.1.0: - resolution: {integrity: sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==} - engines: {node: '>=0.10.0'} + is-boolean-object@1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} - leven@3.1.0: - resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} - engines: {node: '>=6'} + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} - levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} + is-core-module@2.13.1: + resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} - lilconfig@2.1.0: - resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} - engines: {node: '>=10'} + is-data-view@1.0.1: + resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==} + engines: {node: '>= 0.4'} - lilconfig@3.1.2: - resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==} - engines: {node: '>=14'} + is-date-object@1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} - line-height@0.3.1: - resolution: {integrity: sha512-YExecgqPwnp5gplD2+Y8e8A5+jKpr25+DzMbFdI1/1UAr0FJrTFv4VkHLf8/6B590i1wUPJWMKKldkd/bdQ//w==} - engines: {node: '>= 4.0.0'} + is-deflate@1.0.0: + resolution: {integrity: sha512-YDoFpuZWu1VRXlsnlYMzKyVRITXj7Ej/V9gXQ2/pAe7X1J7M/RNOqaIYi6qUn+B7nGyB9pDXrv02dsB58d2ZAQ==} - lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true - linkify-react@4.1.3: - resolution: {integrity: sha512-rhI3zM/fxn5BfRPHfi4r9N7zgac4vOIxub1wHIWXLA5ENTMs+BGaIaFO1D1PhmxgwhIKmJz3H7uCP0Dg5JwSlA==} - peerDependencies: - linkifyjs: ^4.0.0 - react: '>= 15.0.0' + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} - linkifyjs@4.1.3: - resolution: {integrity: sha512-auMesunaJ8yfkHvK4gfg1K0SaKX/6Wn9g2Aac/NwX+l5VdmFZzo/hdPGxEOETj+ryRa4/fiOPjeeKURSAJx1sg==} + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} - lint-staged@13.3.0: - resolution: {integrity: sha512-mPRtrYnipYYv1FEE134ufbWpeggNTo+O/UPzngoaKzbzHAthvR55am+8GfHTnqNRQVRRrYQLGW9ZyUoD7DsBHQ==} - engines: {node: ^16.14.0 || >=18.0.0} - hasBin: true + is-fullwidth-code-point@4.0.0: + resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} + engines: {node: '>=12'} - listr2@6.6.1: - resolution: {integrity: sha512-+rAXGHh0fkEWdXBmX+L6mmfmXmXvDGEKzkjxO+8mP3+nI/r/CWznVBvsibXdxda9Zz0OW2e2ikphN3OwCT/jSg==} - engines: {node: '>=16.0.0'} - peerDependencies: - enquirer: '>= 2.3.0 < 3' - peerDependenciesMeta: - enquirer: - optional: true + is-generator-fn@2.1.0: + resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + engines: {node: '>=6'} - load-json-file@4.0.0: - resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} - engines: {node: '>=4'} + is-generator-function@1.0.10: + resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} + engines: {node: '>= 0.4'} - load-tsconfig@0.2.5: - resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} - loader-runner@4.3.0: - resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} - engines: {node: '>=6.11.5'} + is-gzip@1.0.0: + resolution: {integrity: sha512-rcfALRIb1YewtnksfRIHGcIY93QnK8BIQ/2c9yDYcG/Y6+vRoJuTWBmmSEbyLLYtXm7q35pHOHbZFQBaLrhlWQ==} + engines: {node: '>=0.10.0'} - loader-utils@2.0.4: - resolution: {integrity: sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==} - engines: {node: '>=8.9.0'} + is-interactive@1.0.0: + resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} + engines: {node: '>=8'} - locate-path@2.0.0: - resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} - engines: {node: '>=4'} + is-nan@1.3.2: + resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==} + engines: {node: '>= 0.4'} - locate-path@3.0.0: - resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} - engines: {node: '>=6'} + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} - locate-path@5.0.0: - resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} - engines: {node: '>=8'} + is-number-object@1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} - locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} - lodash.camelcase@4.3.0: - resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + is-obj@2.0.0: + resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} + engines: {node: '>=8'} - lodash.debounce@4.0.8: - resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + is-path-cwd@2.2.0: + resolution: {integrity: sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==} + engines: {node: '>=6'} - lodash.ismatch@4.4.0: - resolution: {integrity: sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==} + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} - lodash.memoize@4.1.2: - resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + is-plain-obj@1.1.0: + resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} + engines: {node: '>=0.10.0'} - lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + is-plain-object@2.0.4: + resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} + engines: {node: '>=0.10.0'} - lodash.sortby@4.7.0: - resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + is-plain-object@5.0.0: + resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} + engines: {node: '>=0.10.0'} - lodash.truncate@4.4.2: - resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} + is-regex@1.1.4: + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + engines: {node: '>= 0.4'} - lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + is-shared-array-buffer@1.0.3: + resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} + engines: {node: '>= 0.4'} - log-symbols@4.1.0: - resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} - engines: {node: '>=10'} + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} - log-update@5.0.1: - resolution: {integrity: sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw==} + is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - hasBin: true + is-string@1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} - lru-cache@10.2.2: - resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} - engines: {node: 14 || >=16.14} + is-symbol@1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} - lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + is-text-path@1.0.1: + resolution: {integrity: sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==} + engines: {node: '>=0.10.0'} - lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + is-typed-array@1.1.13: + resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} + engines: {node: '>= 0.4'} + + is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} - magic-string@0.25.9: - resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} + is-weakref@1.0.2: + resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} - magic-string@0.27.0: - resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==} - engines: {node: '>=12'} + is-what@3.14.1: + resolution: {integrity: sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==} - magic-string@0.30.10: - resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} - make-dir@2.1.0: - resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} - engines: {node: '>=6'} + isarray@0.0.1: + resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==} - make-dir@3.1.0: - resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} - engines: {node: '>=8'} + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - make-dir@4.0.0: - resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} - engines: {node: '>=10'} + isarray@2.0.1: + resolution: {integrity: sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ==} - make-error@1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - makeerror@1.0.12: - resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - map-obj@1.0.1: - resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} + isobject@3.0.1: + resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} engines: {node: '>=0.10.0'} - map-obj@4.3.0: - resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} engines: {node: '>=8'} - map-or-similar@1.5.0: - resolution: {integrity: sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg==} - - markdown-to-jsx@7.4.7: - resolution: {integrity: sha512-0+ls1IQZdU6cwM1yu0ZjjiVWYtkbExSyUIFU2ZeDIFuZM1W42Mh4OlJ4nb4apX4H8smxDHRdFaoIVJGwfv5hkg==} - engines: {node: '>= 10'} - peerDependencies: - react: '>= 0.14.0' + istanbul-lib-instrument@5.2.1: + resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} + engines: {node: '>=8'} - mathml-tag-names@2.1.3: - resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==} + istanbul-lib-instrument@6.0.2: + resolution: {integrity: sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw==} + engines: {node: '>=10'} - mdast-util-definitions@4.0.0: - resolution: {integrity: sha512-k8AJ6aNnUkB7IE+5azR9h81O5EQ/cTDXtWdMq9Kk5KcEW/8ritU5CeLg/9HhOC++nALHBlaogJ5jz0Ybk3kPMQ==} + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} - mdast-util-to-string@1.1.0: - resolution: {integrity: sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A==} + istanbul-lib-source-maps@4.0.1: + resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} + engines: {node: '>=10'} - mdn-data@2.0.14: - resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} + istanbul-reports@3.1.7: + resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + engines: {node: '>=8'} - mdn-data@2.0.30: - resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + jackspeak@3.4.0: + resolution: {integrity: sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==} + engines: {node: '>=14'} - media-typer@0.3.0: - resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} - engines: {node: '>= 0.6'} + jake@10.9.1: + resolution: {integrity: sha512-61btcOHNnLnsOdtLgA5efqQWjnSi/vow5HbI7HMdKKWqvrKR1bLK3BPlJn9gcSaP2ewuamUSMB5XEy76KUIS2w==} + engines: {node: '>=10'} + hasBin: true - memoize-one@5.2.1: - resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} + jest-changed-files@29.7.0: + resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - memoizerific@1.11.3: - resolution: {integrity: sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==} + jest-circus@29.7.0: + resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - meow@13.2.0: - resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} - engines: {node: '>=18'} - - meow@8.1.2: - resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==} - engines: {node: '>=10'} + jest-cli@29.7.0: + resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true - merge-descriptors@1.0.1: - resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} + jest-config@29.7.0: + resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + jest-diff@29.7.0: + resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} + jest-docblock@29.7.0: + resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - methods@1.1.2: - resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} - engines: {node: '>= 0.6'} + jest-each@29.7.0: + resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - micromatch@4.0.5: - resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} - engines: {node: '>=8.6'} + jest-environment-node@29.7.0: + resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - micromatch@4.0.7: - resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} - engines: {node: '>=8.6'} + jest-get-type@29.6.3: + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - millify@6.1.0: - resolution: {integrity: sha512-H/E3J6t+DQs/F2YgfDhxUVZz/dF8JXPPKTLHL/yHCcLZLtCXJDUaqvhJXQwqOVBvbyNn4T0WjLpIHd7PAw7fBA==} - hasBin: true + jest-haste-map@29.7.0: + resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} + jest-leak-detector@29.7.0: + resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} + jest-matcher-utils@29.7.0: + resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - mime@1.6.0: - resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} - engines: {node: '>=4'} - hasBin: true + jest-message-util@29.7.0: + resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - mime@2.6.0: - resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} - engines: {node: '>=4.0.0'} - hasBin: true + jest-mock@29.7.0: + resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + jest-pnp-resolver@1.2.3: + resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true - mimic-fn@4.0.0: - resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} - engines: {node: '>=12'} + jest-regex-util@29.6.3: + resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - min-indent@1.0.1: - resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} - engines: {node: '>=4'} + jest-resolve-dependencies@29.7.0: + resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + jest-resolve@29.7.0: + resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - minimatch@5.1.6: - resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} - engines: {node: '>=10'} + jest-runner@29.7.0: + resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - minimatch@9.0.3: - resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} - engines: {node: '>=16 || 14 >=14.17'} + jest-runtime@29.7.0: + resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - minimatch@9.0.4: - resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==} - engines: {node: '>=16 || 14 >=14.17'} + jest-snapshot@29.7.0: + resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - minimist-options@4.1.0: - resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} - engines: {node: '>= 6'} + jest-util@29.7.0: + resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + jest-validate@29.7.0: + resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - minipass@3.3.6: - resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} - engines: {node: '>=8'} + jest-watcher@29.7.0: + resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - minipass@5.0.0: - resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} - engines: {node: '>=8'} + jest-worker@27.5.1: + resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} + engines: {node: '>= 10.13.0'} - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} - engines: {node: '>=16 || 14 >=14.17'} + jest-worker@29.7.0: + resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - minizlib@2.1.2: - resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} - engines: {node: '>= 8'} + jest@29.7.0: + resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true - mitt@3.0.1: - resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} - mkdirp-classic@0.5.3: - resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + js-base64@3.7.7: + resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==} - mkdirp@0.5.6: - resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} - hasBin: true + js-cookie@2.2.1: + resolution: {integrity: sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==} - mkdirp@1.0.4: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} - engines: {node: '>=10'} + js-sdsl@4.3.0: + resolution: {integrity: sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} hasBin: true - modern-normalize@2.0.0: - resolution: {integrity: sha512-CxBoEVKh5U4DH3XuNbc5ONLF6dQBc8dSc7pdZ1957FGbIO5JBqGqqchhET9dTexri8/pk9xBL6+5ceOtCIp1QA==} - engines: {node: '>=6'} + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true - modify-values@1.0.1: - resolution: {integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==} - engines: {node: '>=0.10.0'} + jscodeshift@0.15.2: + resolution: {integrity: sha512-FquR7Okgmc4Sd0aEDwqho3rEiKR3BdvuG9jfdHjLJ6JQoWSMpavug3AoIfnfWhxFlf+5pzQh8qjqz0DWFrNQzA==} + hasBin: true + peerDependencies: + '@babel/preset-env': ^7.1.6 + peerDependenciesMeta: + '@babel/preset-env': + optional: true - mqtt-packet@6.10.0: - resolution: {integrity: sha512-ja8+mFKIHdB1Tpl6vac+sktqy3gA8t9Mduom1BA75cI+R9AHnZOiaBQwpGiWnaVJLDGRdNhQmFaAqd7tkKSMGA==} + jsesc@0.5.0: + resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} + hasBin: true - mqtt@4.3.8: - resolution: {integrity: sha512-2xT75uYa0kiPEF/PE0VPdavmEkoBzMT/UL9moid0rAvlCtV48qBwxD62m7Ld/4j8tSkIO1E/iqRl/S72SEOhOw==} - engines: {node: '>=10.0.0'} + jsesc@2.5.2: + resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} + engines: {node: '>=4'} hasBin: true - ms@2.0.0: - resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + json-parse-better-errors@1.0.2: + resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - multimatch@5.0.0: - resolution: {integrity: sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==} - engines: {node: '>=10'} + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - mz@2.7.0: - resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - nano-css@5.6.1: - resolution: {integrity: sha512-T2Mhc//CepkTa3X4pUhKgbEheJHYAxD0VptuqFhDbGMUWVV2m+lkNiW/Ieuj35wrfC8Zm0l7HvssQh7zcEttSw==} - peerDependencies: - react: '*' - react-dom: '*' + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - nanoid@3.3.7: - resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true + json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} - natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true - needle@3.3.1: - resolution: {integrity: sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==} - engines: {node: '>= 4.4.x'} + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} hasBin: true - negotiator@0.6.3: - resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} - engines: {node: '>= 0.6'} + jsonfile@6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} - neo-async@2.6.2: - resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + jsonparse@1.3.1: + resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} + engines: {'0': node >= 0.2.0} - node-dir@0.1.17: - resolution: {integrity: sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==} - engines: {node: '>= 0.10.5'} + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - node-fetch-native@1.6.4: - resolution: {integrity: sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==} + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} - node-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} - node-int64@0.4.0: - resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + known-css-properties@0.31.0: + resolution: {integrity: sha512-sBPIUGTNF0czz0mwGGUoKKJC8Q7On1GPbCSFPfyEsfHb2DyBG0Y4QtV+EVWpINSaiGKZblDNuF5AezxSgOhesQ==} - node-releases@2.0.14: - resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} + lazy-universal-dotenv@4.0.0: + resolution: {integrity: sha512-aXpZJRnTkpK6gQ/z4nk+ZBLd/Qdp118cvPruLSIQzQNRhKwEcdXCOzXuF55VDqIiuAaY3UGZ10DJtvZzDcvsxg==} + engines: {node: '>=14.0.0'} - normalize-package-data@2.5.0: - resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} + ldjson-stream@1.2.1: + resolution: {integrity: sha512-xw/nNEXafuPSLu8NjjG3+atVVw+8U1APZAQylmwQn19Hgw6rC7QjHvP6MupnHWCrzSm9m0xs5QWkCLuRvBPjgQ==} - normalize-package-data@3.0.3: - resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} - engines: {node: '>=10'} + less@4.2.0: + resolution: {integrity: sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==} + engines: {node: '>=6'} + hasBin: true - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + leven@2.1.0: + resolution: {integrity: sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==} engines: {node: '>=0.10.0'} - normalize-range@0.1.2: - resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} - engines: {node: '>=0.10.0'} + leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} - npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} - npm-run-path@5.3.0: - resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} - number-allocator@1.0.14: - resolution: {integrity: sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==} + lilconfig@3.1.2: + resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==} + engines: {node: '>=14'} - nypm@0.3.8: - resolution: {integrity: sha512-IGWlC6So2xv6V4cIDmoV0SwwWx7zLG086gyqkyumteH2fIgCAM4nDVFB2iDRszDvmdSVW9xb1N+2KjQ6C7d4og==} - engines: {node: ^14.16.0 || >=16.10.0} - hasBin: true + line-height@0.3.1: + resolution: {integrity: sha512-YExecgqPwnp5gplD2+Y8e8A5+jKpr25+DzMbFdI1/1UAr0FJrTFv4VkHLf8/6B590i1wUPJWMKKldkd/bdQ//w==} + engines: {node: '>= 4.0.0'} - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - object-component@0.0.3: - resolution: {integrity: sha512-S0sN3agnVh2SZNEIGc0N1X4Z5K0JeFbGBrnuZpsxuUh5XLF0BnvWkMjRXo/zGKLd/eghvNIKcx1pQkmUjXIyrA==} + linkify-react@4.1.3: + resolution: {integrity: sha512-rhI3zM/fxn5BfRPHfi4r9N7zgac4vOIxub1wHIWXLA5ENTMs+BGaIaFO1D1PhmxgwhIKmJz3H7uCP0Dg5JwSlA==} + peerDependencies: + linkifyjs: ^4.0.0 + react: '>= 15.0.0' - object-hash@3.0.0: - resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} - engines: {node: '>= 6'} + linkifyjs@4.1.3: + resolution: {integrity: sha512-auMesunaJ8yfkHvK4gfg1K0SaKX/6Wn9g2Aac/NwX+l5VdmFZzo/hdPGxEOETj+ryRa4/fiOPjeeKURSAJx1sg==} - object-inspect@1.13.1: - resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} + lint-staged@13.3.0: + resolution: {integrity: sha512-mPRtrYnipYYv1FEE134ufbWpeggNTo+O/UPzngoaKzbzHAthvR55am+8GfHTnqNRQVRRrYQLGW9ZyUoD7DsBHQ==} + engines: {node: ^16.14.0 || >=18.0.0} + hasBin: true - object-is@1.1.6: - resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} - engines: {node: '>= 0.4'} + listr2@6.6.1: + resolution: {integrity: sha512-+rAXGHh0fkEWdXBmX+L6mmfmXmXvDGEKzkjxO+8mP3+nI/r/CWznVBvsibXdxda9Zz0OW2e2ikphN3OwCT/jSg==} + engines: {node: '>=16.0.0'} + peerDependencies: + enquirer: '>= 2.3.0 < 3' + peerDependenciesMeta: + enquirer: + optional: true - object-keys@1.1.1: - resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} - engines: {node: '>= 0.4'} + load-json-file@4.0.0: + resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} + engines: {node: '>=4'} - object.assign@4.1.5: - resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} - engines: {node: '>= 0.4'} + load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - object.fromentries@2.0.8: - resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} - engines: {node: '>= 0.4'} + loader-runner@4.3.0: + resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} + engines: {node: '>=6.11.5'} - object.groupby@1.0.3: - resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} - engines: {node: '>= 0.4'} + loader-utils@2.0.4: + resolution: {integrity: sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==} + engines: {node: '>=8.9.0'} - object.values@1.2.0: - resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} - engines: {node: '>= 0.4'} + locate-path@2.0.0: + resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} + engines: {node: '>=4'} - ohash@1.1.3: - resolution: {integrity: sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw==} + locate-path@3.0.0: + resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} + engines: {node: '>=6'} - on-finished@2.4.1: - resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} - engines: {node: '>= 0.8'} + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} - on-headers@1.0.2: - resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==} - engines: {node: '>= 0.8'} + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} + lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} - onetime@6.0.0: - resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} - engines: {node: '>=12'} + lodash.ismatch@4.4.0: + resolution: {integrity: sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==} - open@8.4.2: - resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} - engines: {node: '>=12'} + lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} - optionator@0.9.4: - resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} - engines: {node: '>= 0.8.0'} + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - ora@5.4.1: - resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} - engines: {node: '>=10'} + lodash.sortby@4.7.0: + resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} - p-limit@1.3.0: - resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} - engines: {node: '>=4'} + lodash.truncate@4.4.2: + resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} - p-limit@2.3.0: - resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} - engines: {node: '>=6'} + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} - p-locate@2.0.0: - resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==} - engines: {node: '>=4'} + log-update@5.0.1: + resolution: {integrity: sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - p-locate@3.0.0: - resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} - engines: {node: '>=6'} + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true - p-locate@4.1.0: - resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} - engines: {node: '>=8'} + lru-cache@10.2.2: + resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} + engines: {node: 14 || >=16.14} - p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - p-map@4.0.0: - resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} - p-try@1.0.0: - resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} - engines: {node: '>=4'} + magic-string@0.25.9: + resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} - p-try@2.2.0: - resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} - engines: {node: '>=6'} + magic-string@0.27.0: + resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==} + engines: {node: '>=12'} - pako@0.2.9: - resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} + magic-string@0.30.10: + resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} - parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + make-dir@2.1.0: + resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} engines: {node: '>=6'} - parse-json@4.0.0: - resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} - engines: {node: '>=4'} - - parse-json@5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + make-dir@3.1.0: + resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} engines: {node: '>=8'} - parse-node-version@1.0.1: - resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==} - engines: {node: '>= 0.10'} - - parseqs@0.0.5: - resolution: {integrity: sha512-B3Nrjw2aL7aI4TDujOzfA4NsEc4u1lVcIRE0xesutH8kjeWF70uk+W5cBlIQx04zUH9NTBvuN36Y9xLRPK6Jjw==} + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} - parseuri@0.0.5: - resolution: {integrity: sha512-ijhdxJu6l5Ru12jF0JvzXVPvsC+VibqeaExlNoMhWN6VQ79PGjkmc7oA4W1lp00sFkNyj0fx6ivPLdV51/UMog==} + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - parseurl@1.3.3: - resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} - engines: {node: '>= 0.8'} + makeerror@1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} - path-exists@3.0.0: - resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} - engines: {node: '>=4'} + map-obj@1.0.1: + resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} + engines: {node: '>=0.10.0'} - path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + map-obj@4.3.0: + resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} engines: {node: '>=8'} - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} + map-or-similar@1.5.0: + resolution: {integrity: sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg==} - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} + markdown-to-jsx@7.4.7: + resolution: {integrity: sha512-0+ls1IQZdU6cwM1yu0ZjjiVWYtkbExSyUIFU2ZeDIFuZM1W42Mh4OlJ4nb4apX4H8smxDHRdFaoIVJGwfv5hkg==} + engines: {node: '>= 10'} + peerDependencies: + react: '>= 0.14.0' - path-key@4.0.0: - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} - engines: {node: '>=12'} + mathml-tag-names@2.1.3: + resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==} - path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + mdast-util-definitions@4.0.0: + resolution: {integrity: sha512-k8AJ6aNnUkB7IE+5azR9h81O5EQ/cTDXtWdMq9Kk5KcEW/8ritU5CeLg/9HhOC++nALHBlaogJ5jz0Ybk3kPMQ==} - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} + mdast-util-to-string@1.1.0: + resolution: {integrity: sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A==} - path-to-regexp@0.1.7: - resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} + mdn-data@2.0.14: + resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} - path-type@3.0.0: - resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} - engines: {node: '>=4'} + mdn-data@2.0.30: + resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} - path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} - pathe@1.1.2: - resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + memoize-one@5.2.1: + resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} - peek-stream@1.1.3: - resolution: {integrity: sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==} + memoizerific@1.11.3: + resolution: {integrity: sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==} - pend@1.2.0: - resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + meow@13.2.0: + resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} + engines: {node: '>=18'} - picocolors@1.0.1: - resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + meow@8.1.2: + resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==} + engines: {node: '>=10'} - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + merge-descriptors@1.0.1: + resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + + micromatch@4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} engines: {node: '>=8.6'} - pidtree@0.6.0: - resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} - engines: {node: '>=0.10'} + micromatch@4.0.7: + resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} + engines: {node: '>=8.6'} + + millify@6.1.0: + resolution: {integrity: sha512-H/E3J6t+DQs/F2YgfDhxUVZz/dF8JXPPKTLHL/yHCcLZLtCXJDUaqvhJXQwqOVBvbyNn4T0WjLpIHd7PAw7fBA==} hasBin: true - pify@2.3.0: - resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} - engines: {node: '>=0.10.0'} + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} - pify@3.0.0: - resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} engines: {node: '>=4'} + hasBin: true - pify@4.0.1: - resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + mime@2.6.0: + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} + hasBin: true + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} - pirates@4.0.6: - resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} - engines: {node: '>= 6'} + mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} - pkg-dir@3.0.0: - resolution: {integrity: sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==} - engines: {node: '>=6'} + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} - pkg-dir@4.2.0: - resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} - engines: {node: '>=8'} + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - pkg-dir@5.0.0: - resolution: {integrity: sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==} + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} engines: {node: '>=10'} - polished@4.3.1: - resolution: {integrity: sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==} - engines: {node: '>=10'} + minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + engines: {node: '>=16 || 14 >=14.17'} - possible-typed-array-names@1.0.0: - resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} - engines: {node: '>= 0.4'} + minimatch@9.0.4: + resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==} + engines: {node: '>=16 || 14 >=14.17'} - postcss-load-config@3.1.4: - resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} - engines: {node: '>= 10'} - peerDependencies: - postcss: '>=8.0.9' - ts-node: '>=9.0.0' - peerDependenciesMeta: - postcss: - optional: true - ts-node: - optional: true + minimist-options@4.1.0: + resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} + engines: {node: '>= 6'} - postcss-load-config@4.0.2: - resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} - engines: {node: '>= 14'} - peerDependencies: - postcss: '>=8.0.9' - ts-node: '>=9.0.0' - peerDependenciesMeta: - postcss: - optional: true - ts-node: - optional: true + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - postcss-modules-extract-imports@3.1.0: - resolution: {integrity: sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} - postcss-modules-local-by-default@4.0.5: - resolution: {integrity: sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 + minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} - postcss-modules-scope@3.2.0: - resolution: {integrity: sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} - postcss-resolve-nested-selector@0.1.1: - resolution: {integrity: sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==} + minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} - postcss-safe-parser@7.0.0: - resolution: {integrity: sha512-ovehqRNVCpuFzbXoTb4qLtyzK3xn3t/CUBxOs8LsnQjQrShaB4lKiHoVqY8ANaC0hBMHq5QVWk77rwGklFUDrg==} - engines: {node: '>=18.0'} - peerDependencies: - postcss: ^8.4.31 - - postcss-selector-parser@6.1.0: - resolution: {integrity: sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==} - engines: {node: '>=4'} - - postcss-value-parser@4.2.0: - resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - - postcss@8.4.38: - resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} - engines: {node: ^10 || ^12 || >=14} + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} - prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} - prettier@2.4.0: - resolution: {integrity: sha512-DsEPLY1dE5HF3BxCRBmD4uYZ+5DCbvatnolqTqcxEgKVZnL2kUfyu7b8pPQ5+hTBkdhU9SLUmK0/pHb07RE4WQ==} - engines: {node: '>=10.13.0'} + mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true - prettier@2.8.8: - resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} - engines: {node: '>=10.13.0'} + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} hasBin: true - pretty-format@29.7.0: - resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + modern-normalize@2.0.0: + resolution: {integrity: sha512-CxBoEVKh5U4DH3XuNbc5ONLF6dQBc8dSc7pdZ1957FGbIO5JBqGqqchhET9dTexri8/pk9xBL6+5ceOtCIp1QA==} + engines: {node: '>=6'} - pretty-hrtime@1.0.3: - resolution: {integrity: sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==} - engines: {node: '>= 0.8'} + modify-values@1.0.1: + resolution: {integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==} + engines: {node: '>=0.10.0'} - process-nextick-args@2.0.1: - resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + mqtt-packet@6.10.0: + resolution: {integrity: sha512-ja8+mFKIHdB1Tpl6vac+sktqy3gA8t9Mduom1BA75cI+R9AHnZOiaBQwpGiWnaVJLDGRdNhQmFaAqd7tkKSMGA==} - process@0.11.10: - resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} - engines: {node: '>= 0.6.0'} + mqtt@4.3.8: + resolution: {integrity: sha512-2xT75uYa0kiPEF/PE0VPdavmEkoBzMT/UL9moid0rAvlCtV48qBwxD62m7Ld/4j8tSkIO1E/iqRl/S72SEOhOw==} + engines: {node: '>=10.0.0'} + hasBin: true - progress@2.0.3: - resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} - engines: {node: '>=0.4.0'} + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} - prompts@2.4.2: - resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} - engines: {node: '>= 6'} + ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - prop-types@15.8.1: - resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - proxy-addr@2.0.7: - resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} - engines: {node: '>= 0.10'} + multimatch@5.0.0: + resolution: {integrity: sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==} + engines: {node: '>=10'} - proxy-from-env@1.1.0: - resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - prr@1.0.1: - resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} + nano-css@5.6.1: + resolution: {integrity: sha512-T2Mhc//CepkTa3X4pUhKgbEheJHYAxD0VptuqFhDbGMUWVV2m+lkNiW/Ieuj35wrfC8Zm0l7HvssQh7zcEttSw==} + peerDependencies: + react: '*' + react-dom: '*' - pump@2.0.1: - resolution: {integrity: sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==} + nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true - pump@3.0.0: - resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - pumpify@1.5.1: - resolution: {integrity: sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==} + needle@3.3.1: + resolution: {integrity: sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==} + engines: {node: '>= 4.4.x'} + hasBin: true - punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} - puppeteer-core@2.1.1: - resolution: {integrity: sha512-n13AWriBMPYxnpbb6bnaY5YoY6rGj8vPLrz6CZF3o0qJNEwlcfJVxBzYZ0NJsQ21UbdJoijPCDrM++SUVEz7+w==} - engines: {node: '>=8.16.0'} + neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - pure-rand@6.1.0: - resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + node-dir@0.1.17: + resolution: {integrity: sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==} + engines: {node: '>= 0.10.5'} - q@1.5.1: - resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==} - engines: {node: '>=0.6.0', teleport: '>=0.2.0'} - deprecated: |- - You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. + node-fetch-native@1.6.4: + resolution: {integrity: sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==} - (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true - qs@6.11.0: - resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} - engines: {node: '>=0.6'} + node-int64@0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} - qs@6.12.1: - resolution: {integrity: sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==} - engines: {node: '>=0.6'} + node-releases@2.0.14: + resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} - queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + normalize-package-data@2.5.0: + resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} - quick-lru@4.0.1: - resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} - engines: {node: '>=8'} + normalize-package-data@3.0.3: + resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} + engines: {node: '>=10'} - ramda@0.29.0: - resolution: {integrity: sha512-BBea6L67bYLtdbOqfp8f58fPMqEwx0doL+pAi8TZyp2YWz8R9G8z9x75CZI8W+ftqhFHCpEX2cRnUUXK130iKA==} + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} - randombytes@2.1.0: - resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + normalize-range@0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} - range-parser@1.2.1: - resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} - engines: {node: '>= 0.6'} + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} - raw-body@2.5.2: - resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} - engines: {node: '>= 0.8'} + npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - react-colorful@5.6.1: - resolution: {integrity: sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' + number-allocator@1.0.14: + resolution: {integrity: sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==} - react-docgen-typescript@2.2.2: - resolution: {integrity: sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==} - peerDependencies: - typescript: '>= 4.3.x' + nypm@0.3.8: + resolution: {integrity: sha512-IGWlC6So2xv6V4cIDmoV0SwwWx7zLG086gyqkyumteH2fIgCAM4nDVFB2iDRszDvmdSVW9xb1N+2KjQ6C7d4og==} + engines: {node: ^14.16.0 || >=16.10.0} + hasBin: true - react-docgen@7.0.3: - resolution: {integrity: sha512-i8aF1nyKInZnANZ4uZrH49qn1paRgBZ7wZiCNBMnenlPzEv0mRl+ShpTVEI6wZNl8sSc79xZkivtgLKQArcanQ==} - engines: {node: '>=16.14.0'} + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} - react-dom@18.3.1: - resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} - peerDependencies: - react: ^18.3.1 + object-component@0.0.3: + resolution: {integrity: sha512-S0sN3agnVh2SZNEIGc0N1X4Z5K0JeFbGBrnuZpsxuUh5XLF0BnvWkMjRXo/zGKLd/eghvNIKcx1pQkmUjXIyrA==} - react-element-to-jsx-string@15.0.0: - resolution: {integrity: sha512-UDg4lXB6BzlobN60P8fHWVPX3Kyw8ORrTeBtClmIlGdkOOE+GYQSFvmEU5iLLpwp/6v42DINwNcwOhOLfQ//FQ==} - peerDependencies: - react: ^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0 - react-dom: ^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0 + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} - react-hook-form@7.52.0: - resolution: {integrity: sha512-mJX506Xc6mirzLsmXUJyqlAI3Kj9Ph2RhplYhUVffeOQSnubK2uVqBFOBJmvKikvbFV91pxVXmDiR+QMF19x6A==} - engines: {node: '>=12.22.0'} - peerDependencies: - react: ^16.8.0 || ^17 || ^18 || ^19 + object-inspect@1.13.1: + resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} - react-infinite-scroll-component@6.1.0: - resolution: {integrity: sha512-SQu5nCqy8DxQWpnUVLx7V7b7LcA37aM7tvoWjTLZp1dk6EJibM5/4EJKzOnl07/BsM1Y40sKLuqjCwwH/xV0TQ==} - peerDependencies: - react: '>=16.0.0' + object-is@1.1.6: + resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} + engines: {node: '>= 0.4'} - react-insta-stories@2.7.0: - resolution: {integrity: sha512-pCPHzC323b9Ttvr8ByLrX/iH/ft/L3ThWGDJPKdvHnaWdR5dhFkonrQ0/KNiEIH5t539dNyFPevLJUOobkD2WQ==} - peerDependencies: - react: '>=16.8.2' + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} - react-intl@6.6.8: - resolution: {integrity: sha512-M0pkhzcgV31h++2901BiRXWl69hp2zPyLxRrSwRjd1ErXbNoubz/f4M6DrRTd4OiSUrT4ajRQzrmtS5plG4FtA==} - peerDependencies: - react: ^16.6.0 || 17 || 18 - typescript: ^4.7 || 5 - peerDependenciesMeta: - typescript: - optional: true + object.assign@4.1.5: + resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} + engines: {node: '>= 0.4'} - react-is@16.13.1: - resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} - react-is@18.1.0: - resolution: {integrity: sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==} + object.groupby@1.0.3: + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} - react-is@18.3.1: - resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + object.values@1.2.0: + resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} + engines: {node: '>= 0.4'} - react-loading-skeleton@3.4.0: - resolution: {integrity: sha512-1oJEBc9+wn7BbkQQk7YodlYEIjgeR+GrRjD+QXkVjwZN7LGIcAFHrx4NhT7UHGBxNY1+zax3c+Fo6XQM4R7CgA==} - peerDependencies: - react: '>=16.8.0' + ohash@1.1.3: + resolution: {integrity: sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw==} - react-mentions@4.4.10: - resolution: {integrity: sha512-JHiQlgF1oSZR7VYPjq32wy97z1w1oE4x10EuhKjPr4WUKhVzG1uFQhQjKqjQkbVqJrmahf+ldgBTv36NrkpKpA==} - peerDependencies: - react: '>=16.8.3' - react-dom: '>=16.8.3' + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} - react-modal-sheet@2.2.1: - resolution: {integrity: sha512-bVdxbKVhTbsgn+5g3/X9RjhFhKkPGLMCfLR2O22FHk62G28kFIFn4yKqhXBbXR8qPcjBcaUxadrn7cR+LVI8Vw==} - engines: {node: '>=16'} - peerDependencies: - framer-motion: '>=6' - react: '>=16' + on-headers@1.0.2: + resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==} + engines: {node: '>= 0.8'} - react-native-uuid@2.0.2: - resolution: {integrity: sha512-5ypj/hV58P+6VREdjkW0EudSibsH3WdqDERoHKnD9syFWjF+NfRWWrJb2sa3LIwI5zpzMvUiabs+DX40WHpEMw==} - engines: {node: '>=10.0.0', npm: '>=6.0.0'} + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - react-refresh@0.14.2: - resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} - engines: {node: '>=0.10.0'} + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} - react-remove-scroll-bar@2.3.6: - resolution: {integrity: sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true + onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} - react-remove-scroll@2.5.5: - resolution: {integrity: sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true + open@8.4.2: + resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} + engines: {node: '>=12'} - react-router-dom@6.23.1: - resolution: {integrity: sha512-utP+K+aSTtEdbWpC+4gxhdlPFwuEfDKq8ZrPFU65bbRJY+l706qjR7yaidBpo3MSeA/fzwbXWbKBI6ftOnP3OQ==} - engines: {node: '>=14.0.0'} - peerDependencies: - react: '>=16.8' - react-dom: '>=16.8' + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} - react-router@6.23.1: - resolution: {integrity: sha512-fzcOaRF69uvqbbM7OhvQyBTFDVrrGlsFdS3AL+1KfIBtGETibHzi3FkoTRyiDJnWNc2VxrfvR+657ROHjaNjqQ==} - engines: {node: '>=14.0.0'} - peerDependencies: - react: '>=16.8' + ora@5.4.1: + resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} + engines: {node: '>=10'} - react-sizeme@3.0.2: - resolution: {integrity: sha512-xOIAOqqSSmKlKFJLO3inBQBdymzDuXx4iuwkNcJmC96jeiOg5ojByvL+g3MW9LPEsojLbC6pf68zOfobK8IPlw==} + p-limit@1.3.0: + resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} + engines: {node: '>=4'} - react-style-singleton@2.2.1: - resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} - react-textarea-autosize@8.5.3: - resolution: {integrity: sha512-XT1024o2pqCuZSuBt9FwHlaDeNtVrtCXu0Rnz88t1jUGheCLa3PhjE1GH8Ctm2axEtvdCl5SUHYschyQ0L5QHQ==} + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - - react-timeago@7.2.0: - resolution: {integrity: sha512-2KsBEEs+qRhKx/kekUVNSTIpop3Jwd7SRBm0R4Eiq3mPeswRGSsftY9FpKsE/lXLdURyQFiHeHFrIUxLYskG5g==} - peerDependencies: - react: ^16.0.0 || ^17.0.0 || ^18.0.0 - react-tiny-popover@7.2.4: - resolution: {integrity: sha512-T7ZSwXcUtPXCog3Bux9+TjoTvUeMi/+zI0Yv/TkIznZCWUg0XTt2797G0IiT5mTVeJeLivUzdOmKA1hOQdMfOQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + p-locate@2.0.0: + resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==} + engines: {node: '>=4'} - react-truncate-markup@5.1.2: - resolution: {integrity: sha512-eEq6T8Rs+wz98cRYzQECGFNBfXwRYraLg/kz52f6DRBKmzxqB+GYLeDkVe/zrC+2vh5AEwM6nSYFvDWEBljd0w==} - peerDependencies: - react: '>=16.3' + p-locate@3.0.0: + resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} + engines: {node: '>=6'} - react-universal-interface@0.6.2: - resolution: {integrity: sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw==} - peerDependencies: - react: '*' - tslib: '*' + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} - react-use@17.5.0: - resolution: {integrity: sha512-PbfwSPMwp/hoL847rLnm/qkjg3sTRCvn6YhUZiHaUa3FA6/aNoFX79ul5Xt70O1rK+9GxSVqkY0eTwMdsR/bWg==} - peerDependencies: - react: '*' - react-dom: '*' + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} - react@18.3.1: - resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} - engines: {node: '>=0.10.0'} + p-map@4.0.0: + resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} + engines: {node: '>=10'} - read-pkg-up@3.0.0: - resolution: {integrity: sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==} + p-try@1.0.0: + resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} engines: {node: '>=4'} - read-pkg-up@7.0.1: - resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} - engines: {node: '>=8'} + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} - read-pkg@3.0.0: - resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==} + pako@0.2.9: + resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@4.0.0: + resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} engines: {node: '>=4'} - read-pkg@5.2.0: - resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} - readable-stream@1.0.34: - resolution: {integrity: sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==} - - readable-stream@1.1.14: - resolution: {integrity: sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==} + parse-node-version@1.0.1: + resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==} + engines: {node: '>= 0.10'} - readable-stream@2.3.8: - resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + parseqs@0.0.5: + resolution: {integrity: sha512-B3Nrjw2aL7aI4TDujOzfA4NsEc4u1lVcIRE0xesutH8kjeWF70uk+W5cBlIQx04zUH9NTBvuN36Y9xLRPK6Jjw==} - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} + parseuri@0.0.5: + resolution: {integrity: sha512-ijhdxJu6l5Ru12jF0JvzXVPvsC+VibqeaExlNoMhWN6VQ79PGjkmc7oA4W1lp00sFkNyj0fx6ivPLdV51/UMog==} - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} - recast@0.23.9: - resolution: {integrity: sha512-Hx/BGIbwj+Des3+xy5uAtAbdCyqK9y9wbBcDFDYanLS9JnMqf7OeF87HQwUimE87OEc72mr6tkKUKMBBL+hF9Q==} - engines: {node: '>= 4'} + path-exists@3.0.0: + resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} + engines: {node: '>=4'} - redent@3.0.0: - resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} - regenerate-unicode-properties@10.1.1: - resolution: {integrity: sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==} - engines: {node: '>=4'} + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} - regenerate@1.4.2: - resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} - regenerator-runtime@0.13.11: - resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} - regenerator-runtime@0.14.1: - resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - regenerator-transform@0.15.2: - resolution: {integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==} + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} - regexp.prototype.flags@1.5.2: - resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} - engines: {node: '>= 0.4'} + path-to-regexp@0.1.7: + resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} - regexpu-core@5.3.2: - resolution: {integrity: sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==} + path-type@3.0.0: + resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} engines: {node: '>=4'} - regjsparser@0.9.1: - resolution: {integrity: sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==} - hasBin: true + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} - reinterval@1.1.0: - resolution: {integrity: sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==} + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} - remark-external-links@8.0.0: - resolution: {integrity: sha512-5vPSX0kHoSsqtdftSHhIYofVINC8qmp0nctkeU9YoJwV3YfiBRiI6cbFRJ0oI/1F9xS+bopXG0m2KS8VFscuKA==} + peek-stream@1.1.3: + resolution: {integrity: sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==} - remark-slug@6.1.0: - resolution: {integrity: sha512-oGCxDF9deA8phWvxFuyr3oSJsdyUAxMFbA0mZ7Y1Sas+emILtO+e5WutF9564gDsEN4IXaQXm5pFo6MLH+YmwQ==} + pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} - require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} + picocolors@1.0.1: + resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} - require-from-string@2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} - - reserved-words@0.1.2: - resolution: {integrity: sha512-0S5SrIUJ9LfpbVl4Yzij6VipUdafHrOTzvmfazSw/jeZrZtQK303OPZW+obtkaw7jQlTQppy0UvZWm9872PbRw==} + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} - resize-observer-polyfill@1.5.1: - resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} + pidtree@0.6.0: + resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} + engines: {node: '>=0.10'} + hasBin: true - resolve-cwd@3.0.0: - resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} - engines: {node: '>=8'} + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} - resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + pify@3.0.0: + resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} engines: {node: '>=4'} - resolve-from@5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} - - resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + pify@4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} - resolve.exports@2.0.2: - resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} - engines: {node: '>=10'} + pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} - resolve@1.22.8: - resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} - hasBin: true + pkg-dir@3.0.0: + resolution: {integrity: sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==} + engines: {node: '>=6'} - restore-cursor@3.1.0: - resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} - restore-cursor@4.0.0: - resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + pkg-dir@5.0.0: + resolution: {integrity: sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==} + engines: {node: '>=10'} - rfdc@1.4.1: - resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + polished@4.3.1: + resolution: {integrity: sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==} + engines: {node: '>=10'} - rimraf@2.6.3: - resolution: {integrity: sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true + possible-typed-array-names@1.0.0: + resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} + engines: {node: '>= 0.4'} - rimraf@2.7.1: - resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true + postcss-load-config@3.1.4: + resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} + engines: {node: '>= 10'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true - rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true + postcss-load-config@4.0.2: + resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} + engines: {node: '>= 14'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true - rollup@3.29.4: - resolution: {integrity: sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==} - engines: {node: '>=14.18.0', npm: '>=8.0.0'} - hasBin: true + postcss-modules-extract-imports@3.1.0: + resolution: {integrity: sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 - rollup@4.18.0: - resolution: {integrity: sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true + postcss-modules-local-by-default@4.0.5: + resolution: {integrity: sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 - rtl-css-js@1.16.1: - resolution: {integrity: sha512-lRQgou1mu19e+Ya0LsTvKrVJ5TYUbqCVPAiImX3UfLTenarvPUl1QFdvu5Z3PYmHT9RCcwIfbjRQBntExyj3Zg==} + postcss-modules-scope@3.2.0: + resolution: {integrity: sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 - run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + postcss-resolve-nested-selector@0.1.1: + resolution: {integrity: sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==} - safe-array-concat@1.1.2: - resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} - engines: {node: '>=0.4'} + postcss-safe-parser@7.0.0: + resolution: {integrity: sha512-ovehqRNVCpuFzbXoTb4qLtyzK3xn3t/CUBxOs8LsnQjQrShaB4lKiHoVqY8ANaC0hBMHq5QVWk77rwGklFUDrg==} + engines: {node: '>=18.0'} + peerDependencies: + postcss: ^8.4.31 - safe-buffer@5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + postcss-selector-parser@6.1.0: + resolution: {integrity: sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==} + engines: {node: '>=4'} - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - safe-regex-test@1.0.3: - resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} - engines: {node: '>= 0.4'} + postcss@8.4.38: + resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} + engines: {node: ^10 || ^12 || >=14} - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} - sass@1.77.6: - resolution: {integrity: sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q==} - engines: {node: '>=14.0.0'} + prettier@2.4.0: + resolution: {integrity: sha512-DsEPLY1dE5HF3BxCRBmD4uYZ+5DCbvatnolqTqcxEgKVZnL2kUfyu7b8pPQ5+hTBkdhU9SLUmK0/pHb07RE4WQ==} + engines: {node: '>=10.13.0'} hasBin: true - sax@1.3.0: - resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==} + prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true - sax@1.4.1: - resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==} + pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - scheduler@0.23.2: - resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + pretty-hrtime@1.0.3: + resolution: {integrity: sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==} + engines: {node: '>= 0.8'} - schema-utils@3.3.0: - resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} - engines: {node: '>= 10.13.0'} + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - screenfull@5.2.0: - resolution: {integrity: sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==} - engines: {node: '>=0.10.0'} + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} - semver@5.7.2: - resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} - hasBin: true + progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} - semver@7.6.2: - resolution: {integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==} - engines: {node: '>=10'} - hasBin: true + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} - send@0.18.0: - resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} - engines: {node: '>= 0.8.0'} + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} - serialize-javascript@6.0.2: - resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - serve-static@1.15.0: - resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} - engines: {node: '>= 0.8.0'} + prr@1.0.1: + resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} - set-function-length@1.2.2: - resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} - engines: {node: '>= 0.4'} + pump@2.0.1: + resolution: {integrity: sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==} - set-function-name@2.0.2: - resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} - engines: {node: '>= 0.4'} + pump@3.0.0: + resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} - set-harmonic-interval@1.0.1: - resolution: {integrity: sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g==} - engines: {node: '>=6.9'} + pumpify@1.5.1: + resolution: {integrity: sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==} - setprototypeof@1.2.0: - resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} - shallow-clone@3.0.1: - resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==} - engines: {node: '>=8'} + puppeteer-core@2.1.1: + resolution: {integrity: sha512-n13AWriBMPYxnpbb6bnaY5YoY6rGj8vPLrz6CZF3o0qJNEwlcfJVxBzYZ0NJsQ21UbdJoijPCDrM++SUVEz7+w==} + engines: {node: '>=8.16.0'} - shallowequal@1.1.0: - resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==} + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} + q@1.5.1: + resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==} + engines: {node: '>=0.6.0', teleport: '>=0.2.0'} + deprecated: |- + You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. - side-channel@1.0.6: - resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} - engines: {node: '>= 0.4'} + (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) - signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + qs@6.11.0: + resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} + engines: {node: '>=0.6'} - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} + qs@6.12.1: + resolution: {integrity: sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==} + engines: {node: '>=0.6'} - sisteransi@1.0.5: - resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + quick-lru@4.0.1: + resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} engines: {node: '>=8'} - slice-ansi@4.0.0: - resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} - engines: {node: '>=10'} - - slice-ansi@5.0.0: - resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} - engines: {node: '>=12'} - - socket.io-client@2.2.0: - resolution: {integrity: sha512-56ZrkTDbdTLmBIyfFYesgOxsjcLnwAKoN4CiPyTVkMQj3zTUh0QAx3GbvIvLpFEOvQWu92yyWICxB0u7wkVbYA==} - - socket.io-parser@3.3.3: - resolution: {integrity: sha512-qOg87q1PMWWTeO01768Yh9ogn7chB9zkKtQnya41Y355S0UmpXgpcrFwAgjYJxu9BdKug5r5e9YtVSeWhKBUZg==} + ramda@0.29.0: + resolution: {integrity: sha512-BBea6L67bYLtdbOqfp8f58fPMqEwx0doL+pAi8TZyp2YWz8R9G8z9x75CZI8W+ftqhFHCpEX2cRnUUXK130iKA==} - source-map-js@1.2.0: - resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} - engines: {node: '>=0.10.0'} + randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} - source-map-support@0.5.13: - resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} - source-map-support@0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + raw-body@2.5.2: + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} + engines: {node: '>= 0.8'} - source-map@0.5.6: - resolution: {integrity: sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==} - engines: {node: '>=0.10.0'} + react-aria-components@1.2.1: + resolution: {integrity: sha512-iGIdDjbTyLLn0/tGUyBQxxu+E1bw4/H4AU89d0cRcu8yIdw6MXG29YElmRHn0ugiyrERrk/YQALihstnns5kRQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} + react-aria@3.33.1: + resolution: {integrity: sha512-hFC3K/UA+90Krlx2IgRTgzFbC6FSPi4pUwHT+STperPLK+cTEHkI+3Lu0YYwQSBatkgxnIv9+GtFuVbps2kROw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - source-map@0.7.4: - resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} - engines: {node: '>= 8'} + react-colorful@5.6.1: + resolution: {integrity: sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' - source-map@0.8.0-beta.0: - resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} - engines: {node: '>= 8'} + react-docgen-typescript@2.2.2: + resolution: {integrity: sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==} + peerDependencies: + typescript: '>= 4.3.x' - sourcemap-codec@1.4.8: - resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} - deprecated: Please use @jridgewell/sourcemap-codec instead + react-docgen@7.0.3: + resolution: {integrity: sha512-i8aF1nyKInZnANZ4uZrH49qn1paRgBZ7wZiCNBMnenlPzEv0mRl+ShpTVEI6wZNl8sSc79xZkivtgLKQArcanQ==} + engines: {node: '>=16.14.0'} - space-separated-tokens@1.1.5: - resolution: {integrity: sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==} + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 - spdx-correct@3.2.0: - resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} + react-element-to-jsx-string@15.0.0: + resolution: {integrity: sha512-UDg4lXB6BzlobN60P8fHWVPX3Kyw8ORrTeBtClmIlGdkOOE+GYQSFvmEU5iLLpwp/6v42DINwNcwOhOLfQ//FQ==} + peerDependencies: + react: ^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0 + react-dom: ^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0 - spdx-exceptions@2.5.0: - resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} + react-hook-form@7.52.0: + resolution: {integrity: sha512-mJX506Xc6mirzLsmXUJyqlAI3Kj9Ph2RhplYhUVffeOQSnubK2uVqBFOBJmvKikvbFV91pxVXmDiR+QMF19x6A==} + engines: {node: '>=12.22.0'} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 || ^19 - spdx-expression-parse@3.0.1: - resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + react-infinite-scroll-component@6.1.0: + resolution: {integrity: sha512-SQu5nCqy8DxQWpnUVLx7V7b7LcA37aM7tvoWjTLZp1dk6EJibM5/4EJKzOnl07/BsM1Y40sKLuqjCwwH/xV0TQ==} + peerDependencies: + react: '>=16.0.0' - spdx-license-ids@3.0.18: - resolution: {integrity: sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==} + react-insta-stories@2.7.0: + resolution: {integrity: sha512-pCPHzC323b9Ttvr8ByLrX/iH/ft/L3ThWGDJPKdvHnaWdR5dhFkonrQ0/KNiEIH5t539dNyFPevLJUOobkD2WQ==} + peerDependencies: + react: '>=16.8.2' - split2@0.2.1: - resolution: {integrity: sha512-D/oTExYAkC9nWleOCTOyNmAuzfAT/6rHGBA9LIK7FVnGo13CSvrKCUzKenwH6U1s2znY9MqH6v0UQTEDa3vJmg==} + react-intl@6.6.8: + resolution: {integrity: sha512-M0pkhzcgV31h++2901BiRXWl69hp2zPyLxRrSwRjd1ErXbNoubz/f4M6DrRTd4OiSUrT4ajRQzrmtS5plG4FtA==} + peerDependencies: + react: ^16.6.0 || 17 || 18 + typescript: ^4.7 || 5 + peerDependenciesMeta: + typescript: + optional: true - split2@3.2.2: - resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - split@1.0.1: - resolution: {integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==} + react-is@18.1.0: + resolution: {integrity: sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==} - sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - stack-generator@2.0.10: - resolution: {integrity: sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==} + react-loading-skeleton@3.4.0: + resolution: {integrity: sha512-1oJEBc9+wn7BbkQQk7YodlYEIjgeR+GrRjD+QXkVjwZN7LGIcAFHrx4NhT7UHGBxNY1+zax3c+Fo6XQM4R7CgA==} + peerDependencies: + react: '>=16.8.0' - stack-utils@2.0.6: - resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} - engines: {node: '>=10'} + react-mentions@4.4.10: + resolution: {integrity: sha512-JHiQlgF1oSZR7VYPjq32wy97z1w1oE4x10EuhKjPr4WUKhVzG1uFQhQjKqjQkbVqJrmahf+ldgBTv36NrkpKpA==} + peerDependencies: + react: '>=16.8.3' + react-dom: '>=16.8.3' - stackframe@1.3.4: - resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} + react-modal-sheet@2.2.1: + resolution: {integrity: sha512-bVdxbKVhTbsgn+5g3/X9RjhFhKkPGLMCfLR2O22FHk62G28kFIFn4yKqhXBbXR8qPcjBcaUxadrn7cR+LVI8Vw==} + engines: {node: '>=16'} + peerDependencies: + framer-motion: '>=6' + react: '>=16' - stacktrace-gps@3.1.2: - resolution: {integrity: sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ==} + react-native-uuid@2.0.2: + resolution: {integrity: sha512-5ypj/hV58P+6VREdjkW0EudSibsH3WdqDERoHKnD9syFWjF+NfRWWrJb2sa3LIwI5zpzMvUiabs+DX40WHpEMw==} + engines: {node: '>=10.0.0', npm: '>=6.0.0'} - stacktrace-js@2.0.2: - resolution: {integrity: sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==} + react-refresh@0.14.2: + resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} + engines: {node: '>=0.10.0'} - standard-version@9.5.0: - resolution: {integrity: sha512-3zWJ/mmZQsOaO+fOlsa0+QK90pwhNd042qEcw6hKFNoLFs7peGyvPffpEBbK/DSGPbyOvli0mUIFv5A4qTjh2Q==} + react-remove-scroll-bar@2.3.6: + resolution: {integrity: sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==} engines: {node: '>=10'} - hasBin: true + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true - statuses@2.0.1: - resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} - engines: {node: '>= 0.8'} + react-remove-scroll@2.5.5: + resolution: {integrity: sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true - store2@2.14.3: - resolution: {integrity: sha512-4QcZ+yx7nzEFiV4BMLnr/pRa5HYzNITX2ri0Zh6sT9EyQHbBHacC6YigllUPU9X3D0f/22QCgfokpKs52YRrUg==} + react-router-dom@6.23.1: + resolution: {integrity: sha512-utP+K+aSTtEdbWpC+4gxhdlPFwuEfDKq8ZrPFU65bbRJY+l706qjR7yaidBpo3MSeA/fzwbXWbKBI6ftOnP3OQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' - storybook@7.6.19: - resolution: {integrity: sha512-xWD1C4vD/4KMffCrBBrUpsLUO/9uNpm8BVW8+Vcb30gkQDfficZ0oziWkmLexpT53VSioa24iazGXMwBqllYjQ==} - hasBin: true + react-router@6.23.1: + resolution: {integrity: sha512-fzcOaRF69uvqbbM7OhvQyBTFDVrrGlsFdS3AL+1KfIBtGETibHzi3FkoTRyiDJnWNc2VxrfvR+657ROHjaNjqQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: '>=16.8' - stream-shift@1.0.3: - resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==} + react-sizeme@3.0.2: + resolution: {integrity: sha512-xOIAOqqSSmKlKFJLO3inBQBdymzDuXx4iuwkNcJmC96jeiOg5ojByvL+g3MW9LPEsojLbC6pf68zOfobK8IPlw==} - string-argv@0.3.2: - resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} - engines: {node: '>=0.6.19'} + react-stately@3.31.1: + resolution: {integrity: sha512-wuq673NHkYSdoceGryjtMJJvB9iQgyDkQDsnTN0t2v91pXjGDsN/EcOvnUrxXSBtY9eLdIw74R54z9GX5cJNEg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - string-length@4.0.2: - resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} + react-style-singleton@2.2.1: + resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} + react-textarea-autosize@8.5.3: + resolution: {integrity: sha512-XT1024o2pqCuZSuBt9FwHlaDeNtVrtCXu0Rnz88t1jUGheCLa3PhjE1GH8Ctm2axEtvdCl5SUHYschyQ0L5QHQ==} + engines: {node: '>=10'} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} + react-timeago@7.2.0: + resolution: {integrity: sha512-2KsBEEs+qRhKx/kekUVNSTIpop3Jwd7SRBm0R4Eiq3mPeswRGSsftY9FpKsE/lXLdURyQFiHeHFrIUxLYskG5g==} + peerDependencies: + react: ^16.0.0 || ^17.0.0 || ^18.0.0 - string.prototype.trim@1.2.9: - resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==} - engines: {node: '>= 0.4'} - - string.prototype.trimend@1.0.8: - resolution: {integrity: sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==} + react-tiny-popover@7.2.4: + resolution: {integrity: sha512-T7ZSwXcUtPXCog3Bux9+TjoTvUeMi/+zI0Yv/TkIznZCWUg0XTt2797G0IiT5mTVeJeLivUzdOmKA1hOQdMfOQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - string.prototype.trimstart@1.0.8: - resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} - engines: {node: '>= 0.4'} + react-truncate-markup@5.1.2: + resolution: {integrity: sha512-eEq6T8Rs+wz98cRYzQECGFNBfXwRYraLg/kz52f6DRBKmzxqB+GYLeDkVe/zrC+2vh5AEwM6nSYFvDWEBljd0w==} + peerDependencies: + react: '>=16.3' - string_decoder@0.10.31: - resolution: {integrity: sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==} + react-universal-interface@0.6.2: + resolution: {integrity: sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw==} + peerDependencies: + react: '*' + tslib: '*' - string_decoder@1.1.1: - resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + react-use@17.5.0: + resolution: {integrity: sha512-PbfwSPMwp/hoL847rLnm/qkjg3sTRCvn6YhUZiHaUa3FA6/aNoFX79ul5Xt70O1rK+9GxSVqkY0eTwMdsR/bWg==} + peerDependencies: + react: '*' + react-dom: '*' - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} - stringify-package@1.0.1: - resolution: {integrity: sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==} - deprecated: This module is not used anymore, and has been replaced by @npmcli/package-json + read-pkg-up@3.0.0: + resolution: {integrity: sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==} + engines: {node: '>=4'} - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + read-pkg-up@7.0.1: + resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} engines: {node: '>=8'} - strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} - engines: {node: '>=12'} - - strip-bom@3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + read-pkg@3.0.0: + resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==} engines: {node: '>=4'} - strip-bom@4.0.0: - resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + read-pkg@5.2.0: + resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} engines: {node: '>=8'} - strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} - - strip-final-newline@3.0.0: - resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} - engines: {node: '>=12'} + readable-stream@1.0.34: + resolution: {integrity: sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==} - strip-indent@3.0.0: - resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} - engines: {node: '>=8'} + readable-stream@1.1.14: + resolution: {integrity: sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==} - strip-indent@4.0.0: - resolution: {integrity: sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==} - engines: {node: '>=12'} + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} - strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} - styled-components@6.1.11: - resolution: {integrity: sha512-Ui0jXPzbp1phYij90h12ksljKGqF8ncGx+pjrNPsSPhbUUjWT2tD1FwGo2LF6USCnbrsIhNngDfodhxbegfEOA==} - engines: {node: '>= 16'} - peerDependencies: - react: '>= 16.8.0' - react-dom: '>= 16.8.0' + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} - stylelint-config-recommended@14.0.0: - resolution: {integrity: sha512-jSkx290CglS8StmrLp2TxAppIajzIBZKYm3IxT89Kg6fGlxbPiTiyH9PS5YUuVAFwaJLl1ikiXX0QWjI0jmgZQ==} - engines: {node: '>=18.12.0'} - peerDependencies: - stylelint: ^16.0.0 + recast@0.23.9: + resolution: {integrity: sha512-Hx/BGIbwj+Des3+xy5uAtAbdCyqK9y9wbBcDFDYanLS9JnMqf7OeF87HQwUimE87OEc72mr6tkKUKMBBL+hF9Q==} + engines: {node: '>= 4'} - stylelint-config-standard@36.0.0: - resolution: {integrity: sha512-3Kjyq4d62bYFp/Aq8PMKDwlgUyPU4nacXsjDLWJdNPRUgpuxALu1KnlAHIj36cdtxViVhXexZij65yM0uNIHug==} - engines: {node: '>=18.12.0'} - peerDependencies: - stylelint: ^16.1.0 + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} - stylelint-no-unsupported-browser-features@8.0.1: - resolution: {integrity: sha512-tc8Xn5DaqJhxTmbA4H8gZbYdAz027NfuSZv5+cVieQb7BtBrF/1/iKYdpcGwXPl3GtqkQrisiXuGqKkKnzWcLw==} - engines: {node: '>=18.12.0'} - peerDependencies: - stylelint: ^16.0.2 + regenerate-unicode-properties@10.1.1: + resolution: {integrity: sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==} + engines: {node: '>=4'} - stylelint@16.6.1: - resolution: {integrity: sha512-yNgz2PqWLkhH2hw6X9AweV9YvoafbAD5ZsFdKN9BvSDVwGvPh+AUIrn7lYwy1S7IHmtFin75LLfX1m0D2tHu8Q==} - engines: {node: '>=18.12.0'} - hasBin: true + regenerate@1.4.2: + resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} - stylis@4.3.2: - resolution: {integrity: sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==} + regenerator-runtime@0.13.11: + resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} - stylus@0.62.0: - resolution: {integrity: sha512-v3YCf31atbwJQIMtPNX8hcQ+okD4NQaTuKGUWfII8eaqn+3otrbttGL1zSMZAAtiPsBztQnujVBugg/cXFUpyg==} - hasBin: true + regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} - substyle@9.4.1: - resolution: {integrity: sha512-VOngeq/W1/UkxiGzeqVvDbGDPM8XgUyJVWjrqeh+GgKqspEPiLYndK+XRcsKUHM5Muz/++1ctJ1QCF/OqRiKWA==} - peerDependencies: - react: '>=16.8.3' + regenerator-transform@0.15.2: + resolution: {integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==} - sucrase@3.35.0: - resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} - engines: {node: '>=16 || 14 >=14.17'} - hasBin: true + regexp.prototype.flags@1.5.2: + resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} + engines: {node: '>= 0.4'} - supports-color@5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + regexpu-core@5.3.2: + resolution: {integrity: sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==} engines: {node: '>=4'} - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - - supports-color@8.1.1: - resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} - engines: {node: '>=10'} - - supports-hyperlinks@3.0.0: - resolution: {integrity: sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==} - engines: {node: '>=14.18'} - - supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} + regjsparser@0.9.1: + resolution: {integrity: sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==} + hasBin: true - svg-tags@1.0.0: - resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==} + reinterval@1.1.0: + resolution: {integrity: sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==} - svg-url-loader@7.1.1: - resolution: {integrity: sha512-NlsMCePODm7FQhU9aEZyGLPx5Xe1QRI1cSEUE6vTq5LJc9l9pStagvXoEIyZ9O3r00w6G3+Wbkimb+SC3DI/Aw==} - engines: {node: '>=10'} - peerDependencies: - webpack: ^4.0.0 || ^5.0.0 + remark-external-links@8.0.0: + resolution: {integrity: sha512-5vPSX0kHoSsqtdftSHhIYofVINC8qmp0nctkeU9YoJwV3YfiBRiI6cbFRJ0oI/1F9xS+bopXG0m2KS8VFscuKA==} - synchronous-promise@2.0.17: - resolution: {integrity: sha512-AsS729u2RHUfEra9xJrE39peJcc2stq2+poBXX8bcM08Y6g9j/i/PUzwNQqkaJde7Ntg1TO7bSREbR5sdosQ+g==} + remark-slug@6.1.0: + resolution: {integrity: sha512-oGCxDF9deA8phWvxFuyr3oSJsdyUAxMFbA0mZ7Y1Sas+emILtO+e5WutF9564gDsEN4IXaQXm5pFo6MLH+YmwQ==} - table@6.8.2: - resolution: {integrity: sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==} - engines: {node: '>=10.0.0'} + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} - tapable@2.2.1: - resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} - engines: {node: '>=6'} + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} - tar-fs@2.1.1: - resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} + reserved-words@0.1.2: + resolution: {integrity: sha512-0S5SrIUJ9LfpbVl4Yzij6VipUdafHrOTzvmfazSw/jeZrZtQK303OPZW+obtkaw7jQlTQppy0UvZWm9872PbRw==} - tar-stream@2.2.0: - resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} - engines: {node: '>=6'} + resize-observer-polyfill@1.5.1: + resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} - tar@6.2.1: - resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} - engines: {node: '>=10'} + resolve-cwd@3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} - telejson@7.2.0: - resolution: {integrity: sha512-1QTEcJkJEhc8OnStBx/ILRu5J2p0GjvWsBx56bmZRqnrkdBMUe+nX92jxV+p3dB4CP6PZCdJMQJwCggkNBMzkQ==} + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} - temp-dir@2.0.0: - resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==} + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} - temp@0.8.4: - resolution: {integrity: sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==} - engines: {node: '>=6.0.0'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - tempy@1.0.1: - resolution: {integrity: sha512-biM9brNqxSc04Ee71hzFbryD11nX7VPhQQY32AdDmjFvodsRFz/3ufeoTZ6uYkRFfGo188tENcASNs3vTdsM0w==} + resolve.exports@2.0.2: + resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} engines: {node: '>=10'} - terser-webpack-plugin@5.3.10: - resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} - engines: {node: '>= 10.13.0'} - peerDependencies: - '@swc/core': '*' - esbuild: '*' - uglify-js: '*' - webpack: ^5.1.0 - peerDependenciesMeta: - '@swc/core': - optional: true - esbuild: - optional: true - uglify-js: - optional: true - - terser@5.31.1: - resolution: {integrity: sha512-37upzU1+viGvuFtBo9NPufCb9dwM0+l9hMxYyWfBA+fbwrPqNJAhbZ6W47bBFnZHKHTUBnMvi87434qq+qnxOg==} - engines: {node: '>=10'} + resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} hasBin: true - test-exclude@6.0.0: - resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} - text-extensions@1.9.0: - resolution: {integrity: sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==} - engines: {node: '>=0.10'} + restore-cursor@4.0.0: + resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - thenify-all@1.6.0: - resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} - engines: {node: '>=0.8'} + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - thenify@3.3.1: - resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + rimraf@2.6.3: + resolution: {integrity: sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true - throttle-debounce@2.3.0: - resolution: {integrity: sha512-H7oLPV0P7+jgvrk+6mwwwBDmxTaxnu9HMXmloNLXwnNO0ZxZ31Orah2n8lU1eMPvsaowP2CX+USCgyovXfdOFQ==} - engines: {node: '>=8'} + rimraf@2.7.1: + resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true - throttle-debounce@3.0.1: - resolution: {integrity: sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==} - engines: {node: '>=10'} + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true - through2@0.6.5: - resolution: {integrity: sha512-RkK/CCESdTKQZHdmKICijdKKsCRVHs5KsLZ6pACAmF/1GPUQhonHSXWNERctxEp7RmvjdNbZTL5z9V7nSCXKcg==} + rollup@3.29.4: + resolution: {integrity: sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==} + engines: {node: '>=14.18.0', npm: '>=8.0.0'} + hasBin: true - through2@2.0.5: - resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} + rollup@4.18.0: + resolution: {integrity: sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true - through2@4.0.2: - resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} + rtl-css-js@1.16.1: + resolution: {integrity: sha512-lRQgou1mu19e+Ya0LsTvKrVJ5TYUbqCVPAiImX3UfLTenarvPUl1QFdvu5Z3PYmHT9RCcwIfbjRQBntExyj3Zg==} - through@2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - tiny-invariant@1.3.3: - resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + safe-array-concat@1.1.2: + resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} + engines: {node: '>=0.4'} - tmpl@1.0.5: - resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} - to-array@0.1.4: - resolution: {integrity: sha512-LhVdShQD/4Mk4zXNroIQZJC+Ap3zgLcDuwEdcmLv9CCO73NWockQDwyUnW/m8VX/EElfL6FcYx7EeutN4HJA6A==} + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - to-fast-properties@2.0.0: - resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} - engines: {node: '>=4'} + safe-regex-test@1.0.3: + resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} + engines: {node: '>= 0.4'} - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - tocbot@4.28.2: - resolution: {integrity: sha512-/MaSa9xI6mIo84IxqqliSCtPlH0oy7sLcY9s26qPMyH/2CxtZ2vNAXYlIdEQ7kjAkCQnc0rbLygf//F5c663oQ==} + sass@1.77.6: + resolution: {integrity: sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q==} + engines: {node: '>=14.0.0'} + hasBin: true - toggle-selection@1.0.6: - resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} + sax@1.3.0: + resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==} - toidentifier@1.0.1: - resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} - engines: {node: '>=0.6'} + sax@1.4.1: + resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==} - tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} - tr46@1.0.1: - resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} + schema-utils@3.3.0: + resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} + engines: {node: '>= 10.13.0'} - tree-kill@1.2.2: - resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + screenfull@5.2.0: + resolution: {integrity: sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==} + engines: {node: '>=0.10.0'} + + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true - trim-newlines@3.0.1: - resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} - engines: {node: '>=8'} + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true - ts-api-utils@1.3.0: - resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} - engines: {node: '>=16'} - peerDependencies: - typescript: '>=4.2.0' + semver@7.6.2: + resolution: {integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==} + engines: {node: '>=10'} + hasBin: true - ts-dedent@2.2.0: - resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} - engines: {node: '>=6.10'} + send@0.18.0: + resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} + engines: {node: '>= 0.8.0'} - ts-easing@0.2.0: - resolution: {integrity: sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==} + serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} - ts-interface-checker@0.1.13: - resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + serve-static@1.15.0: + resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} + engines: {node: '>= 0.8.0'} - ts-jest@29.1.5: - resolution: {integrity: sha512-UuClSYxM7byvvYfyWdFI+/2UxMmwNyJb0NPkZPQE2hew3RurV7l7zURgOHAd/1I1ZdPpe3GUsXNXAcN8TFKSIg==} - engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@babel/core': '>=7.0.0-beta.0 <8' - '@jest/transform': ^29.0.0 - '@jest/types': ^29.0.0 - babel-jest: ^29.0.0 - esbuild: '*' - jest: ^29.0.0 - typescript: '>=4.3 <6' - peerDependenciesMeta: - '@babel/core': - optional: true - '@jest/transform': - optional: true - '@jest/types': - optional: true - babel-jest: - optional: true - esbuild: - optional: true + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} - tsconfck@3.1.0: - resolution: {integrity: sha512-CMjc5zMnyAjcS9sPLytrbFmj89st2g+JYtY/c02ug4Q+CZaAtCgbyviI0n1YvjZE/pzoc6FbNsINS13DOL1B9w==} - engines: {node: ^18 || >=20} - hasBin: true - peerDependencies: - typescript: ^5.0.0 - peerDependenciesMeta: - typescript: - optional: true + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} - tsconfig-paths@3.15.0: - resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + set-harmonic-interval@1.0.1: + resolution: {integrity: sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g==} + engines: {node: '>=6.9'} - tsconfig-paths@4.2.0: - resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} - engines: {node: '>=6'} + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - tslib@1.14.1: - resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + shallow-clone@3.0.1: + resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==} + engines: {node: '>=8'} - tslib@2.6.2: - resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + shallowequal@1.1.0: + resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==} - tslib@2.6.3: - resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} - tsup@7.3.0: - resolution: {integrity: sha512-Ja1eaSRrE+QarmATlNO5fse2aOACYMBX+IZRKy1T+gpyH+jXgRrl5l4nHIQJQ1DoDgEjHDTw8cpE085UdBZuWQ==} - engines: {node: '>=18'} - deprecated: Breaking node 16 - hasBin: true - peerDependencies: - '@swc/core': ^1 - postcss: ^8.4.12 - typescript: '>=4.5.0' - peerDependenciesMeta: - '@swc/core': - optional: true - postcss: - optional: true - typescript: - optional: true + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} - tsutils@3.21.0: - resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} - engines: {node: '>= 6'} - peerDependencies: - typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + side-channel@1.0.6: + resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} + engines: {node: '>= 0.4'} - type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - type-detect@4.0.8: - resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} - engines: {node: '>=4'} + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} - type-fest@0.16.0: - resolution: {integrity: sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==} - engines: {node: '>=10'} + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - type-fest@0.18.1: - resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} - engines: {node: '>=10'} + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} - type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + slice-ansi@4.0.0: + resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} engines: {node: '>=10'} - type-fest@0.21.3: - resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} - engines: {node: '>=10'} + slice-ansi@5.0.0: + resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} + engines: {node: '>=12'} - type-fest@0.6.0: - resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} - engines: {node: '>=8'} + socket.io-client@2.2.0: + resolution: {integrity: sha512-56ZrkTDbdTLmBIyfFYesgOxsjcLnwAKoN4CiPyTVkMQj3zTUh0QAx3GbvIvLpFEOvQWu92yyWICxB0u7wkVbYA==} - type-fest@0.8.1: - resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} - engines: {node: '>=8'} + socket.io-parser@3.3.3: + resolution: {integrity: sha512-qOg87q1PMWWTeO01768Yh9ogn7chB9zkKtQnya41Y355S0UmpXgpcrFwAgjYJxu9BdKug5r5e9YtVSeWhKBUZg==} - type-fest@1.4.0: - resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} - engines: {node: '>=10'} + source-map-js@1.2.0: + resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} + engines: {node: '>=0.10.0'} - type-fest@2.19.0: - resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} - engines: {node: '>=12.20'} + source-map-support@0.5.13: + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} - type-is@1.6.18: - resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} - engines: {node: '>= 0.6'} + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - typed-array-buffer@1.0.2: - resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} - engines: {node: '>= 0.4'} + source-map@0.5.6: + resolution: {integrity: sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==} + engines: {node: '>=0.10.0'} - typed-array-byte-length@1.0.1: - resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==} - engines: {node: '>= 0.4'} + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} - typed-array-byte-offset@1.0.2: - resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==} - engines: {node: '>= 0.4'} + source-map@0.7.4: + resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} + engines: {node: '>= 8'} - typed-array-length@1.0.6: - resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} - engines: {node: '>= 0.4'} + source-map@0.8.0-beta.0: + resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} + engines: {node: '>= 8'} - typedarray@0.0.6: - resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + sourcemap-codec@1.4.8: + resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} + deprecated: Please use @jridgewell/sourcemap-codec instead - typescript-plugin-css-modules@5.1.0: - resolution: {integrity: sha512-6h+sLBa4l+XYSTn/31vZHd/1c3SvAbLpobY6FxDiUOHJQG1eD9Gh3eCs12+Eqc+TCOAdxcO+zAPvUq0jBfdciw==} - peerDependencies: - typescript: '>=4.0.0' + space-separated-tokens@1.1.5: + resolution: {integrity: sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==} - typescript@4.9.5: - resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} - engines: {node: '>=4.2.0'} - hasBin: true + spdx-correct@3.2.0: + resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} - ufo@1.5.3: - resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==} + spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} - uglify-js@3.18.0: - resolution: {integrity: sha512-SyVVbcNBCk0dzr9XL/R/ySrmYf0s372K6/hFklzgcp2lBFyXtw4I7BOdDjlLhE1aVqaI/SHWXWmYdlZxuyF38A==} - engines: {node: '>=0.8.0'} - hasBin: true + spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} - unbox-primitive@1.0.2: - resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + spdx-license-ids@3.0.18: + resolution: {integrity: sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==} - undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + split2@0.2.1: + resolution: {integrity: sha512-D/oTExYAkC9nWleOCTOyNmAuzfAT/6rHGBA9LIK7FVnGo13CSvrKCUzKenwH6U1s2znY9MqH6v0UQTEDa3vJmg==} - unicode-canonical-property-names-ecmascript@2.0.0: - resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} - engines: {node: '>=4'} + split2@3.2.2: + resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} - unicode-match-property-ecmascript@2.0.0: - resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} - engines: {node: '>=4'} + split@1.0.1: + resolution: {integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==} - unicode-match-property-value-ecmascript@2.1.0: - resolution: {integrity: sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==} - engines: {node: '>=4'} + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - unicode-property-aliases-ecmascript@2.1.0: - resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==} - engines: {node: '>=4'} + stack-generator@2.0.10: + resolution: {integrity: sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==} - unique-string@2.0.0: - resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} - engines: {node: '>=8'} + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} - unist-util-is@4.1.0: - resolution: {integrity: sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==} + stackframe@1.3.4: + resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} - unist-util-visit-parents@3.1.1: - resolution: {integrity: sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==} + stacktrace-gps@3.1.2: + resolution: {integrity: sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ==} - unist-util-visit@2.0.3: - resolution: {integrity: sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==} + stacktrace-js@2.0.2: + resolution: {integrity: sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==} - universalify@2.0.1: - resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} - engines: {node: '>= 10.0.0'} + standard-version@9.5.0: + resolution: {integrity: sha512-3zWJ/mmZQsOaO+fOlsa0+QK90pwhNd042qEcw6hKFNoLFs7peGyvPffpEBbK/DSGPbyOvli0mUIFv5A4qTjh2Q==} + engines: {node: '>=10'} + hasBin: true - unpipe@1.0.0: - resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} - unplugin@1.10.1: - resolution: {integrity: sha512-d6Mhq8RJeGA8UfKCu54Um4lFA0eSaRa3XxdAJg8tIdxbu1ubW0hBCZUL7yI2uGyYCRndvbK8FLHzqy2XKfeMsg==} - engines: {node: '>=14.0.0'} - - untildify@4.0.0: - resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} - engines: {node: '>=8'} + store2@2.14.3: + resolution: {integrity: sha512-4QcZ+yx7nzEFiV4BMLnr/pRa5HYzNITX2ri0Zh6sT9EyQHbBHacC6YigllUPU9X3D0f/22QCgfokpKs52YRrUg==} - update-browserslist-db@1.0.16: - resolution: {integrity: sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==} + storybook@7.6.19: + resolution: {integrity: sha512-xWD1C4vD/4KMffCrBBrUpsLUO/9uNpm8BVW8+Vcb30gkQDfficZ0oziWkmLexpT53VSioa24iazGXMwBqllYjQ==} hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + stream-shift@1.0.3: + resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==} - use-callback-ref@1.3.2: - resolution: {integrity: sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true + string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} + engines: {node: '>=0.6.19'} - use-composed-ref@1.3.0: - resolution: {integrity: sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + string-length@4.0.2: + resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} + engines: {node: '>=10'} - use-isomorphic-layout-effect@1.1.2: - resolution: {integrity: sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} - use-latest@1.2.1: - resolution: {integrity: sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw==} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} - use-resize-observer@9.1.0: - resolution: {integrity: sha512-R25VqO9Wb3asSD4eqtcxk8sJalvIOYBqS8MNZlpDSQ4l4xMQxC/J7Id9HoTqPq8FwULIn0PVW+OAqF2dyYbjow==} - peerDependencies: - react: 16.8.0 - 18 - react-dom: 16.8.0 - 18 + string.prototype.trim@1.2.9: + resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==} + engines: {node: '>= 0.4'} - use-sidecar@1.1.2: - resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': ^16.9.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true + string.prototype.trimend@1.0.8: + resolution: {integrity: sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==} - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} - util@0.12.5: - resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} + string_decoder@0.10.31: + resolution: {integrity: sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==} - utils-merge@1.0.1: - resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} - engines: {node: '>= 0.4.0'} + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} - uuid@8.3.2: - resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} - hasBin: true + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - uuid@9.0.1: - resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} - hasBin: true + stringify-package@1.0.1: + resolution: {integrity: sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==} + deprecated: This module is not used anymore, and has been replaced by @npmcli/package-json - v8-to-istanbul@9.2.0: - resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==} - engines: {node: '>=10.12.0'} + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} - validate-npm-package-license@3.0.4: - resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} - vary@1.1.2: - resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} - engines: {node: '>= 0.8'} + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} - vaul@0.9.1: - resolution: {integrity: sha512-fAhd7i4RNMinx+WEm6pF3nOl78DFkAazcN04ElLPFF9BMCNGbY/kou8UMhIcicm0rJCNePJP0Yyza60gGOD0Jw==} - peerDependencies: - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - - vite-tsconfig-paths@4.3.2: - resolution: {integrity: sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==} - peerDependencies: - vite: '*' - peerDependenciesMeta: - vite: - optional: true - - vite@4.5.3: - resolution: {integrity: sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==} - engines: {node: ^14.18.0 || >=16.0.0} - hasBin: true - peerDependencies: - '@types/node': '>= 14' - less: '*' - lightningcss: ^1.21.0 - sass: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - - walker@1.0.8: - resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + strip-bom@4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} - watchpack@2.4.1: - resolution: {integrity: sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==} - engines: {node: '>=10.13.0'} + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} - wcwidth@1.0.1: - resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} - webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} - webidl-conversions@4.0.2: - resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} + strip-indent@4.0.0: + resolution: {integrity: sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==} + engines: {node: '>=12'} - webpack-sources@3.2.3: - resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} - engines: {node: '>=10.13.0'} + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} - webpack-virtual-modules@0.6.2: - resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + styled-components@6.1.11: + resolution: {integrity: sha512-Ui0jXPzbp1phYij90h12ksljKGqF8ncGx+pjrNPsSPhbUUjWT2tD1FwGo2LF6USCnbrsIhNngDfodhxbegfEOA==} + engines: {node: '>= 16'} + peerDependencies: + react: '>= 16.8.0' + react-dom: '>= 16.8.0' - webpack@5.92.0: - resolution: {integrity: sha512-Bsw2X39MYIgxouNATyVpCNVWBCuUwDgWtN78g6lSdPJRLaQ/PUVm/oXcaRAyY/sMFoKFQrsPeqvTizWtq7QPCA==} - engines: {node: '>=10.13.0'} - hasBin: true + stylelint-config-recommended@14.0.0: + resolution: {integrity: sha512-jSkx290CglS8StmrLp2TxAppIajzIBZKYm3IxT89Kg6fGlxbPiTiyH9PS5YUuVAFwaJLl1ikiXX0QWjI0jmgZQ==} + engines: {node: '>=18.12.0'} peerDependencies: - webpack-cli: '*' - peerDependenciesMeta: - webpack-cli: - optional: true + stylelint: ^16.0.0 - whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + stylelint-config-standard@36.0.0: + resolution: {integrity: sha512-3Kjyq4d62bYFp/Aq8PMKDwlgUyPU4nacXsjDLWJdNPRUgpuxALu1KnlAHIj36cdtxViVhXexZij65yM0uNIHug==} + engines: {node: '>=18.12.0'} + peerDependencies: + stylelint: ^16.1.0 - whatwg-url@7.1.0: - resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} + stylelint-no-unsupported-browser-features@8.0.1: + resolution: {integrity: sha512-tc8Xn5DaqJhxTmbA4H8gZbYdAz027NfuSZv5+cVieQb7BtBrF/1/iKYdpcGwXPl3GtqkQrisiXuGqKkKnzWcLw==} + engines: {node: '>=18.12.0'} + peerDependencies: + stylelint: ^16.0.2 - which-boxed-primitive@1.0.2: - resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + stylelint@16.6.1: + resolution: {integrity: sha512-yNgz2PqWLkhH2hw6X9AweV9YvoafbAD5ZsFdKN9BvSDVwGvPh+AUIrn7lYwy1S7IHmtFin75LLfX1m0D2tHu8Q==} + engines: {node: '>=18.12.0'} + hasBin: true - which-typed-array@1.1.15: - resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} - engines: {node: '>= 0.4'} + stylis@4.3.2: + resolution: {integrity: sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==} - which@1.3.1: - resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + stylus@0.62.0: + resolution: {integrity: sha512-v3YCf31atbwJQIMtPNX8hcQ+okD4NQaTuKGUWfII8eaqn+3otrbttGL1zSMZAAtiPsBztQnujVBugg/cXFUpyg==} hasBin: true - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} + substyle@9.4.1: + resolution: {integrity: sha512-VOngeq/W1/UkxiGzeqVvDbGDPM8XgUyJVWjrqeh+GgKqspEPiLYndK+XRcsKUHM5Muz/++1ctJ1QCF/OqRiKWA==} + peerDependencies: + react: '>=16.8.3' + + sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} hasBin: true - word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} - wordwrap@1.0.0: - resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} engines: {node: '>=10'} - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - write-file-atomic@2.4.3: - resolution: {integrity: sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==} - - write-file-atomic@4.0.2: - resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + supports-hyperlinks@3.0.0: + resolution: {integrity: sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==} + engines: {node: '>=14.18'} - write-file-atomic@5.0.1: - resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} - ws@6.1.4: - resolution: {integrity: sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true + svg-tags@1.0.0: + resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==} - ws@6.2.3: - resolution: {integrity: sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==} + svg-url-loader@7.1.1: + resolution: {integrity: sha512-NlsMCePODm7FQhU9aEZyGLPx5Xe1QRI1cSEUE6vTq5LJc9l9pStagvXoEIyZ9O3r00w6G3+Wbkimb+SC3DI/Aw==} + engines: {node: '>=10'} peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true + webpack: ^4.0.0 || ^5.0.0 - ws@7.5.10: - resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} - engines: {node: '>=8.3.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true + synchronous-promise@2.0.17: + resolution: {integrity: sha512-AsS729u2RHUfEra9xJrE39peJcc2stq2+poBXX8bcM08Y6g9j/i/PUzwNQqkaJde7Ntg1TO7bSREbR5sdosQ+g==} - ws@8.17.1: - resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} + table@6.8.2: + resolution: {integrity: sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==} engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - xmlhttprequest-ssl@1.6.3: - resolution: {integrity: sha512-3XfeQE/wNkvrIktn2Kf0869fC0BN6UpydVasGIeSm2B1Llihf7/0UfZM+eCkOw3P7bP4+qPgqhm7ZoxuJtFU0Q==} - engines: {node: '>=0.4.0'} + tapable@2.2.1: + resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} + engines: {node: '>=6'} - xtend@4.0.2: - resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} - engines: {node: '>=0.4'} + tar-fs@2.1.1: + resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} - y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} - yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + telejson@7.2.0: + resolution: {integrity: sha512-1QTEcJkJEhc8OnStBx/ILRu5J2p0GjvWsBx56bmZRqnrkdBMUe+nX92jxV+p3dB4CP6PZCdJMQJwCggkNBMzkQ==} - yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + temp-dir@2.0.0: + resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==} + engines: {node: '>=8'} - yaml@1.10.2: - resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} - engines: {node: '>= 6'} + temp@0.8.4: + resolution: {integrity: sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==} + engines: {node: '>=6.0.0'} - yaml@2.3.1: - resolution: {integrity: sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==} - engines: {node: '>= 14'} - - yaml@2.4.5: - resolution: {integrity: sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==} - engines: {node: '>= 14'} - hasBin: true - - yargs-parser@20.2.9: - resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + tempy@1.0.1: + resolution: {integrity: sha512-biM9brNqxSc04Ee71hzFbryD11nX7VPhQQY32AdDmjFvodsRFz/3ufeoTZ6uYkRFfGo188tENcASNs3vTdsM0w==} engines: {node: '>=10'} - yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} + terser-webpack-plugin@5.3.10: + resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@swc/core': '*' + esbuild: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@swc/core': + optional: true + esbuild: + optional: true + uglify-js: + optional: true - yargs@16.2.0: - resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + terser@5.31.1: + resolution: {integrity: sha512-37upzU1+viGvuFtBo9NPufCb9dwM0+l9hMxYyWfBA+fbwrPqNJAhbZ6W47bBFnZHKHTUBnMvi87434qq+qnxOg==} engines: {node: '>=10'} + hasBin: true - yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} - engines: {node: '>=12'} - - yauzl@2.10.0: - resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} - yeast@0.1.2: - resolution: {integrity: sha512-8HFIh676uyGYP6wP13R/j6OJ/1HwJ46snpvzE7aHAN3Ryqh2yX6Xox2B4CUmTwwOIzlG3Bs7ocsP5dZH/R1Qbg==} + text-extensions@1.9.0: + resolution: {integrity: sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==} + engines: {node: '>=0.10'} - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - zod@3.23.8: - resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} -snapshots: + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - '@adobe/css-tools@4.3.3': {} + throttle-debounce@2.3.0: + resolution: {integrity: sha512-H7oLPV0P7+jgvrk+6mwwwBDmxTaxnu9HMXmloNLXwnNO0ZxZ31Orah2n8lU1eMPvsaowP2CX+USCgyovXfdOFQ==} + engines: {node: '>=8'} - '@amityco/ts-sdk@6.26.3': - dependencies: - agentkeepalive: 4.5.0 - axios: 1.7.2(debug@4.3.5) - debug: 4.3.5 - hls.js: 1.5.11 - js-base64: 3.7.7 - mitt: 3.0.1 - mqtt: 4.3.8 - object-hash: 3.0.0 - react-native-uuid: 2.0.2 - socket.io-client: 2.2.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate + throttle-debounce@3.0.1: + resolution: {integrity: sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==} + engines: {node: '>=10'} - '@ampproject/remapping@2.3.0': - dependencies: - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 + through2@0.6.5: + resolution: {integrity: sha512-RkK/CCESdTKQZHdmKICijdKKsCRVHs5KsLZ6pACAmF/1GPUQhonHSXWNERctxEp7RmvjdNbZTL5z9V7nSCXKcg==} - '@aw-web-design/x-default-browser@1.4.126': - dependencies: - default-browser-id: 3.0.0 + through2@2.0.5: + resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} - '@babel/code-frame@7.24.7': - dependencies: - '@babel/highlight': 7.24.7 - picocolors: 1.0.1 + through2@4.0.2: + resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} - '@babel/compat-data@7.24.7': {} + through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - '@babel/core@7.24.7': - dependencies: - '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.24.7 - '@babel/generator': 7.24.7 - '@babel/helper-compilation-targets': 7.24.7 - '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7) - '@babel/helpers': 7.24.7 - '@babel/parser': 7.24.7 - '@babel/template': 7.24.7 - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 - convert-source-map: 2.0.0 - debug: 4.3.5 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} - '@babel/generator@7.24.7': - dependencies: - '@babel/types': 7.24.7 - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 - jsesc: 2.5.2 + tmpl@1.0.5: + resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} - '@babel/helper-annotate-as-pure@7.24.7': - dependencies: - '@babel/types': 7.24.7 + to-array@0.1.4: + resolution: {integrity: sha512-LhVdShQD/4Mk4zXNroIQZJC+Ap3zgLcDuwEdcmLv9CCO73NWockQDwyUnW/m8VX/EElfL6FcYx7EeutN4HJA6A==} - '@babel/helper-builder-binary-assignment-operator-visitor@7.24.7': - dependencies: - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 - transitivePeerDependencies: - - supports-color + to-fast-properties@2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} - '@babel/helper-compilation-targets@7.24.7': - dependencies: - '@babel/compat-data': 7.24.7 - '@babel/helper-validator-option': 7.24.7 - browserslist: 4.23.1 - lru-cache: 5.1.1 - semver: 6.3.1 + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} - '@babel/helper-create-class-features-plugin@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-annotate-as-pure': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-function-name': 7.24.7 - '@babel/helper-member-expression-to-functions': 7.24.7 - '@babel/helper-optimise-call-expression': 7.24.7 - '@babel/helper-replace-supers': 7.24.7(@babel/core@7.24.7) - '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 - '@babel/helper-split-export-declaration': 7.24.7 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color + tocbot@4.28.2: + resolution: {integrity: sha512-/MaSa9xI6mIo84IxqqliSCtPlH0oy7sLcY9s26qPMyH/2CxtZ2vNAXYlIdEQ7kjAkCQnc0rbLygf//F5c663oQ==} - '@babel/helper-create-regexp-features-plugin@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-annotate-as-pure': 7.24.7 - regexpu-core: 5.3.2 - semver: 6.3.1 + toggle-selection@1.0.6: + resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} - '@babel/helper-define-polyfill-provider@0.6.2(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-compilation-targets': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - debug: 4.3.5 - lodash.debounce: 4.0.8 - resolve: 1.22.8 - transitivePeerDependencies: - - supports-color + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} - '@babel/helper-environment-visitor@7.24.7': - dependencies: - '@babel/types': 7.24.7 + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - '@babel/helper-function-name@7.24.7': - dependencies: - '@babel/template': 7.24.7 - '@babel/types': 7.24.7 + tr46@1.0.1: + resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} - '@babel/helper-hoist-variables@7.24.7': - dependencies: - '@babel/types': 7.24.7 + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true - '@babel/helper-member-expression-to-functions@7.24.7': - dependencies: - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 - transitivePeerDependencies: - - supports-color + trim-newlines@3.0.1: + resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} + engines: {node: '>=8'} - '@babel/helper-module-imports@7.24.7': - dependencies: - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 - transitivePeerDependencies: - - supports-color + ts-api-utils@1.3.0: + resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' - '@babel/helper-module-transforms@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-module-imports': 7.24.7 - '@babel/helper-simple-access': 7.24.7 - '@babel/helper-split-export-declaration': 7.24.7 - '@babel/helper-validator-identifier': 7.24.7 - transitivePeerDependencies: - - supports-color + ts-dedent@2.2.0: + resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} + engines: {node: '>=6.10'} - '@babel/helper-optimise-call-expression@7.24.7': - dependencies: - '@babel/types': 7.24.7 + ts-easing@0.2.0: + resolution: {integrity: sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==} - '@babel/helper-plugin-utils@7.24.7': {} + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - '@babel/helper-remap-async-to-generator@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-annotate-as-pure': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-wrap-function': 7.24.7 - transitivePeerDependencies: - - supports-color + ts-jest@29.1.5: + resolution: {integrity: sha512-UuClSYxM7byvvYfyWdFI+/2UxMmwNyJb0NPkZPQE2hew3RurV7l7zURgOHAd/1I1ZdPpe3GUsXNXAcN8TFKSIg==} + engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/transform': ^29.0.0 + '@jest/types': ^29.0.0 + babel-jest: ^29.0.0 + esbuild: '*' + jest: ^29.0.0 + typescript: '>=4.3 <6' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/transform': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true - '@babel/helper-replace-supers@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-member-expression-to-functions': 7.24.7 - '@babel/helper-optimise-call-expression': 7.24.7 - transitivePeerDependencies: - - supports-color + tsconfck@3.1.0: + resolution: {integrity: sha512-CMjc5zMnyAjcS9sPLytrbFmj89st2g+JYtY/c02ug4Q+CZaAtCgbyviI0n1YvjZE/pzoc6FbNsINS13DOL1B9w==} + engines: {node: ^18 || >=20} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true - '@babel/helper-simple-access@7.24.7': - dependencies: - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 - transitivePeerDependencies: - - supports-color + tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} - '@babel/helper-skip-transparent-expression-wrappers@7.24.7': - dependencies: - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 - transitivePeerDependencies: - - supports-color + tsconfig-paths@4.2.0: + resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} + engines: {node: '>=6'} - '@babel/helper-split-export-declaration@7.24.7': - dependencies: - '@babel/types': 7.24.7 + tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} - '@babel/helper-string-parser@7.24.7': {} + tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - '@babel/helper-validator-identifier@7.24.7': {} + tslib@2.6.3: + resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} - '@babel/helper-validator-option@7.24.7': {} + tsup@7.3.0: + resolution: {integrity: sha512-Ja1eaSRrE+QarmATlNO5fse2aOACYMBX+IZRKy1T+gpyH+jXgRrl5l4nHIQJQ1DoDgEjHDTw8cpE085UdBZuWQ==} + engines: {node: '>=18'} + deprecated: Breaking node 16 + hasBin: true + peerDependencies: + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.5.0' + peerDependenciesMeta: + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true - '@babel/helper-wrap-function@7.24.7': - dependencies: - '@babel/helper-function-name': 7.24.7 - '@babel/template': 7.24.7 - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 - transitivePeerDependencies: - - supports-color + tsutils@3.21.0: + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' - '@babel/helpers@7.24.7': - dependencies: - '@babel/template': 7.24.7 - '@babel/types': 7.24.7 + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} - '@babel/highlight@7.24.7': - dependencies: - '@babel/helper-validator-identifier': 7.24.7 - chalk: 2.4.2 - js-tokens: 4.0.0 - picocolors: 1.0.1 + type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} - '@babel/parser@7.24.7': - dependencies: - '@babel/types': 7.24.7 + type-fest@0.16.0: + resolution: {integrity: sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==} + engines: {node: '>=10'} - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + type-fest@0.18.1: + resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} + engines: {node: '>=10'} - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 - '@babel/plugin-transform-optional-chaining': 7.24.7(@babel/core@7.24.7) - transitivePeerDependencies: - - supports-color + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + type-fest@0.6.0: + resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} + engines: {node: '>=8'} - '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 + type-fest@0.8.1: + resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} + engines: {node: '>=8'} - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + type-fest@1.4.0: + resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} + engines: {node: '>=10'} - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + type-fest@2.19.0: + resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} + engines: {node: '>=12.20'} - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + typed-array-buffer@1.0.2: + resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} + engines: {node: '>= 0.4'} - '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + typed-array-byte-length@1.0.1: + resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==} + engines: {node: '>= 0.4'} - '@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + typed-array-byte-offset@1.0.2: + resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==} + engines: {node: '>= 0.4'} - '@babel/plugin-syntax-flow@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + typed-array-length@1.0.6: + resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} + engines: {node: '>= 0.4'} - '@babel/plugin-syntax-import-assertions@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - '@babel/plugin-syntax-import-attributes@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + typescript-plugin-css-modules@5.1.0: + resolution: {integrity: sha512-6h+sLBa4l+XYSTn/31vZHd/1c3SvAbLpobY6FxDiUOHJQG1eD9Gh3eCs12+Eqc+TCOAdxcO+zAPvUq0jBfdciw==} + peerDependencies: + typescript: '>=4.0.0' - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + typescript@4.9.5: + resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} + engines: {node: '>=4.2.0'} + hasBin: true - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + ufo@1.5.3: + resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==} - '@babel/plugin-syntax-jsx@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + uglify-js@3.18.0: + resolution: {integrity: sha512-SyVVbcNBCk0dzr9XL/R/ySrmYf0s372K6/hFklzgcp2lBFyXtw4I7BOdDjlLhE1aVqaI/SHWXWmYdlZxuyF38A==} + engines: {node: '>=0.8.0'} + hasBin: true - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + unbox-primitive@1.0.2: + resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + unicode-canonical-property-names-ecmascript@2.0.0: + resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} + engines: {node: '>=4'} - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + unicode-match-property-ecmascript@2.0.0: + resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} + engines: {node: '>=4'} - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + unicode-match-property-value-ecmascript@2.1.0: + resolution: {integrity: sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==} + engines: {node: '>=4'} - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + unicode-property-aliases-ecmascript@2.1.0: + resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==} + engines: {node: '>=4'} - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + unique-string@2.0.0: + resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} + engines: {node: '>=8'} - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + unist-util-is@4.1.0: + resolution: {integrity: sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==} - '@babel/plugin-syntax-typescript@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + unist-util-visit-parents@3.1.1: + resolution: {integrity: sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==} - '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-create-regexp-features-plugin': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 + unist-util-visit@2.0.3: + resolution: {integrity: sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==} - '@babel/plugin-transform-arrow-functions@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} - '@babel/plugin-transform-async-generator-functions@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-remap-async-to-generator': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.7) - transitivePeerDependencies: - - supports-color + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} - '@babel/plugin-transform-async-to-generator@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-module-imports': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-remap-async-to-generator': 7.24.7(@babel/core@7.24.7) - transitivePeerDependencies: - - supports-color + unplugin@1.10.1: + resolution: {integrity: sha512-d6Mhq8RJeGA8UfKCu54Um4lFA0eSaRa3XxdAJg8tIdxbu1ubW0hBCZUL7yI2uGyYCRndvbK8FLHzqy2XKfeMsg==} + engines: {node: '>=14.0.0'} - '@babel/plugin-transform-block-scoped-functions@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + untildify@4.0.0: + resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} + engines: {node: '>=8'} - '@babel/plugin-transform-block-scoping@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + update-browserslist-db@1.0.16: + resolution: {integrity: sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' - '@babel/plugin-transform-class-properties@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-create-class-features-plugin': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 - transitivePeerDependencies: - - supports-color + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - '@babel/plugin-transform-class-static-block@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-create-class-features-plugin': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.24.7) - transitivePeerDependencies: - - supports-color + use-callback-ref@1.3.2: + resolution: {integrity: sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true - '@babel/plugin-transform-classes@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-annotate-as-pure': 7.24.7 - '@babel/helper-compilation-targets': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-function-name': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-replace-supers': 7.24.7(@babel/core@7.24.7) - '@babel/helper-split-export-declaration': 7.24.7 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color + use-composed-ref@1.3.0: + resolution: {integrity: sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 - '@babel/plugin-transform-computed-properties@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/template': 7.24.7 + use-isomorphic-layout-effect@1.1.2: + resolution: {integrity: sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true - '@babel/plugin-transform-destructuring@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + use-latest@1.2.1: + resolution: {integrity: sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw==} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true - '@babel/plugin-transform-dotall-regex@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-create-regexp-features-plugin': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 + use-resize-observer@9.1.0: + resolution: {integrity: sha512-R25VqO9Wb3asSD4eqtcxk8sJalvIOYBqS8MNZlpDSQ4l4xMQxC/J7Id9HoTqPq8FwULIn0PVW+OAqF2dyYbjow==} + peerDependencies: + react: 16.8.0 - 18 + react-dom: 16.8.0 - 18 - '@babel/plugin-transform-duplicate-keys@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + use-sidecar@1.1.2: + resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.9.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true - '@babel/plugin-transform-dynamic-import@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.24.7) + use-sync-external-store@1.2.2: + resolution: {integrity: sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 - '@babel/plugin-transform-exponentiation-operator@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-builder-binary-assignment-operator-visitor': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - transitivePeerDependencies: - - supports-color + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - '@babel/plugin-transform-export-namespace-from@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.24.7) + util@0.12.5: + resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} - '@babel/plugin-transform-flow-strip-types@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-flow': 7.24.7(@babel/core@7.24.7) + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} - '@babel/plugin-transform-for-of@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 - transitivePeerDependencies: - - supports-color + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true - '@babel/plugin-transform-function-name@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-compilation-targets': 7.24.7 - '@babel/helper-function-name': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true - '@babel/plugin-transform-json-strings@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.7) + v8-to-istanbul@9.2.0: + resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==} + engines: {node: '>=10.12.0'} - '@babel/plugin-transform-literals@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + validate-npm-package-license@3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} - '@babel/plugin-transform-logical-assignment-operators@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.7) + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} - '@babel/plugin-transform-member-expression-literals@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + vaul@0.9.1: + resolution: {integrity: sha512-fAhd7i4RNMinx+WEm6pF3nOl78DFkAazcN04ElLPFF9BMCNGbY/kou8UMhIcicm0rJCNePJP0Yyza60gGOD0Jw==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 - '@babel/plugin-transform-modules-amd@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 - transitivePeerDependencies: - - supports-color + vite-tsconfig-paths@4.3.2: + resolution: {integrity: sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==} + peerDependencies: + vite: '*' + peerDependenciesMeta: + vite: + optional: true - '@babel/plugin-transform-modules-commonjs@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-simple-access': 7.24.7 - transitivePeerDependencies: - - supports-color + vite@4.5.3: + resolution: {integrity: sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==} + engines: {node: ^14.18.0 || >=16.0.0} + hasBin: true + peerDependencies: + '@types/node': '>= 14' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true - '@babel/plugin-transform-modules-systemjs@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-hoist-variables': 7.24.7 - '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-validator-identifier': 7.24.7 - transitivePeerDependencies: - - supports-color + walker@1.0.8: + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} - '@babel/plugin-transform-modules-umd@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 - transitivePeerDependencies: - - supports-color + watchpack@2.4.1: + resolution: {integrity: sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==} + engines: {node: '>=10.13.0'} - '@babel/plugin-transform-named-capturing-groups-regex@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-create-regexp-features-plugin': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 + wcwidth@1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} - '@babel/plugin-transform-new-target@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - '@babel/plugin-transform-nullish-coalescing-operator@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.7) + webidl-conversions@4.0.2: + resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} - '@babel/plugin-transform-numeric-separator@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.7) + webpack-sources@3.2.3: + resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} + engines: {node: '>=10.13.0'} - '@babel/plugin-transform-object-rest-spread@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-compilation-targets': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.7) - '@babel/plugin-transform-parameters': 7.24.7(@babel/core@7.24.7) + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} - '@babel/plugin-transform-object-super@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-replace-supers': 7.24.7(@babel/core@7.24.7) - transitivePeerDependencies: - - supports-color + webpack@5.92.0: + resolution: {integrity: sha512-Bsw2X39MYIgxouNATyVpCNVWBCuUwDgWtN78g6lSdPJRLaQ/PUVm/oXcaRAyY/sMFoKFQrsPeqvTizWtq7QPCA==} + engines: {node: '>=10.13.0'} + hasBin: true + peerDependencies: + webpack-cli: '*' + peerDependenciesMeta: + webpack-cli: + optional: true - '@babel/plugin-transform-optional-catch-binding@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.7) + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - '@babel/plugin-transform-optional-chaining@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.7) - transitivePeerDependencies: - - supports-color + whatwg-url@7.1.0: + resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} - '@babel/plugin-transform-parameters@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + which-boxed-primitive@1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} - '@babel/plugin-transform-private-methods@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-create-class-features-plugin': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 - transitivePeerDependencies: - - supports-color + which-typed-array@1.1.15: + resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} + engines: {node: '>= 0.4'} - '@babel/plugin-transform-private-property-in-object@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-annotate-as-pure': 7.24.7 - '@babel/helper-create-class-features-plugin': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.24.7) - transitivePeerDependencies: - - supports-color + which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true - '@babel/plugin-transform-property-literals@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true - '@babel/plugin-transform-react-jsx-self@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} - '@babel/plugin-transform-react-jsx-source@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} - '@babel/plugin-transform-regenerator@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - regenerator-transform: 0.15.2 + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} - '@babel/plugin-transform-reserved-words@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} - '@babel/plugin-transform-shorthand-properties@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - '@babel/plugin-transform-spread@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 - transitivePeerDependencies: - - supports-color + write-file-atomic@2.4.3: + resolution: {integrity: sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==} - '@babel/plugin-transform-sticky-regex@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + write-file-atomic@4.0.2: + resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - '@babel/plugin-transform-template-literals@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + write-file-atomic@5.0.1: + resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - '@babel/plugin-transform-typeof-symbol@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + ws@6.1.4: + resolution: {integrity: sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true - '@babel/plugin-transform-typescript@7.24.7(@babel/core@7.24.7)': + ws@6.2.3: + resolution: {integrity: sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + ws@7.5.10: + resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + ws@8.17.1: + resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xmlhttprequest-ssl@1.6.3: + resolution: {integrity: sha512-3XfeQE/wNkvrIktn2Kf0869fC0BN6UpydVasGIeSm2B1Llihf7/0UfZM+eCkOw3P7bP4+qPgqhm7ZoxuJtFU0Q==} + engines: {node: '>=0.4.0'} + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + + yaml@2.3.1: + resolution: {integrity: sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==} + engines: {node: '>= 14'} + + yaml@2.4.5: + resolution: {integrity: sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==} + engines: {node: '>= 14'} + hasBin: true + + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + + yeast@0.1.2: + resolution: {integrity: sha512-8HFIh676uyGYP6wP13R/j6OJ/1HwJ46snpvzE7aHAN3Ryqh2yX6Xox2B4CUmTwwOIzlG3Bs7ocsP5dZH/R1Qbg==} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zod@3.23.8: + resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} + +snapshots: + + '@adobe/css-tools@4.3.3': {} + + '@amityco/ts-sdk@6.26.3': + dependencies: + agentkeepalive: 4.5.0 + axios: 1.7.2(debug@4.3.5) + debug: 4.3.5 + hls.js: 1.5.11 + js-base64: 3.7.7 + mitt: 3.0.1 + mqtt: 4.3.8 + object-hash: 3.0.0 + react-native-uuid: 2.0.2 + socket.io-client: 2.2.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + + '@aw-web-design/x-default-browser@1.4.126': + dependencies: + default-browser-id: 3.0.0 + + '@babel/code-frame@7.24.7': + dependencies: + '@babel/highlight': 7.24.7 + picocolors: 1.0.1 + + '@babel/compat-data@7.24.7': {} + + '@babel/core@7.24.7': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.24.7 + '@babel/generator': 7.24.7 + '@babel/helper-compilation-targets': 7.24.7 + '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7) + '@babel/helpers': 7.24.7 + '@babel/parser': 7.24.7 + '@babel/template': 7.24.7 + '@babel/traverse': 7.24.7 + '@babel/types': 7.24.7 + convert-source-map: 2.0.0 + debug: 4.3.5 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.24.7': + dependencies: + '@babel/types': 7.24.7 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 2.5.2 + + '@babel/helper-annotate-as-pure@7.24.7': + dependencies: + '@babel/types': 7.24.7 + + '@babel/helper-builder-binary-assignment-operator-visitor@7.24.7': + dependencies: + '@babel/traverse': 7.24.7 + '@babel/types': 7.24.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-compilation-targets@7.24.7': + dependencies: + '@babel/compat-data': 7.24.7 + '@babel/helper-validator-option': 7.24.7 + browserslist: 4.23.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.24.7(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 '@babel/helper-annotate-as-pure': 7.24.7 - '@babel/helper-create-class-features-plugin': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-typescript': 7.24.7(@babel/core@7.24.7) + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-function-name': 7.24.7 + '@babel/helper-member-expression-to-functions': 7.24.7 + '@babel/helper-optimise-call-expression': 7.24.7 + '@babel/helper-replace-supers': 7.24.7(@babel/core@7.24.7) + '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 + '@babel/helper-split-export-declaration': 7.24.7 + semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-unicode-escapes@7.24.7(@babel/core@7.24.7)': + '@babel/helper-create-regexp-features-plugin@7.24.7(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/helper-annotate-as-pure': 7.24.7 + regexpu-core: 5.3.2 + semver: 6.3.1 - '@babel/plugin-transform-unicode-property-regex@7.24.7(@babel/core@7.24.7)': + '@babel/helper-define-polyfill-provider@0.6.2(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 - '@babel/helper-create-regexp-features-plugin': 7.24.7(@babel/core@7.24.7) + '@babel/helper-compilation-targets': 7.24.7 '@babel/helper-plugin-utils': 7.24.7 + debug: 4.3.5 + lodash.debounce: 4.0.8 + resolve: 1.22.8 + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-unicode-regex@7.24.7(@babel/core@7.24.7)': + '@babel/helper-environment-visitor@7.24.7': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-create-regexp-features-plugin': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 + '@babel/types': 7.24.7 - '@babel/plugin-transform-unicode-sets-regex@7.24.7(@babel/core@7.24.7)': + '@babel/helper-function-name@7.24.7': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-create-regexp-features-plugin': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 + '@babel/template': 7.24.7 + '@babel/types': 7.24.7 - '@babel/preset-env@7.24.7(@babel/core@7.24.7)': + '@babel/helper-hoist-variables@7.24.7': + dependencies: + '@babel/types': 7.24.7 + + '@babel/helper-member-expression-to-functions@7.24.7': + dependencies: + '@babel/traverse': 7.24.7 + '@babel/types': 7.24.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.24.7': + dependencies: + '@babel/traverse': 7.24.7 + '@babel/types': 7.24.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.24.7(@babel/core@7.24.7)': dependencies: - '@babel/compat-data': 7.24.7 '@babel/core': 7.24.7 - '@babel/helper-compilation-targets': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-validator-option': 7.24.7 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.24.7) - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.7) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.24.7) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.24.7) - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.24.7) - '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.24.7) - '@babel/plugin-syntax-import-assertions': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-syntax-import-attributes': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.24.7) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.7) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.7) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.7) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.7) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.7) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.7) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.7) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.24.7) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.24.7) - '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.24.7) - '@babel/plugin-transform-arrow-functions': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-async-generator-functions': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-async-to-generator': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-block-scoped-functions': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-block-scoping': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-class-properties': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-class-static-block': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-classes': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-computed-properties': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-destructuring': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-dotall-regex': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-duplicate-keys': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-dynamic-import': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-exponentiation-operator': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-export-namespace-from': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-for-of': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-function-name': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-json-strings': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-literals': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-logical-assignment-operators': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-member-expression-literals': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-modules-amd': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-modules-commonjs': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-modules-systemjs': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-modules-umd': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-named-capturing-groups-regex': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-new-target': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-nullish-coalescing-operator': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-numeric-separator': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-object-rest-spread': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-object-super': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-optional-catch-binding': 7.24.7(@babel/core@7.24.7) + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-module-imports': 7.24.7 + '@babel/helper-simple-access': 7.24.7 + '@babel/helper-split-export-declaration': 7.24.7 + '@babel/helper-validator-identifier': 7.24.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.24.7': + dependencies: + '@babel/types': 7.24.7 + + '@babel/helper-plugin-utils@7.24.7': {} + + '@babel/helper-remap-async-to-generator@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-annotate-as-pure': 7.24.7 + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-wrap-function': 7.24.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-replace-supers@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-member-expression-to-functions': 7.24.7 + '@babel/helper-optimise-call-expression': 7.24.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-simple-access@7.24.7': + dependencies: + '@babel/traverse': 7.24.7 + '@babel/types': 7.24.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.24.7': + dependencies: + '@babel/traverse': 7.24.7 + '@babel/types': 7.24.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-split-export-declaration@7.24.7': + dependencies: + '@babel/types': 7.24.7 + + '@babel/helper-string-parser@7.24.7': {} + + '@babel/helper-validator-identifier@7.24.7': {} + + '@babel/helper-validator-option@7.24.7': {} + + '@babel/helper-wrap-function@7.24.7': + dependencies: + '@babel/helper-function-name': 7.24.7 + '@babel/template': 7.24.7 + '@babel/traverse': 7.24.7 + '@babel/types': 7.24.7 + transitivePeerDependencies: + - supports-color + + '@babel/helpers@7.24.7': + dependencies: + '@babel/template': 7.24.7 + '@babel/types': 7.24.7 + + '@babel/highlight@7.24.7': + dependencies: + '@babel/helper-validator-identifier': 7.24.7 + chalk: 2.4.2 + js-tokens: 4.0.0 + picocolors: 1.0.1 + + '@babel/parser@7.24.7': + dependencies: + '@babel/types': 7.24.7 + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 '@babel/plugin-transform-optional-chaining': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-parameters': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-private-methods': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-private-property-in-object': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-property-literals': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-regenerator': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-reserved-words': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-shorthand-properties': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-spread': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-sticky-regex': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-template-literals': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-typeof-symbol': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-unicode-escapes': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-unicode-property-regex': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-unicode-regex': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-unicode-sets-regex': 7.24.7(@babel/core@7.24.7) - '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.24.7) - babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.24.7) - babel-plugin-polyfill-corejs3: 0.10.4(@babel/core@7.24.7) - babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.24.7) - core-js-compat: 3.37.1 - semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/preset-flow@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-syntax-flow@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-syntax-import-assertions@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-syntax-import-attributes@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-syntax-jsx@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-syntax-typescript@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-create-regexp-features-plugin': 7.24.7(@babel/core@7.24.7) + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-transform-arrow-functions@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-transform-async-generator-functions@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + '@babel/helper-remap-async-to-generator': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.7) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-async-to-generator@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-module-imports': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + '@babel/helper-remap-async-to-generator': 7.24.7(@babel/core@7.24.7) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-block-scoped-functions@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-transform-block-scoping@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-transform-class-properties@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-create-class-features-plugin': 7.24.7(@babel/core@7.24.7) + '@babel/helper-plugin-utils': 7.24.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-class-static-block@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-create-class-features-plugin': 7.24.7(@babel/core@7.24.7) + '@babel/helper-plugin-utils': 7.24.7 + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.24.7) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-classes@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-annotate-as-pure': 7.24.7 + '@babel/helper-compilation-targets': 7.24.7 + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-function-name': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + '@babel/helper-replace-supers': 7.24.7(@babel/core@7.24.7) + '@babel/helper-split-export-declaration': 7.24.7 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-computed-properties@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + '@babel/template': 7.24.7 + + '@babel/plugin-transform-destructuring@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-transform-dotall-regex@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-create-regexp-features-plugin': 7.24.7(@babel/core@7.24.7) + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-transform-duplicate-keys@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-transform-dynamic-import@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.24.7) + + '@babel/plugin-transform-exponentiation-operator@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-builder-binary-assignment-operator-visitor': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-export-namespace-from@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.24.7) + + '@babel/plugin-transform-flow-strip-types@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + '@babel/plugin-syntax-flow': 7.24.7(@babel/core@7.24.7) + + '@babel/plugin-transform-for-of@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-function-name@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-compilation-targets': 7.24.7 + '@babel/helper-function-name': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-transform-json-strings@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.7) + + '@babel/plugin-transform-literals@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-transform-logical-assignment-operators@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.7) + + '@babel/plugin-transform-member-expression-literals@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-transform-modules-amd@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7) + '@babel/helper-plugin-utils': 7.24.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-commonjs@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7) + '@babel/helper-plugin-utils': 7.24.7 + '@babel/helper-simple-access': 7.24.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-systemjs@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-hoist-variables': 7.24.7 + '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7) + '@babel/helper-plugin-utils': 7.24.7 + '@babel/helper-validator-identifier': 7.24.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-umd@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7) + '@babel/helper-plugin-utils': 7.24.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-named-capturing-groups-regex@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-create-regexp-features-plugin': 7.24.7(@babel/core@7.24.7) + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-transform-new-target@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-transform-nullish-coalescing-operator@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.7) + + '@babel/plugin-transform-numeric-separator@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.7) + + '@babel/plugin-transform-object-rest-spread@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-compilation-targets': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.7) + '@babel/plugin-transform-parameters': 7.24.7(@babel/core@7.24.7) + + '@babel/plugin-transform-object-super@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + '@babel/helper-replace-supers': 7.24.7(@babel/core@7.24.7) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-optional-catch-binding@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.7) + + '@babel/plugin-transform-optional-chaining@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.7) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-parameters@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-transform-private-methods@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-create-class-features-plugin': 7.24.7(@babel/core@7.24.7) + '@babel/helper-plugin-utils': 7.24.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-private-property-in-object@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-annotate-as-pure': 7.24.7 + '@babel/helper-create-class-features-plugin': 7.24.7(@babel/core@7.24.7) + '@babel/helper-plugin-utils': 7.24.7 + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.24.7) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-property-literals@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-transform-react-jsx-self@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-transform-react-jsx-source@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-transform-regenerator@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + regenerator-transform: 0.15.2 + + '@babel/plugin-transform-reserved-words@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-transform-shorthand-properties@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-transform-spread@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-sticky-regex@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-transform-template-literals@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-transform-typeof-symbol@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-transform-typescript@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-annotate-as-pure': 7.24.7 + '@babel/helper-create-class-features-plugin': 7.24.7(@babel/core@7.24.7) + '@babel/helper-plugin-utils': 7.24.7 + '@babel/plugin-syntax-typescript': 7.24.7(@babel/core@7.24.7) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-unicode-escapes@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-transform-unicode-property-regex@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-create-regexp-features-plugin': 7.24.7(@babel/core@7.24.7) + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-transform-unicode-regex@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-create-regexp-features-plugin': 7.24.7(@babel/core@7.24.7) + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-transform-unicode-sets-regex@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-create-regexp-features-plugin': 7.24.7(@babel/core@7.24.7) + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/preset-env@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/compat-data': 7.24.7 + '@babel/core': 7.24.7 + '@babel/helper-compilation-targets': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + '@babel/helper-validator-option': 7.24.7 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.24.7) + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.7) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.24.7) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.24.7) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.24.7) + '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.24.7) + '@babel/plugin-syntax-import-assertions': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-syntax-import-attributes': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.24.7) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.7) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.7) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.7) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.7) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.7) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.7) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.7) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.24.7) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.24.7) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.24.7) + '@babel/plugin-transform-arrow-functions': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-async-generator-functions': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-async-to-generator': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-block-scoped-functions': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-block-scoping': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-class-properties': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-class-static-block': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-classes': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-computed-properties': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-destructuring': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-dotall-regex': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-duplicate-keys': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-dynamic-import': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-exponentiation-operator': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-export-namespace-from': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-for-of': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-function-name': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-json-strings': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-literals': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-logical-assignment-operators': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-member-expression-literals': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-modules-amd': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-modules-commonjs': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-modules-systemjs': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-modules-umd': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-named-capturing-groups-regex': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-new-target': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-nullish-coalescing-operator': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-numeric-separator': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-object-rest-spread': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-object-super': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-optional-catch-binding': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-optional-chaining': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-parameters': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-private-methods': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-private-property-in-object': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-property-literals': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-regenerator': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-reserved-words': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-shorthand-properties': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-spread': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-sticky-regex': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-template-literals': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-typeof-symbol': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-unicode-escapes': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-unicode-property-regex': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-unicode-regex': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-unicode-sets-regex': 7.24.7(@babel/core@7.24.7) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.24.7) + babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.24.7) + babel-plugin-polyfill-corejs3: 0.10.4(@babel/core@7.24.7) + babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.24.7) + core-js-compat: 3.37.1 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/preset-flow@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + '@babel/helper-validator-option': 7.24.7 + '@babel/plugin-transform-flow-strip-types': 7.24.7(@babel/core@7.24.7) + + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + '@babel/types': 7.24.7 + esutils: 2.0.3 + + '@babel/preset-typescript@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + '@babel/helper-validator-option': 7.24.7 + '@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-modules-commonjs': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-typescript': 7.24.7(@babel/core@7.24.7) + transitivePeerDependencies: + - supports-color + + '@babel/register@7.24.6(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + clone-deep: 4.0.1 + find-cache-dir: 2.1.0 + make-dir: 2.1.0 + pirates: 4.0.6 + source-map-support: 0.5.21 + + '@babel/regjsgen@0.8.0': {} + + '@babel/runtime@7.24.7': + dependencies: + regenerator-runtime: 0.14.1 + + '@babel/runtime@7.4.5': + dependencies: + regenerator-runtime: 0.13.11 + + '@babel/template@7.24.7': + dependencies: + '@babel/code-frame': 7.24.7 + '@babel/parser': 7.24.7 + '@babel/types': 7.24.7 + + '@babel/traverse@7.24.7': + dependencies: + '@babel/code-frame': 7.24.7 + '@babel/generator': 7.24.7 + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-function-name': 7.24.7 + '@babel/helper-hoist-variables': 7.24.7 + '@babel/helper-split-export-declaration': 7.24.7 + '@babel/parser': 7.24.7 + '@babel/types': 7.24.7 + debug: 4.3.5 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.24.7': + dependencies: + '@babel/helper-string-parser': 7.24.7 + '@babel/helper-validator-identifier': 7.24.7 + to-fast-properties: 2.0.0 + + '@base2/pretty-print-object@1.0.1': {} + + '@bcoe/v8-coverage@0.2.3': {} + + '@colors/colors@1.5.0': + optional: true + + '@csstools/css-parser-algorithms@2.6.3(@csstools/css-tokenizer@2.3.1)': + dependencies: + '@csstools/css-tokenizer': 2.3.1 + + '@csstools/css-tokenizer@2.3.1': {} + + '@csstools/media-query-list-parser@2.1.11(@csstools/css-parser-algorithms@2.6.3(@csstools/css-tokenizer@2.3.1))(@csstools/css-tokenizer@2.3.1)': + dependencies: + '@csstools/css-parser-algorithms': 2.6.3(@csstools/css-tokenizer@2.3.1) + '@csstools/css-tokenizer': 2.3.1 + + '@csstools/selector-specificity@3.1.1(postcss-selector-parser@6.1.0)': + dependencies: + postcss-selector-parser: 6.1.0 + + '@discoveryjs/json-ext@0.5.7': {} + + '@dual-bundle/import-meta-resolve@4.1.0': {} + + '@emotion/is-prop-valid@1.2.2': + dependencies: + '@emotion/memoize': 0.8.1 + + '@emotion/memoize@0.8.1': {} + + '@emotion/unitless@0.8.1': {} + + '@emotion/use-insertion-effect-with-fallbacks@1.0.1(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@esbuild/aix-ppc64@0.19.12': + optional: true + + '@esbuild/android-arm64@0.18.20': + optional: true + + '@esbuild/android-arm64@0.19.12': + optional: true + + '@esbuild/android-arm@0.18.20': + optional: true + + '@esbuild/android-arm@0.19.12': + optional: true + + '@esbuild/android-x64@0.18.20': + optional: true + + '@esbuild/android-x64@0.19.12': + optional: true + + '@esbuild/darwin-arm64@0.18.20': + optional: true + + '@esbuild/darwin-arm64@0.19.12': + optional: true + + '@esbuild/darwin-x64@0.18.20': + optional: true + + '@esbuild/darwin-x64@0.19.12': + optional: true + + '@esbuild/freebsd-arm64@0.18.20': + optional: true + + '@esbuild/freebsd-arm64@0.19.12': + optional: true + + '@esbuild/freebsd-x64@0.18.20': + optional: true + + '@esbuild/freebsd-x64@0.19.12': + optional: true + + '@esbuild/linux-arm64@0.18.20': + optional: true + + '@esbuild/linux-arm64@0.19.12': + optional: true + + '@esbuild/linux-arm@0.18.20': + optional: true + + '@esbuild/linux-arm@0.19.12': + optional: true + + '@esbuild/linux-ia32@0.18.20': + optional: true + + '@esbuild/linux-ia32@0.19.12': + optional: true + + '@esbuild/linux-loong64@0.18.20': + optional: true + + '@esbuild/linux-loong64@0.19.12': + optional: true + + '@esbuild/linux-mips64el@0.18.20': + optional: true + + '@esbuild/linux-mips64el@0.19.12': + optional: true + + '@esbuild/linux-ppc64@0.18.20': + optional: true + + '@esbuild/linux-ppc64@0.19.12': + optional: true + + '@esbuild/linux-riscv64@0.18.20': + optional: true + + '@esbuild/linux-riscv64@0.19.12': + optional: true + + '@esbuild/linux-s390x@0.18.20': + optional: true + + '@esbuild/linux-s390x@0.19.12': + optional: true + + '@esbuild/linux-x64@0.18.20': + optional: true + + '@esbuild/linux-x64@0.19.12': + optional: true + + '@esbuild/netbsd-x64@0.18.20': + optional: true + + '@esbuild/netbsd-x64@0.19.12': + optional: true + + '@esbuild/openbsd-x64@0.18.20': + optional: true + + '@esbuild/openbsd-x64@0.19.12': + optional: true + + '@esbuild/sunos-x64@0.18.20': + optional: true + + '@esbuild/sunos-x64@0.19.12': + optional: true + + '@esbuild/win32-arm64@0.18.20': + optional: true + + '@esbuild/win32-arm64@0.19.12': + optional: true + + '@esbuild/win32-ia32@0.18.20': + optional: true + + '@esbuild/win32-ia32@0.19.12': + optional: true + + '@esbuild/win32-x64@0.18.20': + optional: true + + '@esbuild/win32-x64@0.19.12': + optional: true + + '@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)': + dependencies: + eslint: 8.57.0 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.10.1': {} + + '@eslint/eslintrc@2.1.4': + dependencies: + ajv: 6.12.6 + debug: 4.3.5 + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.1 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@8.57.0': {} + + '@fal-works/esbuild-plugin-global-externals@2.1.2': {} + + '@floating-ui/core@1.6.2': + dependencies: + '@floating-ui/utils': 0.2.2 + + '@floating-ui/dom@1.6.5': + dependencies: + '@floating-ui/core': 1.6.2 + '@floating-ui/utils': 0.2.2 + + '@floating-ui/react-dom@2.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/dom': 1.6.5 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@floating-ui/utils@0.2.2': {} + + '@formatjs/ecma402-abstract@2.0.0': + dependencies: + '@formatjs/intl-localematcher': 0.5.4 + tslib: 2.6.3 + + '@formatjs/fast-memoize@2.2.0': + dependencies: + tslib: 2.6.3 + + '@formatjs/icu-messageformat-parser@2.7.8': + dependencies: + '@formatjs/ecma402-abstract': 2.0.0 + '@formatjs/icu-skeleton-parser': 1.8.2 + tslib: 2.6.3 + + '@formatjs/icu-skeleton-parser@1.8.2': + dependencies: + '@formatjs/ecma402-abstract': 2.0.0 + tslib: 2.6.3 + + '@formatjs/intl-displaynames@6.6.8': + dependencies: + '@formatjs/ecma402-abstract': 2.0.0 + '@formatjs/intl-localematcher': 0.5.4 + tslib: 2.6.3 + + '@formatjs/intl-listformat@7.5.7': + dependencies: + '@formatjs/ecma402-abstract': 2.0.0 + '@formatjs/intl-localematcher': 0.5.4 + tslib: 2.6.3 + + '@formatjs/intl-localematcher@0.5.4': + dependencies: + tslib: 2.6.3 + + '@formatjs/intl@2.10.4(typescript@4.9.5)': + dependencies: + '@formatjs/ecma402-abstract': 2.0.0 + '@formatjs/fast-memoize': 2.2.0 + '@formatjs/icu-messageformat-parser': 2.7.8 + '@formatjs/intl-displaynames': 6.6.8 + '@formatjs/intl-listformat': 7.5.7 + intl-messageformat: 10.5.14 + tslib: 2.6.3 + optionalDependencies: + typescript: 4.9.5 + + '@fortawesome/fontawesome-common-types@0.2.36': {} + + '@fortawesome/fontawesome-common-types@0.3.0': {} + + '@fortawesome/fontawesome-svg-core@1.3.0': + dependencies: + '@fortawesome/fontawesome-common-types': 0.3.0 + + '@fortawesome/free-solid-svg-icons@5.15.4': + dependencies: + '@fortawesome/fontawesome-common-types': 0.2.36 + + '@fortawesome/react-fontawesome@0.2.2(@fortawesome/fontawesome-svg-core@1.3.0)(react@18.3.1)': + dependencies: + '@fortawesome/fontawesome-svg-core': 1.3.0 + prop-types: 15.8.1 + react: 18.3.1 + + '@hookform/error-message@2.0.1(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.52.0(react@18.3.1))(react@18.3.1)': + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-hook-form: 7.52.0(react@18.3.1) + + '@hookform/resolvers@3.6.0(react-hook-form@7.52.0(react@18.3.1))': + dependencies: + react-hook-form: 7.52.0(react@18.3.1) + + '@humanwhocodes/config-array@0.11.14': + dependencies: + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.3.5 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/object-schema@2.0.3': {} + + '@hutson/parse-repository-url@3.0.2': {} + + '@internationalized/date@3.5.4': + dependencies: + '@swc/helpers': 0.5.11 + + '@internationalized/message@3.1.4': + dependencies: + '@swc/helpers': 0.5.11 + intl-messageformat: 10.5.14 + + '@internationalized/number@3.5.3': + dependencies: + '@swc/helpers': 0.5.11 + + '@internationalized/string@3.2.3': + dependencies: + '@swc/helpers': 0.5.11 + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@istanbuljs/load-nyc-config@1.1.0': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-validator-option': 7.24.7 - '@babel/plugin-transform-flow-strip-types': 7.24.7(@babel/core@7.24.7) + camelcase: 5.3.1 + find-up: 4.1.0 + get-package-type: 0.1.0 + js-yaml: 3.14.1 + resolve-from: 5.0.0 - '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.24.7)': + '@istanbuljs/schema@0.1.3': {} + + '@jest/console@29.7.0': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/types': 7.24.7 - esutils: 2.0.3 + '@jest/types': 29.6.3 + '@types/node': 20.14.4 + chalk: 4.1.2 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 - '@babel/preset-typescript@7.24.7(@babel/core@7.24.7)': + '@jest/core@29.7.0': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-validator-option': 7.24.7 - '@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-modules-commonjs': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-typescript': 7.24.7(@babel/core@7.24.7) + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.14.4 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@20.14.4) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.7 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 transitivePeerDependencies: + - babel-plugin-macros - supports-color + - ts-node - '@babel/register@7.24.6(@babel/core@7.24.7)': + '@jest/environment@29.7.0': dependencies: - '@babel/core': 7.24.7 - clone-deep: 4.0.1 - find-cache-dir: 2.1.0 - make-dir: 2.1.0 - pirates: 4.0.6 - source-map-support: 0.5.21 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.14.4 + jest-mock: 29.7.0 - '@babel/regjsgen@0.8.0': {} + '@jest/expect-utils@29.7.0': + dependencies: + jest-get-type: 29.6.3 - '@babel/runtime@7.24.7': + '@jest/expect@29.7.0': dependencies: - regenerator-runtime: 0.14.1 + expect: 29.7.0 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color - '@babel/runtime@7.4.5': + '@jest/fake-timers@29.7.0': dependencies: - regenerator-runtime: 0.13.11 + '@jest/types': 29.6.3 + '@sinonjs/fake-timers': 10.3.0 + '@types/node': 20.14.4 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 - '@babel/template@7.24.7': + '@jest/globals@29.7.0': dependencies: - '@babel/code-frame': 7.24.7 - '@babel/parser': 7.24.7 - '@babel/types': 7.24.7 + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/types': 29.6.3 + jest-mock: 29.7.0 + transitivePeerDependencies: + - supports-color - '@babel/traverse@7.24.7': + '@jest/reporters@29.7.0': dependencies: - '@babel/code-frame': 7.24.7 - '@babel/generator': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-function-name': 7.24.7 - '@babel/helper-hoist-variables': 7.24.7 - '@babel/helper-split-export-declaration': 7.24.7 - '@babel/parser': 7.24.7 - '@babel/types': 7.24.7 - debug: 4.3.5 - globals: 11.12.0 + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.25 + '@types/node': 20.14.4 + chalk: 4.1.2 + collect-v8-coverage: 1.0.2 + exit: 0.1.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-instrument: 6.0.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 4.0.1 + istanbul-reports: 3.1.7 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + jest-worker: 29.7.0 + slash: 3.0.0 + string-length: 4.0.2 + strip-ansi: 6.0.1 + v8-to-istanbul: 9.2.0 transitivePeerDependencies: - supports-color - '@babel/types@7.24.7': + '@jest/schemas@29.6.3': dependencies: - '@babel/helper-string-parser': 7.24.7 - '@babel/helper-validator-identifier': 7.24.7 - to-fast-properties: 2.0.0 + '@sinclair/typebox': 0.27.8 - '@base2/pretty-print-object@1.0.1': {} + '@jest/source-map@29.6.3': + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + callsites: 3.1.0 + graceful-fs: 4.2.11 - '@bcoe/v8-coverage@0.2.3': {} + '@jest/test-result@29.7.0': + dependencies: + '@jest/console': 29.7.0 + '@jest/types': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + collect-v8-coverage: 1.0.2 - '@colors/colors@1.5.0': - optional: true + '@jest/test-sequencer@29.7.0': + dependencies: + '@jest/test-result': 29.7.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + slash: 3.0.0 - '@csstools/css-parser-algorithms@2.6.3(@csstools/css-tokenizer@2.3.1)': + '@jest/transform@29.7.0': dependencies: - '@csstools/css-tokenizer': 2.3.1 + '@babel/core': 7.24.7 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.25 + babel-plugin-istanbul: 6.1.1 + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + micromatch: 4.0.7 + pirates: 4.0.6 + slash: 3.0.0 + write-file-atomic: 4.0.2 + transitivePeerDependencies: + - supports-color - '@csstools/css-tokenizer@2.3.1': {} + '@jest/types@29.6.3': + dependencies: + '@jest/schemas': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 20.14.4 + '@types/yargs': 17.0.32 + chalk: 4.1.2 - '@csstools/media-query-list-parser@2.1.11(@csstools/css-parser-algorithms@2.6.3(@csstools/css-tokenizer@2.3.1))(@csstools/css-tokenizer@2.3.1)': + '@joshwooding/vite-plugin-react-docgen-typescript@0.3.0(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1))': dependencies: - '@csstools/css-parser-algorithms': 2.6.3(@csstools/css-tokenizer@2.3.1) - '@csstools/css-tokenizer': 2.3.1 + glob: 7.2.3 + glob-promise: 4.2.2(glob@7.2.3) + magic-string: 0.27.0 + react-docgen-typescript: 2.2.2(typescript@4.9.5) + vite: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1) + optionalDependencies: + typescript: 4.9.5 - '@csstools/selector-specificity@3.1.1(postcss-selector-parser@6.1.0)': + '@jridgewell/gen-mapping@0.3.5': dependencies: - postcss-selector-parser: 6.1.0 + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.25 - '@discoveryjs/json-ext@0.5.7': {} + '@jridgewell/resolve-uri@3.1.2': {} - '@dual-bundle/import-meta-resolve@4.1.0': {} + '@jridgewell/set-array@1.2.1': {} - '@emotion/is-prop-valid@1.2.2': + '@jridgewell/source-map@0.3.6': dependencies: - '@emotion/memoize': 0.8.1 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 - '@emotion/memoize@0.8.1': {} + '@jridgewell/sourcemap-codec@1.4.15': {} - '@emotion/unitless@0.8.1': {} + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + + '@juggle/resize-observer@3.4.0': {} + + '@mdx-js/react@2.3.0(react@18.3.1)': + dependencies: + '@types/mdx': 2.0.13 + '@types/react': 17.0.80 + react: 18.3.1 + + '@ndelangen/get-tarball@3.0.9': + dependencies: + gunzip-maybe: 1.4.2 + pump: 3.0.0 + tar-fs: 2.1.1 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} - '@emotion/use-insertion-effect-with-fallbacks@1.0.1(react@18.3.1)': + '@nodelib/fs.walk@1.2.8': dependencies: - react: 18.3.1 - - '@esbuild/aix-ppc64@0.19.12': - optional: true + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 - '@esbuild/android-arm64@0.18.20': + '@pkgjs/parseargs@0.11.0': optional: true - '@esbuild/android-arm64@0.19.12': - optional: true + '@radix-ui/number@1.0.1': + dependencies: + '@babel/runtime': 7.24.7 - '@esbuild/android-arm@0.18.20': - optional: true + '@radix-ui/primitive@1.0.1': + dependencies: + '@babel/runtime': 7.24.7 - '@esbuild/android-arm@0.19.12': - optional: true + '@radix-ui/react-arrow@1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 17.0.80 - '@esbuild/android-x64@0.18.20': - optional: true + '@radix-ui/react-collection@1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-context': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.0.2(@types/react@17.0.80)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 17.0.80 - '@esbuild/android-x64@0.19.12': - optional: true + '@radix-ui/react-compose-refs@1.0.1(@types/react@17.0.80)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + react: 18.3.1 + optionalDependencies: + '@types/react': 17.0.80 - '@esbuild/darwin-arm64@0.18.20': - optional: true + '@radix-ui/react-context@1.0.1(@types/react@17.0.80)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + react: 18.3.1 + optionalDependencies: + '@types/react': 17.0.80 - '@esbuild/darwin-arm64@0.19.12': - optional: true + '@radix-ui/react-dialog@1.0.5(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-context': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.0.5(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-portal': 1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.0.1(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.0.2(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@17.0.80)(react@18.3.1) + aria-hidden: 1.2.4 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.5.5(@types/react@17.0.80)(react@18.3.1) + optionalDependencies: + '@types/react': 17.0.80 - '@esbuild/darwin-x64@0.18.20': - optional: true + '@radix-ui/react-direction@1.0.1(@types/react@17.0.80)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + react: 18.3.1 + optionalDependencies: + '@types/react': 17.0.80 - '@esbuild/darwin-x64@0.19.12': - optional: true + '@radix-ui/react-dismissable-layer@1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-use-escape-keydown': 1.0.3(@types/react@17.0.80)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 17.0.80 - '@esbuild/freebsd-arm64@0.18.20': - optional: true + '@radix-ui/react-dismissable-layer@1.0.5(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-use-escape-keydown': 1.0.3(@types/react@17.0.80)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 17.0.80 - '@esbuild/freebsd-arm64@0.19.12': - optional: true + '@radix-ui/react-focus-guards@1.0.1(@types/react@17.0.80)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + react: 18.3.1 + optionalDependencies: + '@types/react': 17.0.80 - '@esbuild/freebsd-x64@0.18.20': - optional: true + '@radix-ui/react-focus-scope@1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.80)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 17.0.80 - '@esbuild/freebsd-x64@0.19.12': - optional: true + '@radix-ui/react-focus-scope@1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.80)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 17.0.80 - '@esbuild/linux-arm64@0.18.20': - optional: true + '@radix-ui/react-id@1.0.1(@types/react@17.0.80)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@17.0.80)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 17.0.80 - '@esbuild/linux-arm64@0.19.12': - optional: true + '@radix-ui/react-popper@1.1.2(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@floating-ui/react-dom': 2.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-arrow': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-context': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-use-rect': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-use-size': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/rect': 1.0.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 17.0.80 - '@esbuild/linux-arm@0.18.20': - optional: true + '@radix-ui/react-portal@1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 17.0.80 - '@esbuild/linux-arm@0.19.12': - optional: true + '@radix-ui/react-portal@1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 17.0.80 - '@esbuild/linux-ia32@0.18.20': - optional: true + '@radix-ui/react-presence@1.0.1(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@17.0.80)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 17.0.80 - '@esbuild/linux-ia32@0.19.12': - optional: true + '@radix-ui/react-primitive@1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@radix-ui/react-slot': 1.0.2(@types/react@17.0.80)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 17.0.80 - '@esbuild/linux-loong64@0.18.20': - optional: true + '@radix-ui/react-roving-focus@1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-collection': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-context': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-direction': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-id': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@17.0.80)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 17.0.80 - '@esbuild/linux-loong64@0.19.12': - optional: true + '@radix-ui/react-select@1.2.2(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@radix-ui/number': 1.0.1 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-collection': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-context': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-direction': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-popper': 1.1.2(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.0.2(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-use-previous': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + aria-hidden: 1.2.4 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.5.5(@types/react@17.0.80)(react@18.3.1) + optionalDependencies: + '@types/react': 17.0.80 - '@esbuild/linux-mips64el@0.18.20': - optional: true + '@radix-ui/react-separator@1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 17.0.80 - '@esbuild/linux-mips64el@0.19.12': - optional: true + '@radix-ui/react-slot@1.0.2(@types/react@17.0.80)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 17.0.80 - '@esbuild/linux-ppc64@0.18.20': - optional: true + '@radix-ui/react-tabs@1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-context': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-direction': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-id': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-presence': 1.0.1(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@17.0.80)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 17.0.80 - '@esbuild/linux-ppc64@0.19.12': - optional: true + '@radix-ui/react-toggle-group@1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-context': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-direction': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-toggle': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@17.0.80)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 17.0.80 - '@esbuild/linux-riscv64@0.18.20': - optional: true + '@radix-ui/react-toggle@1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@17.0.80)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 17.0.80 - '@esbuild/linux-riscv64@0.19.12': - optional: true + '@radix-ui/react-toolbar@1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-context': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-direction': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-separator': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-toggle-group': 1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 17.0.80 - '@esbuild/linux-s390x@0.18.20': - optional: true + '@radix-ui/react-use-callback-ref@1.0.1(@types/react@17.0.80)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + react: 18.3.1 + optionalDependencies: + '@types/react': 17.0.80 - '@esbuild/linux-s390x@0.19.12': - optional: true + '@radix-ui/react-use-controllable-state@1.0.1(@types/react@17.0.80)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.80)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 17.0.80 - '@esbuild/linux-x64@0.18.20': - optional: true + '@radix-ui/react-use-escape-keydown@1.0.3(@types/react@17.0.80)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.80)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 17.0.80 - '@esbuild/linux-x64@0.19.12': - optional: true + '@radix-ui/react-use-layout-effect@1.0.1(@types/react@17.0.80)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + react: 18.3.1 + optionalDependencies: + '@types/react': 17.0.80 - '@esbuild/netbsd-x64@0.18.20': - optional: true + '@radix-ui/react-use-previous@1.0.1(@types/react@17.0.80)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + react: 18.3.1 + optionalDependencies: + '@types/react': 17.0.80 - '@esbuild/netbsd-x64@0.19.12': - optional: true + '@radix-ui/react-use-rect@1.0.1(@types/react@17.0.80)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@radix-ui/rect': 1.0.1 + react: 18.3.1 + optionalDependencies: + '@types/react': 17.0.80 - '@esbuild/openbsd-x64@0.18.20': - optional: true + '@radix-ui/react-use-size@1.0.1(@types/react@17.0.80)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@17.0.80)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 17.0.80 - '@esbuild/openbsd-x64@0.19.12': - optional: true + '@radix-ui/react-visually-hidden@1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 17.0.80 - '@esbuild/sunos-x64@0.18.20': - optional: true + '@radix-ui/rect@1.0.1': + dependencies: + '@babel/runtime': 7.24.7 - '@esbuild/sunos-x64@0.19.12': - optional: true + '@react-aria/breadcrumbs@3.5.13(react@18.3.1)': + dependencies: + '@react-aria/i18n': 3.11.1(react@18.3.1) + '@react-aria/link': 3.7.1(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-types/breadcrumbs': 3.7.5(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 - '@esbuild/win32-arm64@0.18.20': - optional: true + '@react-aria/button@3.9.5(react@18.3.1)': + dependencies: + '@react-aria/focus': 3.17.1(react@18.3.1) + '@react-aria/interactions': 3.21.3(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-stately/toggle': 3.7.4(react@18.3.1) + '@react-types/button': 3.9.4(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 - '@esbuild/win32-arm64@0.19.12': - optional: true + '@react-aria/calendar@3.5.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@internationalized/date': 3.5.4 + '@react-aria/i18n': 3.11.1(react@18.3.1) + '@react-aria/interactions': 3.21.3(react@18.3.1) + '@react-aria/live-announcer': 3.3.4 + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-stately/calendar': 3.5.1(react@18.3.1) + '@react-types/button': 3.9.4(react@18.3.1) + '@react-types/calendar': 3.4.6(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) - '@esbuild/win32-ia32@0.18.20': - optional: true + '@react-aria/checkbox@3.14.3(react@18.3.1)': + dependencies: + '@react-aria/form': 3.0.5(react@18.3.1) + '@react-aria/interactions': 3.21.3(react@18.3.1) + '@react-aria/label': 3.7.8(react@18.3.1) + '@react-aria/toggle': 3.10.4(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-stately/checkbox': 3.6.5(react@18.3.1) + '@react-stately/form': 3.0.3(react@18.3.1) + '@react-stately/toggle': 3.7.4(react@18.3.1) + '@react-types/checkbox': 3.8.1(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 - '@esbuild/win32-ia32@0.19.12': - optional: true + '@react-aria/color@3.0.0-beta.33(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@react-aria/i18n': 3.11.1(react@18.3.1) + '@react-aria/interactions': 3.21.3(react@18.3.1) + '@react-aria/numberfield': 3.11.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/slider': 3.7.8(react@18.3.1) + '@react-aria/spinbutton': 3.6.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/textfield': 3.14.5(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-aria/visually-hidden': 3.8.12(react@18.3.1) + '@react-stately/color': 3.6.1(react@18.3.1) + '@react-stately/form': 3.0.3(react@18.3.1) + '@react-types/color': 3.0.0-beta.25(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) - '@esbuild/win32-x64@0.18.20': - optional: true + '@react-aria/combobox@3.9.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@react-aria/i18n': 3.11.1(react@18.3.1) + '@react-aria/listbox': 3.12.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/live-announcer': 3.3.4 + '@react-aria/menu': 3.14.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/overlays': 3.22.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/selection': 3.18.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/textfield': 3.14.5(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-stately/collections': 3.10.7(react@18.3.1) + '@react-stately/combobox': 3.8.4(react@18.3.1) + '@react-stately/form': 3.0.3(react@18.3.1) + '@react-types/button': 3.9.4(react@18.3.1) + '@react-types/combobox': 3.11.1(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) - '@esbuild/win32-x64@0.19.12': - optional: true + '@react-aria/datepicker@3.10.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@internationalized/date': 3.5.4 + '@internationalized/number': 3.5.3 + '@internationalized/string': 3.2.3 + '@react-aria/focus': 3.17.1(react@18.3.1) + '@react-aria/form': 3.0.5(react@18.3.1) + '@react-aria/i18n': 3.11.1(react@18.3.1) + '@react-aria/interactions': 3.21.3(react@18.3.1) + '@react-aria/label': 3.7.8(react@18.3.1) + '@react-aria/spinbutton': 3.6.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-stately/datepicker': 3.9.4(react@18.3.1) + '@react-stately/form': 3.0.3(react@18.3.1) + '@react-types/button': 3.9.4(react@18.3.1) + '@react-types/calendar': 3.4.6(react@18.3.1) + '@react-types/datepicker': 3.7.4(react@18.3.1) + '@react-types/dialog': 3.5.10(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) - '@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)': + '@react-aria/dialog@3.5.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - eslint: 8.57.0 - eslint-visitor-keys: 3.4.3 - - '@eslint-community/regexpp@4.10.1': {} + '@react-aria/focus': 3.17.1(react@18.3.1) + '@react-aria/overlays': 3.22.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-types/dialog': 3.5.10(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) - '@eslint/eslintrc@2.1.4': + '@react-aria/dnd@3.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - ajv: 6.12.6 - debug: 4.3.5 - espree: 9.6.1 - globals: 13.24.0 - ignore: 5.3.1 - import-fresh: 3.3.0 - js-yaml: 4.1.0 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - - '@eslint/js@8.57.0': {} - - '@fal-works/esbuild-plugin-global-externals@2.1.2': {} + '@internationalized/string': 3.2.3 + '@react-aria/i18n': 3.11.1(react@18.3.1) + '@react-aria/interactions': 3.21.3(react@18.3.1) + '@react-aria/live-announcer': 3.3.4 + '@react-aria/overlays': 3.22.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-stately/dnd': 3.3.1(react@18.3.1) + '@react-types/button': 3.9.4(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) - '@floating-ui/core@1.6.2': + '@react-aria/focus@3.17.1(react@18.3.1)': dependencies: - '@floating-ui/utils': 0.2.2 + '@react-aria/interactions': 3.21.3(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + clsx: 2.1.1 + react: 18.3.1 - '@floating-ui/dom@1.6.5': + '@react-aria/form@3.0.5(react@18.3.1)': dependencies: - '@floating-ui/core': 1.6.2 - '@floating-ui/utils': 0.2.2 + '@react-aria/interactions': 3.21.3(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-stately/form': 3.0.3(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 - '@floating-ui/react-dom@2.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@floating-ui/dom': 1.6.5 + '@react-aria/grid@3.9.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@react-aria/focus': 3.17.1(react@18.3.1) + '@react-aria/i18n': 3.11.1(react@18.3.1) + '@react-aria/interactions': 3.21.3(react@18.3.1) + '@react-aria/live-announcer': 3.3.4 + '@react-aria/selection': 3.18.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-stately/collections': 3.10.7(react@18.3.1) + '@react-stately/grid': 3.8.7(react@18.3.1) + '@react-stately/selection': 3.15.1(react@18.3.1) + '@react-stately/virtualizer': 3.7.1(react@18.3.1) + '@react-types/checkbox': 3.8.1(react@18.3.1) + '@react-types/grid': 3.2.6(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@react-aria/gridlist@3.8.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@react-aria/focus': 3.17.1(react@18.3.1) + '@react-aria/grid': 3.9.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/i18n': 3.11.1(react@18.3.1) + '@react-aria/interactions': 3.21.3(react@18.3.1) + '@react-aria/selection': 3.18.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-stately/collections': 3.10.7(react@18.3.1) + '@react-stately/list': 3.10.5(react@18.3.1) + '@react-stately/tree': 3.8.1(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@floating-ui/utils@0.2.2': {} - - '@formatjs/ecma402-abstract@2.0.0': + '@react-aria/i18n@3.11.1(react@18.3.1)': dependencies: - '@formatjs/intl-localematcher': 0.5.4 - tslib: 2.6.3 + '@internationalized/date': 3.5.4 + '@internationalized/message': 3.1.4 + '@internationalized/number': 3.5.3 + '@internationalized/string': 3.2.3 + '@react-aria/ssr': 3.9.4(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 - '@formatjs/fast-memoize@2.2.0': + '@react-aria/interactions@3.21.3(react@18.3.1)': dependencies: - tslib: 2.6.3 + '@react-aria/ssr': 3.9.4(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 - '@formatjs/icu-messageformat-parser@2.7.8': + '@react-aria/label@3.7.8(react@18.3.1)': dependencies: - '@formatjs/ecma402-abstract': 2.0.0 - '@formatjs/icu-skeleton-parser': 1.8.2 - tslib: 2.6.3 + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 - '@formatjs/icu-skeleton-parser@1.8.2': + '@react-aria/link@3.7.1(react@18.3.1)': dependencies: - '@formatjs/ecma402-abstract': 2.0.0 - tslib: 2.6.3 + '@react-aria/focus': 3.17.1(react@18.3.1) + '@react-aria/interactions': 3.21.3(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-types/link': 3.5.5(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 - '@formatjs/intl-displaynames@6.6.8': + '@react-aria/listbox@3.12.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@formatjs/ecma402-abstract': 2.0.0 - '@formatjs/intl-localematcher': 0.5.4 - tslib: 2.6.3 + '@react-aria/interactions': 3.21.3(react@18.3.1) + '@react-aria/label': 3.7.8(react@18.3.1) + '@react-aria/selection': 3.18.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-stately/collections': 3.10.7(react@18.3.1) + '@react-stately/list': 3.10.5(react@18.3.1) + '@react-types/listbox': 3.4.9(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) - '@formatjs/intl-listformat@7.5.7': + '@react-aria/live-announcer@3.3.4': dependencies: - '@formatjs/ecma402-abstract': 2.0.0 - '@formatjs/intl-localematcher': 0.5.4 - tslib: 2.6.3 + '@swc/helpers': 0.5.11 - '@formatjs/intl-localematcher@0.5.4': - dependencies: - tslib: 2.6.3 + '@react-aria/menu@3.14.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@react-aria/focus': 3.17.1(react@18.3.1) + '@react-aria/i18n': 3.11.1(react@18.3.1) + '@react-aria/interactions': 3.21.3(react@18.3.1) + '@react-aria/overlays': 3.22.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/selection': 3.18.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-stately/collections': 3.10.7(react@18.3.1) + '@react-stately/menu': 3.7.1(react@18.3.1) + '@react-stately/tree': 3.8.1(react@18.3.1) + '@react-types/button': 3.9.4(react@18.3.1) + '@react-types/menu': 3.9.9(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) - '@formatjs/intl@2.10.4(typescript@4.9.5)': + '@react-aria/meter@3.4.13(react@18.3.1)': dependencies: - '@formatjs/ecma402-abstract': 2.0.0 - '@formatjs/fast-memoize': 2.2.0 - '@formatjs/icu-messageformat-parser': 2.7.8 - '@formatjs/intl-displaynames': 6.6.8 - '@formatjs/intl-listformat': 7.5.7 - intl-messageformat: 10.5.14 - tslib: 2.6.3 - optionalDependencies: - typescript: 4.9.5 - - '@fortawesome/fontawesome-common-types@0.2.36': {} + '@react-aria/progress': 3.4.13(react@18.3.1) + '@react-types/meter': 3.4.1(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 - '@fortawesome/fontawesome-common-types@0.3.0': {} + '@react-aria/numberfield@3.11.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@react-aria/i18n': 3.11.1(react@18.3.1) + '@react-aria/interactions': 3.21.3(react@18.3.1) + '@react-aria/spinbutton': 3.6.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/textfield': 3.14.5(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-stately/form': 3.0.3(react@18.3.1) + '@react-stately/numberfield': 3.9.3(react@18.3.1) + '@react-types/button': 3.9.4(react@18.3.1) + '@react-types/numberfield': 3.8.3(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) - '@fortawesome/fontawesome-svg-core@1.3.0': + '@react-aria/overlays@3.22.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@fortawesome/fontawesome-common-types': 0.3.0 + '@react-aria/focus': 3.17.1(react@18.3.1) + '@react-aria/i18n': 3.11.1(react@18.3.1) + '@react-aria/interactions': 3.21.3(react@18.3.1) + '@react-aria/ssr': 3.9.4(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-aria/visually-hidden': 3.8.12(react@18.3.1) + '@react-stately/overlays': 3.6.7(react@18.3.1) + '@react-types/button': 3.9.4(react@18.3.1) + '@react-types/overlays': 3.8.7(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) - '@fortawesome/free-solid-svg-icons@5.15.4': + '@react-aria/progress@3.4.13(react@18.3.1)': dependencies: - '@fortawesome/fontawesome-common-types': 0.2.36 + '@react-aria/i18n': 3.11.1(react@18.3.1) + '@react-aria/label': 3.7.8(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-types/progress': 3.5.4(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 - '@fortawesome/react-fontawesome@0.2.2(@fortawesome/fontawesome-svg-core@1.3.0)(react@18.3.1)': + '@react-aria/radio@3.10.4(react@18.3.1)': dependencies: - '@fortawesome/fontawesome-svg-core': 1.3.0 - prop-types: 15.8.1 + '@react-aria/focus': 3.17.1(react@18.3.1) + '@react-aria/form': 3.0.5(react@18.3.1) + '@react-aria/i18n': 3.11.1(react@18.3.1) + '@react-aria/interactions': 3.21.3(react@18.3.1) + '@react-aria/label': 3.7.8(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-stately/radio': 3.10.4(react@18.3.1) + '@react-types/radio': 3.8.1(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 react: 18.3.1 - '@hookform/error-message@2.0.1(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.52.0(react@18.3.1))(react@18.3.1)': + '@react-aria/searchfield@3.7.5(react@18.3.1)': dependencies: + '@react-aria/i18n': 3.11.1(react@18.3.1) + '@react-aria/textfield': 3.14.5(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-stately/searchfield': 3.5.3(react@18.3.1) + '@react-types/button': 3.9.4(react@18.3.1) + '@react-types/searchfield': 3.5.5(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 + + '@react-aria/select@3.14.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@react-aria/form': 3.0.5(react@18.3.1) + '@react-aria/i18n': 3.11.1(react@18.3.1) + '@react-aria/interactions': 3.21.3(react@18.3.1) + '@react-aria/label': 3.7.8(react@18.3.1) + '@react-aria/listbox': 3.12.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/menu': 3.14.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/selection': 3.18.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-aria/visually-hidden': 3.8.12(react@18.3.1) + '@react-stately/select': 3.6.4(react@18.3.1) + '@react-types/button': 3.9.4(react@18.3.1) + '@react-types/select': 3.9.4(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-hook-form: 7.52.0(react@18.3.1) - '@hookform/resolvers@3.6.0(react-hook-form@7.52.0(react@18.3.1))': + '@react-aria/selection@3.18.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - react-hook-form: 7.52.0(react@18.3.1) + '@react-aria/focus': 3.17.1(react@18.3.1) + '@react-aria/i18n': 3.11.1(react@18.3.1) + '@react-aria/interactions': 3.21.3(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-stately/selection': 3.15.1(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) - '@humanwhocodes/config-array@0.11.14': + '@react-aria/separator@3.3.13(react@18.3.1)': dependencies: - '@humanwhocodes/object-schema': 2.0.3 - debug: 4.3.5 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - - '@humanwhocodes/module-importer@1.0.1': {} - - '@humanwhocodes/object-schema@2.0.3': {} - - '@hutson/parse-repository-url@3.0.2': {} + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 - '@isaacs/cliui@8.0.2': + '@react-aria/slider@3.7.8(react@18.3.1)': dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.0 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 + '@react-aria/focus': 3.17.1(react@18.3.1) + '@react-aria/i18n': 3.11.1(react@18.3.1) + '@react-aria/interactions': 3.21.3(react@18.3.1) + '@react-aria/label': 3.7.8(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-stately/slider': 3.5.4(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@react-types/slider': 3.7.3(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 - '@istanbuljs/load-nyc-config@1.1.0': + '@react-aria/spinbutton@3.6.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - camelcase: 5.3.1 - find-up: 4.1.0 - get-package-type: 0.1.0 - js-yaml: 3.14.1 - resolve-from: 5.0.0 - - '@istanbuljs/schema@0.1.3': {} + '@react-aria/i18n': 3.11.1(react@18.3.1) + '@react-aria/live-announcer': 3.3.4 + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-types/button': 3.9.4(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) - '@jest/console@29.7.0': + '@react-aria/ssr@3.9.4(react@18.3.1)': dependencies: - '@jest/types': 29.6.3 - '@types/node': 20.14.4 - chalk: 4.1.2 - jest-message-util: 29.7.0 - jest-util: 29.7.0 - slash: 3.0.0 + '@swc/helpers': 0.5.11 + react: 18.3.1 - '@jest/core@29.7.0': + '@react-aria/switch@3.6.4(react@18.3.1)': dependencies: - '@jest/console': 29.7.0 - '@jest/reporters': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 20.14.4 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - ci-info: 3.9.0 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.14.4) - jest-haste-map: 29.7.0 - jest-message-util: 29.7.0 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-resolve-dependencies: 29.7.0 - jest-runner: 29.7.0 - jest-runtime: 29.7.0 - jest-snapshot: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - jest-watcher: 29.7.0 - micromatch: 4.0.7 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-ansi: 6.0.1 - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - ts-node + '@react-aria/toggle': 3.10.4(react@18.3.1) + '@react-stately/toggle': 3.7.4(react@18.3.1) + '@react-types/switch': 3.5.3(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 - '@jest/environment@29.7.0': - dependencies: - '@jest/fake-timers': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 20.14.4 - jest-mock: 29.7.0 + '@react-aria/table@3.14.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@react-aria/focus': 3.17.1(react@18.3.1) + '@react-aria/grid': 3.9.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/i18n': 3.11.1(react@18.3.1) + '@react-aria/interactions': 3.21.3(react@18.3.1) + '@react-aria/live-announcer': 3.3.4 + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-aria/visually-hidden': 3.8.12(react@18.3.1) + '@react-stately/collections': 3.10.7(react@18.3.1) + '@react-stately/flags': 3.0.3 + '@react-stately/table': 3.11.8(react@18.3.1) + '@react-stately/virtualizer': 3.7.1(react@18.3.1) + '@react-types/checkbox': 3.8.1(react@18.3.1) + '@react-types/grid': 3.2.6(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@react-types/table': 3.9.5(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) - '@jest/expect-utils@29.7.0': + '@react-aria/tabs@3.9.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - jest-get-type: 29.6.3 + '@react-aria/focus': 3.17.1(react@18.3.1) + '@react-aria/i18n': 3.11.1(react@18.3.1) + '@react-aria/selection': 3.18.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-stately/tabs': 3.6.6(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@react-types/tabs': 3.3.7(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) - '@jest/expect@29.7.0': + '@react-aria/tag@3.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - expect: 29.7.0 - jest-snapshot: 29.7.0 - transitivePeerDependencies: - - supports-color + '@react-aria/gridlist': 3.8.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/i18n': 3.11.1(react@18.3.1) + '@react-aria/interactions': 3.21.3(react@18.3.1) + '@react-aria/label': 3.7.8(react@18.3.1) + '@react-aria/selection': 3.18.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-stately/list': 3.10.5(react@18.3.1) + '@react-types/button': 3.9.4(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) - '@jest/fake-timers@29.7.0': + '@react-aria/textfield@3.14.5(react@18.3.1)': dependencies: - '@jest/types': 29.6.3 - '@sinonjs/fake-timers': 10.3.0 - '@types/node': 20.14.4 - jest-message-util: 29.7.0 - jest-mock: 29.7.0 - jest-util: 29.7.0 + '@react-aria/focus': 3.17.1(react@18.3.1) + '@react-aria/form': 3.0.5(react@18.3.1) + '@react-aria/label': 3.7.8(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-stately/form': 3.0.3(react@18.3.1) + '@react-stately/utils': 3.10.1(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@react-types/textfield': 3.9.3(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 - '@jest/globals@29.7.0': + '@react-aria/toggle@3.10.4(react@18.3.1)': dependencies: - '@jest/environment': 29.7.0 - '@jest/expect': 29.7.0 - '@jest/types': 29.6.3 - jest-mock: 29.7.0 - transitivePeerDependencies: - - supports-color + '@react-aria/focus': 3.17.1(react@18.3.1) + '@react-aria/interactions': 3.21.3(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-stately/toggle': 3.7.4(react@18.3.1) + '@react-types/checkbox': 3.8.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 - '@jest/reporters@29.7.0': - dependencies: - '@bcoe/v8-coverage': 0.2.3 - '@jest/console': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@jridgewell/trace-mapping': 0.3.25 - '@types/node': 20.14.4 - chalk: 4.1.2 - collect-v8-coverage: 1.0.2 - exit: 0.1.2 - glob: 7.2.3 - graceful-fs: 4.2.11 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-instrument: 6.0.2 - istanbul-lib-report: 3.0.1 - istanbul-lib-source-maps: 4.0.1 - istanbul-reports: 3.1.7 - jest-message-util: 29.7.0 - jest-util: 29.7.0 - jest-worker: 29.7.0 - slash: 3.0.0 - string-length: 4.0.2 - strip-ansi: 6.0.1 - v8-to-istanbul: 9.2.0 - transitivePeerDependencies: - - supports-color + '@react-aria/toolbar@3.0.0-beta.5(react@18.3.1)': + dependencies: + '@react-aria/focus': 3.17.1(react@18.3.1) + '@react-aria/i18n': 3.11.1(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 - '@jest/schemas@29.6.3': + '@react-aria/tooltip@3.7.4(react@18.3.1)': dependencies: - '@sinclair/typebox': 0.27.8 + '@react-aria/focus': 3.17.1(react@18.3.1) + '@react-aria/interactions': 3.21.3(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-stately/tooltip': 3.4.9(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@react-types/tooltip': 3.4.9(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 - '@jest/source-map@29.6.3': + '@react-aria/tree@3.0.0-alpha.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@jridgewell/trace-mapping': 0.3.25 - callsites: 3.1.0 - graceful-fs: 4.2.11 + '@react-aria/gridlist': 3.8.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/i18n': 3.11.1(react@18.3.1) + '@react-aria/selection': 3.18.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-stately/tree': 3.8.1(react@18.3.1) + '@react-types/button': 3.9.4(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) - '@jest/test-result@29.7.0': + '@react-aria/utils@3.17.0(react@18.3.1)': dependencies: - '@jest/console': 29.7.0 - '@jest/types': 29.6.3 - '@types/istanbul-lib-coverage': 2.0.6 - collect-v8-coverage: 1.0.2 + '@react-aria/ssr': 3.9.4(react@18.3.1) + '@react-stately/utils': 3.10.1(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.4.36 + clsx: 1.2.1 + react: 18.3.1 - '@jest/test-sequencer@29.7.0': + '@react-aria/utils@3.24.1(react@18.3.1)': dependencies: - '@jest/test-result': 29.7.0 - graceful-fs: 4.2.11 - jest-haste-map: 29.7.0 - slash: 3.0.0 + '@react-aria/ssr': 3.9.4(react@18.3.1) + '@react-stately/utils': 3.10.1(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + clsx: 2.1.1 + react: 18.3.1 - '@jest/transform@29.7.0': + '@react-aria/visually-hidden@3.8.12(react@18.3.1)': dependencies: - '@babel/core': 7.24.7 - '@jest/types': 29.6.3 - '@jridgewell/trace-mapping': 0.3.25 - babel-plugin-istanbul: 6.1.1 - chalk: 4.1.2 - convert-source-map: 2.0.0 - fast-json-stable-stringify: 2.1.0 - graceful-fs: 4.2.11 - jest-haste-map: 29.7.0 - jest-regex-util: 29.6.3 - jest-util: 29.7.0 - micromatch: 4.0.7 - pirates: 4.0.6 - slash: 3.0.0 - write-file-atomic: 4.0.2 - transitivePeerDependencies: - - supports-color + '@react-aria/interactions': 3.21.3(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 - '@jest/types@29.6.3': + '@react-stately/calendar@3.5.1(react@18.3.1)': dependencies: - '@jest/schemas': 29.6.3 - '@types/istanbul-lib-coverage': 2.0.6 - '@types/istanbul-reports': 3.0.4 - '@types/node': 20.14.4 - '@types/yargs': 17.0.32 - chalk: 4.1.2 + '@internationalized/date': 3.5.4 + '@react-stately/utils': 3.10.1(react@18.3.1) + '@react-types/calendar': 3.4.6(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 - '@joshwooding/vite-plugin-react-docgen-typescript@0.3.0(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1))': + '@react-stately/checkbox@3.6.5(react@18.3.1)': dependencies: - glob: 7.2.3 - glob-promise: 4.2.2(glob@7.2.3) - magic-string: 0.27.0 - react-docgen-typescript: 2.2.2(typescript@4.9.5) - vite: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1) - optionalDependencies: - typescript: 4.9.5 + '@react-stately/form': 3.0.3(react@18.3.1) + '@react-stately/utils': 3.10.1(react@18.3.1) + '@react-types/checkbox': 3.8.1(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 - '@jridgewell/gen-mapping@0.3.5': + '@react-stately/collections@3.10.7(react@18.3.1)': dependencies: - '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.4.15 - '@jridgewell/trace-mapping': 0.3.25 + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 - '@jridgewell/resolve-uri@3.1.2': {} + '@react-stately/color@3.6.1(react@18.3.1)': + dependencies: + '@internationalized/number': 3.5.3 + '@internationalized/string': 3.2.3 + '@react-aria/i18n': 3.11.1(react@18.3.1) + '@react-stately/form': 3.0.3(react@18.3.1) + '@react-stately/numberfield': 3.9.3(react@18.3.1) + '@react-stately/slider': 3.5.4(react@18.3.1) + '@react-stately/utils': 3.10.1(react@18.3.1) + '@react-types/color': 3.0.0-beta.25(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 - '@jridgewell/set-array@1.2.1': {} + '@react-stately/combobox@3.8.4(react@18.3.1)': + dependencies: + '@react-stately/collections': 3.10.7(react@18.3.1) + '@react-stately/form': 3.0.3(react@18.3.1) + '@react-stately/list': 3.10.5(react@18.3.1) + '@react-stately/overlays': 3.6.7(react@18.3.1) + '@react-stately/select': 3.6.4(react@18.3.1) + '@react-stately/utils': 3.10.1(react@18.3.1) + '@react-types/combobox': 3.11.1(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 - '@jridgewell/source-map@0.3.6': + '@react-stately/data@3.11.4(react@18.3.1)': dependencies: - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 - '@jridgewell/sourcemap-codec@1.4.15': {} + '@react-stately/datepicker@3.9.4(react@18.3.1)': + dependencies: + '@internationalized/date': 3.5.4 + '@internationalized/string': 3.2.3 + '@react-stately/form': 3.0.3(react@18.3.1) + '@react-stately/overlays': 3.6.7(react@18.3.1) + '@react-stately/utils': 3.10.1(react@18.3.1) + '@react-types/datepicker': 3.7.4(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 - '@jridgewell/trace-mapping@0.3.25': + '@react-stately/dnd@3.3.1(react@18.3.1)': dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.4.15 + '@react-stately/selection': 3.15.1(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 - '@juggle/resize-observer@3.4.0': {} + '@react-stately/flags@3.0.3': + dependencies: + '@swc/helpers': 0.5.11 - '@mdx-js/react@2.3.0(react@18.3.1)': + '@react-stately/form@3.0.3(react@18.3.1)': dependencies: - '@types/mdx': 2.0.13 - '@types/react': 17.0.80 + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 react: 18.3.1 - '@ndelangen/get-tarball@3.0.9': + '@react-stately/grid@3.8.7(react@18.3.1)': dependencies: - gunzip-maybe: 1.4.2 - pump: 3.0.0 - tar-fs: 2.1.1 + '@react-stately/collections': 3.10.7(react@18.3.1) + '@react-stately/selection': 3.15.1(react@18.3.1) + '@react-types/grid': 3.2.6(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 - '@nodelib/fs.scandir@2.1.5': + '@react-stately/list@3.10.5(react@18.3.1)': dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - - '@nodelib/fs.stat@2.0.5': {} + '@react-stately/collections': 3.10.7(react@18.3.1) + '@react-stately/selection': 3.15.1(react@18.3.1) + '@react-stately/utils': 3.10.1(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 - '@nodelib/fs.walk@1.2.8': + '@react-stately/menu@3.7.1(react@18.3.1)': dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.17.1 + '@react-stately/overlays': 3.6.7(react@18.3.1) + '@react-types/menu': 3.9.9(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 - '@pkgjs/parseargs@0.11.0': - optional: true + '@react-stately/numberfield@3.9.3(react@18.3.1)': + dependencies: + '@internationalized/number': 3.5.3 + '@react-stately/form': 3.0.3(react@18.3.1) + '@react-stately/utils': 3.10.1(react@18.3.1) + '@react-types/numberfield': 3.8.3(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 - '@radix-ui/number@1.0.1': + '@react-stately/overlays@3.6.7(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 + '@react-stately/utils': 3.10.1(react@18.3.1) + '@react-types/overlays': 3.8.7(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 - '@radix-ui/primitive@1.0.1': + '@react-stately/radio@3.10.4(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 + '@react-stately/form': 3.0.3(react@18.3.1) + '@react-stately/utils': 3.10.1(react@18.3.1) + '@react-types/radio': 3.8.1(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 + react: 18.3.1 - '@radix-ui/react-arrow@1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@react-stately/searchfield@3.5.3(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-stately/utils': 3.10.1(react@18.3.1) + '@react-types/searchfield': 3.5.5(react@18.3.1) + '@swc/helpers': 0.5.11 react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 17.0.80 - '@radix-ui/react-collection@1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@react-stately/select@3.6.4(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-context': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.0.2(@types/react@17.0.80)(react@18.3.1) + '@react-stately/form': 3.0.3(react@18.3.1) + '@react-stately/list': 3.10.5(react@18.3.1) + '@react-stately/overlays': 3.6.7(react@18.3.1) + '@react-types/select': 3.9.4(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 17.0.80 - '@radix-ui/react-compose-refs@1.0.1(@types/react@17.0.80)(react@18.3.1)': + '@react-stately/selection@3.15.1(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 + '@react-stately/collections': 3.10.7(react@18.3.1) + '@react-stately/utils': 3.10.1(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 react: 18.3.1 - optionalDependencies: - '@types/react': 17.0.80 - '@radix-ui/react-context@1.0.1(@types/react@17.0.80)(react@18.3.1)': + '@react-stately/slider@3.5.4(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 + '@react-stately/utils': 3.10.1(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@react-types/slider': 3.7.3(react@18.3.1) + '@swc/helpers': 0.5.11 react: 18.3.1 - optionalDependencies: - '@types/react': 17.0.80 - '@radix-ui/react-dialog@1.0.5(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@react-stately/table@3.11.8(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-context': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.0.5(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-focus-guards': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-focus-scope': 1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-id': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-portal': 1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-presence': 1.0.1(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.0.2(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@17.0.80)(react@18.3.1) - aria-hidden: 1.2.4 + '@react-stately/collections': 3.10.7(react@18.3.1) + '@react-stately/flags': 3.0.3 + '@react-stately/grid': 3.8.7(react@18.3.1) + '@react-stately/selection': 3.15.1(react@18.3.1) + '@react-stately/utils': 3.10.1(react@18.3.1) + '@react-types/grid': 3.2.6(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@react-types/table': 3.9.5(react@18.3.1) + '@swc/helpers': 0.5.11 react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.5.5(@types/react@17.0.80)(react@18.3.1) - optionalDependencies: - '@types/react': 17.0.80 - '@radix-ui/react-direction@1.0.1(@types/react@17.0.80)(react@18.3.1)': + '@react-stately/tabs@3.6.6(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 + '@react-stately/list': 3.10.5(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@react-types/tabs': 3.3.7(react@18.3.1) + '@swc/helpers': 0.5.11 react: 18.3.1 - optionalDependencies: - '@types/react': 17.0.80 - '@radix-ui/react-dismissable-layer@1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@react-stately/toggle@3.7.4(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-use-escape-keydown': 1.0.3(@types/react@17.0.80)(react@18.3.1) + '@react-stately/utils': 3.10.1(react@18.3.1) + '@react-types/checkbox': 3.8.1(react@18.3.1) + '@swc/helpers': 0.5.11 react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 17.0.80 - '@radix-ui/react-dismissable-layer@1.0.5(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@react-stately/tooltip@3.4.9(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-use-escape-keydown': 1.0.3(@types/react@17.0.80)(react@18.3.1) + '@react-stately/overlays': 3.6.7(react@18.3.1) + '@react-types/tooltip': 3.4.9(react@18.3.1) + '@swc/helpers': 0.5.11 react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 17.0.80 - '@radix-ui/react-focus-guards@1.0.1(@types/react@17.0.80)(react@18.3.1)': + '@react-stately/tree@3.8.1(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 + '@react-stately/collections': 3.10.7(react@18.3.1) + '@react-stately/selection': 3.15.1(react@18.3.1) + '@react-stately/utils': 3.10.1(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 react: 18.3.1 - optionalDependencies: - '@types/react': 17.0.80 - '@radix-ui/react-focus-scope@1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@react-stately/utils@3.10.1(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@swc/helpers': 0.5.11 react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 17.0.80 - '@radix-ui/react-focus-scope@1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@react-stately/virtualizer@3.7.1(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@swc/helpers': 0.5.11 react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 17.0.80 - '@radix-ui/react-id@1.0.1(@types/react@17.0.80)(react@18.3.1)': + '@react-types/breadcrumbs@3.7.5(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@react-types/link': 3.5.5(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) react: 18.3.1 - optionalDependencies: - '@types/react': 17.0.80 - '@radix-ui/react-popper@1.1.2(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@react-types/button@3.9.4(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 - '@floating-ui/react-dom': 2.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-arrow': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-context': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-use-rect': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-use-size': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/rect': 1.0.1 + '@react-types/shared': 3.23.1(react@18.3.1) react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 17.0.80 - '@radix-ui/react-portal@1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@react-types/calendar@3.4.6(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@internationalized/date': 3.5.4 + '@react-types/shared': 3.23.1(react@18.3.1) react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 17.0.80 - '@radix-ui/react-portal@1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@react-types/checkbox@3.8.1(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 17.0.80 - '@radix-ui/react-presence@1.0.1(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@react-types/color@3.0.0-beta.25(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@react-types/slider': 3.7.3(react@18.3.1) react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 17.0.80 - '@radix-ui/react-primitive@1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@react-types/combobox@3.11.1(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/react-slot': 1.0.2(@types/react@17.0.80)(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 17.0.80 - '@radix-ui/react-roving-focus@1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@react-types/datepicker@3.7.4(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-collection': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-context': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-direction': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-id': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@internationalized/date': 3.5.4 + '@react-types/calendar': 3.4.6(react@18.3.1) + '@react-types/overlays': 3.8.7(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 17.0.80 - '@radix-ui/react-select@1.2.2(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@react-types/dialog@3.5.10(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/number': 1.0.1 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-collection': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-context': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-direction': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-focus-guards': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-focus-scope': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-id': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-popper': 1.1.2(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-portal': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.0.2(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-use-previous': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-visually-hidden': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - aria-hidden: 1.2.4 + '@react-types/overlays': 3.8.7(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.5.5(@types/react@17.0.80)(react@18.3.1) - optionalDependencies: - '@types/react': 17.0.80 - '@radix-ui/react-separator@1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@react-types/form@3.7.4(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 17.0.80 - '@radix-ui/react-slot@1.0.2(@types/react@17.0.80)(react@18.3.1)': + '@react-types/grid@3.2.6(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) react: 18.3.1 - optionalDependencies: - '@types/react': 17.0.80 - '@radix-ui/react-tabs@1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@react-types/link@3.5.5(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-context': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-direction': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-id': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-presence': 1.0.1(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-roving-focus': 1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 17.0.80 - '@radix-ui/react-toggle-group@1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@react-types/listbox@3.4.9(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-context': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-direction': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-roving-focus': 1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-toggle': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 17.0.80 - '@radix-ui/react-toggle@1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@react-types/menu@3.9.9(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@react-types/overlays': 3.8.7(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 17.0.80 - '@radix-ui/react-toolbar@1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@react-types/meter@3.4.1(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-context': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-direction': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-roving-focus': 1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-separator': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-toggle-group': 1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-types/progress': 3.5.4(react@18.3.1) react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 17.0.80 - '@radix-ui/react-use-callback-ref@1.0.1(@types/react@17.0.80)(react@18.3.1)': + '@react-types/numberfield@3.8.3(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 + '@react-types/shared': 3.23.1(react@18.3.1) react: 18.3.1 - optionalDependencies: - '@types/react': 17.0.80 - '@radix-ui/react-use-controllable-state@1.0.1(@types/react@17.0.80)(react@18.3.1)': + '@react-types/overlays@3.8.7(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) react: 18.3.1 - optionalDependencies: - '@types/react': 17.0.80 - '@radix-ui/react-use-escape-keydown@1.0.3(@types/react@17.0.80)(react@18.3.1)': + '@react-types/progress@3.5.4(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.80)(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) react: 18.3.1 - optionalDependencies: - '@types/react': 17.0.80 - '@radix-ui/react-use-layout-effect@1.0.1(@types/react@17.0.80)(react@18.3.1)': + '@react-types/radio@3.8.1(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 + '@react-types/shared': 3.23.1(react@18.3.1) react: 18.3.1 - optionalDependencies: - '@types/react': 17.0.80 - '@radix-ui/react-use-previous@1.0.1(@types/react@17.0.80)(react@18.3.1)': + '@react-types/searchfield@3.5.5(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 + '@react-types/shared': 3.23.1(react@18.3.1) + '@react-types/textfield': 3.9.3(react@18.3.1) react: 18.3.1 - optionalDependencies: - '@types/react': 17.0.80 - '@radix-ui/react-use-rect@1.0.1(@types/react@17.0.80)(react@18.3.1)': + '@react-types/select@3.9.4(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/rect': 1.0.1 + '@react-types/shared': 3.23.1(react@18.3.1) react: 18.3.1 - optionalDependencies: - '@types/react': 17.0.80 - '@radix-ui/react-use-size@1.0.1(@types/react@17.0.80)(react@18.3.1)': + '@react-types/shared@3.23.1(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@17.0.80)(react@18.3.1) react: 18.3.1 - optionalDependencies: - '@types/react': 17.0.80 - '@radix-ui/react-visually-hidden@1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@react-types/slider@3.7.3(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 17.0.80 - '@radix-ui/rect@1.0.1': + '@react-types/switch@3.5.3(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 + '@react-types/shared': 3.23.1(react@18.3.1) + react: 18.3.1 - '@react-aria/ssr@3.9.4(react@18.3.1)': + '@react-types/table@3.9.5(react@18.3.1)': dependencies: - '@swc/helpers': 0.5.11 + '@react-types/grid': 3.2.6(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) react: 18.3.1 - '@react-aria/utils@3.17.0(react@18.3.1)': + '@react-types/tabs@3.3.7(react@18.3.1)': dependencies: - '@react-aria/ssr': 3.9.4(react@18.3.1) - '@react-stately/utils': 3.10.1(react@18.3.1) '@react-types/shared': 3.23.1(react@18.3.1) - '@swc/helpers': 0.4.36 - clsx: 1.2.1 react: 18.3.1 - '@react-stately/utils@3.10.1(react@18.3.1)': + '@react-types/textfield@3.9.3(react@18.3.1)': dependencies: - '@swc/helpers': 0.5.11 + '@react-types/shared': 3.23.1(react@18.3.1) react: 18.3.1 - '@react-types/shared@3.23.1(react@18.3.1)': + '@react-types/tooltip@3.4.9(react@18.3.1)': dependencies: + '@react-types/overlays': 3.8.7(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) react: 18.3.1 '@remix-run/router@1.16.1': {} @@ -10450,6 +11886,8 @@ snapshots: slice-ansi: 5.0.0 string-width: 5.1.2 + client-only@0.0.1: {} + cliui@7.0.4: dependencies: string-width: 4.2.3 @@ -13377,6 +14815,76 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 + react-aria-components@1.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@internationalized/date': 3.5.4 + '@internationalized/string': 3.2.3 + '@react-aria/color': 3.0.0-beta.33(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/focus': 3.17.1(react@18.3.1) + '@react-aria/interactions': 3.21.3(react@18.3.1) + '@react-aria/menu': 3.14.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/toolbar': 3.0.0-beta.5(react@18.3.1) + '@react-aria/tree': 3.0.0-alpha.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-stately/color': 3.6.1(react@18.3.1) + '@react-stately/menu': 3.7.1(react@18.3.1) + '@react-stately/table': 3.11.8(react@18.3.1) + '@react-stately/utils': 3.10.1(react@18.3.1) + '@react-types/color': 3.0.0-beta.25(react@18.3.1) + '@react-types/form': 3.7.4(react@18.3.1) + '@react-types/grid': 3.2.6(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + '@react-types/table': 3.9.5(react@18.3.1) + '@swc/helpers': 0.5.11 + client-only: 0.0.1 + react: 18.3.1 + react-aria: 3.33.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-dom: 18.3.1(react@18.3.1) + react-stately: 3.31.1(react@18.3.1) + use-sync-external-store: 1.2.2(react@18.3.1) + + react-aria@3.33.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@internationalized/string': 3.2.3 + '@react-aria/breadcrumbs': 3.5.13(react@18.3.1) + '@react-aria/button': 3.9.5(react@18.3.1) + '@react-aria/calendar': 3.5.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/checkbox': 3.14.3(react@18.3.1) + '@react-aria/combobox': 3.9.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/datepicker': 3.10.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/dialog': 3.5.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/dnd': 3.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/focus': 3.17.1(react@18.3.1) + '@react-aria/gridlist': 3.8.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/i18n': 3.11.1(react@18.3.1) + '@react-aria/interactions': 3.21.3(react@18.3.1) + '@react-aria/label': 3.7.8(react@18.3.1) + '@react-aria/link': 3.7.1(react@18.3.1) + '@react-aria/listbox': 3.12.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/menu': 3.14.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/meter': 3.4.13(react@18.3.1) + '@react-aria/numberfield': 3.11.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/overlays': 3.22.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/progress': 3.4.13(react@18.3.1) + '@react-aria/radio': 3.10.4(react@18.3.1) + '@react-aria/searchfield': 3.7.5(react@18.3.1) + '@react-aria/select': 3.14.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/selection': 3.18.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/separator': 3.3.13(react@18.3.1) + '@react-aria/slider': 3.7.8(react@18.3.1) + '@react-aria/ssr': 3.9.4(react@18.3.1) + '@react-aria/switch': 3.6.4(react@18.3.1) + '@react-aria/table': 3.14.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/tabs': 3.9.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/tag': 3.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-aria/textfield': 3.14.5(react@18.3.1) + '@react-aria/tooltip': 3.7.4(react@18.3.1) + '@react-aria/utils': 3.24.1(react@18.3.1) + '@react-aria/visually-hidden': 3.8.12(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-colorful@5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: react: 18.3.1 @@ -13511,6 +15019,33 @@ snapshots: shallowequal: 1.1.0 throttle-debounce: 3.0.1 + react-stately@3.31.1(react@18.3.1): + dependencies: + '@react-stately/calendar': 3.5.1(react@18.3.1) + '@react-stately/checkbox': 3.6.5(react@18.3.1) + '@react-stately/collections': 3.10.7(react@18.3.1) + '@react-stately/combobox': 3.8.4(react@18.3.1) + '@react-stately/data': 3.11.4(react@18.3.1) + '@react-stately/datepicker': 3.9.4(react@18.3.1) + '@react-stately/dnd': 3.3.1(react@18.3.1) + '@react-stately/form': 3.0.3(react@18.3.1) + '@react-stately/list': 3.10.5(react@18.3.1) + '@react-stately/menu': 3.7.1(react@18.3.1) + '@react-stately/numberfield': 3.9.3(react@18.3.1) + '@react-stately/overlays': 3.6.7(react@18.3.1) + '@react-stately/radio': 3.10.4(react@18.3.1) + '@react-stately/searchfield': 3.5.3(react@18.3.1) + '@react-stately/select': 3.6.4(react@18.3.1) + '@react-stately/selection': 3.15.1(react@18.3.1) + '@react-stately/slider': 3.5.4(react@18.3.1) + '@react-stately/table': 3.11.8(react@18.3.1) + '@react-stately/tabs': 3.6.6(react@18.3.1) + '@react-stately/toggle': 3.7.4(react@18.3.1) + '@react-stately/tooltip': 3.4.9(react@18.3.1) + '@react-stately/tree': 3.8.1(react@18.3.1) + '@react-types/shared': 3.23.1(react@18.3.1) + react: 18.3.1 + react-style-singleton@2.2.1(@types/react@17.0.80)(react@18.3.1): dependencies: get-nonce: 1.0.1 @@ -14666,6 +16201,10 @@ snapshots: optionalDependencies: '@types/react': 17.0.80 + use-sync-external-store@1.2.2(react@18.3.1): + dependencies: + react: 18.3.1 + util-deprecate@1.0.2: {} util@0.12.5: diff --git a/src/i18n/en.json b/src/i18n/en.json index 7e84367f0..458f64e2d 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -53,6 +53,7 @@ "collapsible.viewAllFiles": "View all files", "collapsible.viewMoreComments": "View more comments", "collapsible.viewMoreReplies": "View more replies", + "collapsible.viewMoreReply": "View {count, plural, one {# reply} other {# replies}}", "community.createSuccess": "Your community was successfully created", "community.updateSuccess": "Your community was successfully updated", diff --git a/src/social/hooks/collections/useCommunityModeratorsCollection.ts b/src/social/hooks/collections/useCommunityModeratorsCollection.ts index 412c78f82..3019dd8bb 100644 --- a/src/social/hooks/collections/useCommunityModeratorsCollection.ts +++ b/src/social/hooks/collections/useCommunityModeratorsCollection.ts @@ -1,5 +1,4 @@ import { CommunityRepository } from '@amityco/ts-sdk'; - import useLiveCollection from '~/core/hooks/useLiveCollection'; import { MemberRoles } from '~/social/constants'; diff --git a/src/v4/social/components/StoryTab/StoryTabCommunity.tsx b/src/v4/social/components/StoryTab/StoryTabCommunity.tsx index dce30cd72..7a64e72ce 100644 --- a/src/v4/social/components/StoryTab/StoryTabCommunity.tsx +++ b/src/v4/social/components/StoryTab/StoryTabCommunity.tsx @@ -1,40 +1,38 @@ -import React, { useRef } from 'react'; +import React from 'react'; import Truncate from 'react-truncate-markup'; - -import useStories from '~/social/hooks/useStories'; -import useSDK from '~/core/hooks/useSDK'; - +import { FileTrigger, Button } from 'react-aria-components'; import { FormattedMessage } from 'react-intl'; import { StoryRing } from '~/v4/social/elements/StoryRing/StoryRing'; import clsx from 'clsx'; - -import styles from './StoryTabCommunity.module.css'; +import { useGetActiveStoriesByTarget } from '~/v4/social/hooks/useGetActiveStories'; +import useSDK from '~/v4/core/hooks/useSDK'; import useUser from '~/v4/core/hooks/objects/useUser'; -import { useCommunityInfo } from '~/social/components/CommunityInfo/hooks'; import { isAdmin, isModerator } from '~/v4/utils/permissions'; import { checkStoryPermission } from '~/v4/social/utils'; -import { Avatar } from '~/v4/core/components'; -import { AVATAR_SIZE } from '~/v4/core/components/Avatar'; -import CommunityDefaultImg from '~/v4/icons/Community'; +import { useCommunityInfo } from '~/v4/social/hooks/useCommunityInfo'; -const AddIcon = (props: React.SVGProps<SVGSVGElement>) => { +import styles from './StoryTabCommunity.module.css'; +import { CreateNewStoryButton } from '~/v4/social/elements/CreateNewStoryButton'; + +const AddStoryIcon = (props: React.SVGProps<SVGSVGElement>) => { return ( <svg xmlns="http://www.w3.org/2000/svg" - width="16" - height="16" + width="24" + height="24" + viewBox="0 0 24 24" fill="none" - viewBox="0 0 16 16" + stroke="currentColor" + strokeWidth="2" + strokeLinecap="round" + strokeLinejoin="round" {...props} > - <circle cx="8" cy="8" r="7.25" fill="#1054DE" stroke="#fff" strokeWidth="1.5"></circle> - <path - fill="#fff" - d="M11.438 7.625c.156 0 .312.156.312.313v.625a.321.321 0 01-.313.312H8.626v2.813a.321.321 0 01-.313.312h-.624a.308.308 0 01-.313-.313V8.876H4.562a.309.309 0 01-.312-.313v-.624c0-.157.137-.313.313-.313h2.812V4.812c0-.156.137-.312.313-.312h.625c.156 0 .312.156.312.313v2.812h2.813z" - ></path> + <path d="M12 5v14M5 12h14"></path> </svg> ); }; + const ErrorIcon = (props: React.SVGProps<SVGSVGElement>) => { return ( <svg @@ -69,13 +67,12 @@ export const StoryTabCommunityFeed: React.FC<StoryTabCommunityFeedProps> = ({ onFileChange, onStoryClick, }) => { - const { stories } = useStories({ + const { stories } = useGetActiveStoriesByTarget({ targetId: communityId, targetType: 'community', options: { orderBy: 'asc', sortBy: 'createdAt' }, }); const { avatarFileUrl, community } = useCommunityInfo(communityId); - const fileInputRef = useRef<HTMLInputElement>(null); const { currentUserId, client } = useSDK(); const { user } = useUser(currentUserId); @@ -88,25 +85,12 @@ export const StoryTabCommunityFeed: React.FC<StoryTabCommunityFeedProps> = ({ const uploading = stories.some((story) => story?.syncState === 'syncing'); const isErrored = stories.some((story) => story?.syncState === 'error'); - const handleAddIconClick = () => { - if (fileInputRef.current) { - fileInputRef.current.click(); - } - }; - - const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => { - const selectedFile = event.target.files?.[0]; - if (selectedFile) { - onFileChange(selectedFile); - } - }; - const handleOnClick = () => { if (Array.isArray(stories) && stories.length === 0) return; onStoryClick(); }; - if (!community?.isJoined && !hasStories) return null; + if (!community?.isJoined || !hasStories) return null; return ( <div className={clsx(styles.storyTabContainer)}> @@ -123,25 +107,19 @@ export const StoryTabCommunityFeed: React.FC<StoryTabCommunityFeedProps> = ({ )} <div className={clsx(styles.storyAvatarContainer)}> - <Avatar - avatar={avatarFileUrl} - size={AVATAR_SIZE.SMALL} - className={clsx(styles.storyAvatar)} - onClick={handleOnClick} - defaultImage={<CommunityDefaultImg />} - /> + <img src={avatarFileUrl} className={clsx(styles.storyAvatar)} onClick={handleOnClick} /> </div> {hasStoryPermission && ( <> - <AddIcon className={styles.addStoryButton} onClick={handleAddIconClick} /> - <input - className={clsx(styles.hiddenInput)} - ref={fileInputRef} - type="file" - accept="image/*,video/*" - onChange={handleFileChange} - /> + <FileTrigger + onSelect={(e) => { + const files = Array.from(e as FileList); + onFileChange(files[0]); + }} + > + <CreateNewStoryButton pageId="story_page" componentId="*" /> + </FileTrigger> </> )} {isErrored && <ErrorIcon className={clsx(styles.errorIcon)} />} diff --git a/src/v4/social/components/StoryTab/StoryTabItem.tsx b/src/v4/social/components/StoryTab/StoryTabItem.tsx index be173c87b..5fc1de699 100644 --- a/src/v4/social/components/StoryTab/StoryTabItem.tsx +++ b/src/v4/social/components/StoryTab/StoryTabItem.tsx @@ -92,7 +92,7 @@ export const StoryTabItem: React.FC<StoryTabProps> = ({ )} </div> {isErrored && <ErrorIcon className={styles.errorIcon} />} - {community?.isOfficial && <Verified className={styles.verifiedIcon} />} + {community?.isOfficial && !isErrored && <Verified className={styles.verifiedIcon} />} </div> <Typography.Caption className={clsx(styles.displayName)}> diff --git a/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx b/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx index 0eba1e071..13d351080 100644 --- a/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx +++ b/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx @@ -38,7 +38,6 @@ export const ShareStoryButton = ({ onClick, }: ShareButtonProps) => { const elementId = 'share_story_button'; - const { config, isExcluded } = useAmityElement({ pageId, componentId, diff --git a/src/v4/social/hooks/collections/useCommunityMembersCollection.tsx b/src/v4/social/hooks/collections/useCommunityMembersCollection.ts similarity index 100% rename from src/v4/social/hooks/collections/useCommunityMembersCollection.tsx rename to src/v4/social/hooks/collections/useCommunityMembersCollection.ts diff --git a/src/v4/social/hooks/collections/useCommunityModeratorsCollection.ts b/src/v4/social/hooks/collections/useCommunityModeratorsCollection.ts new file mode 100644 index 000000000..e6a7ae273 --- /dev/null +++ b/src/v4/social/hooks/collections/useCommunityModeratorsCollection.ts @@ -0,0 +1,18 @@ +import { CommunityRepository } from '@amityco/ts-sdk'; +import useLiveCollection from '~/v4/core/hooks/useLiveCollection'; +import { MemberRoles } from '~/v4/social/constants/memberRoles'; + +const { COMMUNITY_MODERATOR } = MemberRoles; + +export default function useCommunityModeratorsCollection(communityId?: string) { + const { items, ...rest } = useLiveCollection({ + fetcher: CommunityRepository.Membership.getMembers, + params: { communityId: communityId as string, roles: [COMMUNITY_MODERATOR] }, + shouldCall: () => !!communityId, + }); + + return { + moderators: items, + ...rest, + }; +} diff --git a/src/v4/social/hooks/collections/useGlobalStoryTargets.tsx b/src/v4/social/hooks/collections/useGlobalStoryTargets.ts similarity index 100% rename from src/v4/social/hooks/collections/useGlobalStoryTargets.tsx rename to src/v4/social/hooks/collections/useGlobalStoryTargets.ts diff --git a/src/v4/social/hooks/collections/useReactionsCollection.tsx b/src/v4/social/hooks/collections/useReactionsCollection.ts similarity index 100% rename from src/v4/social/hooks/collections/useReactionsCollection.tsx rename to src/v4/social/hooks/collections/useReactionsCollection.ts diff --git a/src/v4/social/hooks/index.ts b/src/v4/social/hooks/index.ts index e19849fde..518c834d5 100644 --- a/src/v4/social/hooks/index.ts +++ b/src/v4/social/hooks/index.ts @@ -1,3 +1,10 @@ export * from './collections/useReactionsCollection'; export { useGetActiveStoriesByTarget } from './useGetActiveStories'; export { useCommentFlaggedByMe } from './useCommentFlaggedByMe'; +export { default as useCommunityPermission } from './useCommunityPermission'; +export { useCommunityInfo } from './useCommunityInfo'; +export { default as useCategoriesByIds } from './useCategoriesByIds'; +export { default as useCommunityStoriesSubscription } from './useCommunityStoriesSubscription'; +export { default as useGetStoryByStoryId } from './useGetStoryByStoryId'; +export { default as useLiveCollection } from './useLiveCollection'; +export { useUserQueryByDisplayName } from './useUserQueryByDisplayName'; diff --git a/src/v4/social/hooks/useCategoriesByIds.ts b/src/v4/social/hooks/useCategoriesByIds.ts new file mode 100644 index 000000000..981e36ec7 --- /dev/null +++ b/src/v4/social/hooks/useCategoriesByIds.ts @@ -0,0 +1,23 @@ +import { CategoryRepository } from '@amityco/ts-sdk'; +import { useEffect, useState } from 'react'; + +const useCategoriesByIds = (categoryIds?: string[]) => { + const [categories, setCategories] = useState<Amity.Category[]>([]); + + useEffect(() => { + async function run() { + if (categoryIds == null || categoryIds.length === 0) return; + const categories = await Promise.all( + categoryIds.map( + async (categoryId) => (await CategoryRepository.getCategory(categoryId)).data, + ), + ); + setCategories(categories); + } + run(); + }, [categoryIds]); + + return categories; +}; + +export default useCategoriesByIds; diff --git a/src/v4/social/hooks/useCommentFlaggedByMe.tsx b/src/v4/social/hooks/useCommentFlaggedByMe.ts similarity index 89% rename from src/v4/social/hooks/useCommentFlaggedByMe.tsx rename to src/v4/social/hooks/useCommentFlaggedByMe.ts index f318ebd33..50fbfa908 100644 --- a/src/v4/social/hooks/useCommentFlaggedByMe.tsx +++ b/src/v4/social/hooks/useCommentFlaggedByMe.ts @@ -1,7 +1,6 @@ +import React, { useEffect, useState } from 'react'; import { CommentRepository } from '@amityco/ts-sdk'; import { useQuery } from '@tanstack/react-query'; -import React, { useEffect, useState } from 'react'; -import { FormattedMessage } from 'react-intl'; import { useNotifications } from '~/v4/core/providers/NotificationProvider'; export const useCommentFlaggedByMe = (commentId?: string) => { @@ -51,12 +50,12 @@ export const useCommentFlaggedByMe = (commentId?: string) => { if (isFlaggedByMe) { await unflagComment(); notification.success({ - content: <FormattedMessage id="storyViewer.toast.comment.unreported" />, + content: 'Comment unreported', }); } else { await flagComment(); notification.success({ - content: <FormattedMessage id="storyViewer.toast.comment.reported" />, + content: 'Comment reported', }); } }; diff --git a/src/v4/social/hooks/useCommunityInfo.ts b/src/v4/social/hooks/useCommunityInfo.ts new file mode 100644 index 000000000..10696952e --- /dev/null +++ b/src/v4/social/hooks/useCommunityInfo.ts @@ -0,0 +1,55 @@ +import { useMemo } from 'react'; +import { CommunityRepository } from '@amityco/ts-sdk'; +import useCommunityPermission from '~/social/hooks/useCommunityPermission'; +import usePostsCollection from '~/social/hooks/collections/usePostsCollection'; +import useImage from '~/v4/core/hooks/useImage'; +import useCommunity from '~/v4/core/hooks/collections/useCommunity'; +import useCategoriesByIds from '~/v4/social/hooks/useCategoriesByIds'; +import { useNavigation } from '~/v4/core/providers/NavigationProvider'; + +export const useCommunityInfo = (communityId?: string) => { + const { onEditCommunity } = useNavigation(); + const { community } = useCommunity({ + communityId, + shouldCall: () => !!communityId, + }); + const avatarFileUrl = useImage({ fileId: community?.avatarFileId, imageSize: 'medium' }); + + const { posts: reviewingPosts } = usePostsCollection({ + targetType: 'community', + targetId: communityId, + feedType: 'reviewing', + }); + + const categories = useCategoriesByIds(community?.categoryIds); + + const { canReview, canEdit } = useCommunityPermission({ community }); + + const pendingPostsCount = useMemo(() => { + return reviewingPosts?.length || 0; + }, [reviewingPosts]); + + type updateCommunityFnType = typeof CommunityRepository.updateCommunity; + + const updateCommunity = (payload: Parameters<updateCommunityFnType>[1]) => + communityId && CommunityRepository.updateCommunity(communityId, payload); + + const joinCommunity = () => communityId && CommunityRepository.joinCommunity(communityId); + const leaveCommunity = () => communityId && CommunityRepository.leaveCommunity(communityId); + const closeCommunity = () => communityId && CommunityRepository.deleteCommunity(communityId); + + return { + community, + reviewingPosts, + avatarFileUrl, + communityCategories: categories, + pendingPostsCount, + canEditCommunity: canEdit, + canReviewCommunityPosts: canReview, + onEditCommunity, + joinCommunity, + leaveCommunity, + updateCommunity, + closeCommunity, + }; +}; diff --git a/src/v4/social/hooks/useCommunityPermission.ts b/src/v4/social/hooks/useCommunityPermission.ts new file mode 100644 index 000000000..722357aad --- /dev/null +++ b/src/v4/social/hooks/useCommunityPermission.ts @@ -0,0 +1,21 @@ +import useSDK from '~/v4/core/hooks/useSDK'; +import useCommunityModeratorsCollection from '~/v4/social/hooks/collections/useCommunityModeratorsCollection'; + +const useCommunityPermission = ({ community }: { community?: Amity.Community | null }) => { + const { currentUserId, userRoles } = useSDK(); + const { moderators } = useCommunityModeratorsCollection(community?.communityId); + + const moderator = moderators.find((moderator) => moderator.userId === currentUserId); + + const isGlobalAdmin = userRoles.find((role) => role === 'global-admin') != null; + + const isModerator = moderator != null; + + return { + isModerator, + canEdit: isGlobalAdmin || isModerator, + canReview: isGlobalAdmin || isModerator, + }; +}; + +export default useCommunityPermission; diff --git a/src/v4/social/hooks/useCommunityStoriesSubscription.tsx b/src/v4/social/hooks/useCommunityStoriesSubscription.ts similarity index 100% rename from src/v4/social/hooks/useCommunityStoriesSubscription.tsx rename to src/v4/social/hooks/useCommunityStoriesSubscription.ts diff --git a/src/v4/social/hooks/useGetActiveStories.tsx b/src/v4/social/hooks/useGetActiveStories.ts similarity index 100% rename from src/v4/social/hooks/useGetActiveStories.tsx rename to src/v4/social/hooks/useGetActiveStories.ts diff --git a/src/v4/social/hooks/useGetStoryByStoryId.tsx b/src/v4/social/hooks/useGetStoryByStoryId.ts similarity index 100% rename from src/v4/social/hooks/useGetStoryByStoryId.tsx rename to src/v4/social/hooks/useGetStoryByStoryId.ts From 99f87371bf29a7eafbc898b23b71aa84d79fe3d1 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Fri, 21 Jun 2024 02:00:30 +0700 Subject: [PATCH 152/300] fix: type and layout (#434) --- .../PostContent/LinkPreview/LinkPreview.tsx | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/v4/social/components/PostContent/LinkPreview/LinkPreview.tsx b/src/v4/social/components/PostContent/LinkPreview/LinkPreview.tsx index 300981e52..bf921f5d8 100644 --- a/src/v4/social/components/PostContent/LinkPreview/LinkPreview.tsx +++ b/src/v4/social/components/PostContent/LinkPreview/LinkPreview.tsx @@ -34,10 +34,10 @@ const usePreviewLink = ({ url }: { url: string }) => { queryKey: ['asc-uikit', 'previewLink'], queryFn: async () => { const data = await client?.http.get<{ - title: 'string'; - description: 'string'; - image: 'string'; - video: 'string'; + title: string; + description: string; + image: string; + video: string; }>('/api/v1/link-preview', { params: { url, @@ -87,15 +87,12 @@ export function LinkPreview({ url }: LinkPreviewProps) { <object data={previewData.data.image} className={styles.linkPreview__object}> <UnableToPreview /> </object> + ) : previewData.data?.video ? ( + <VideoPreview src={previewData.data.video} /> ) : ( <UnableToPreview /> )} </div> - {previewData.data?.video ? ( - <VideoPreview src={previewData.data.video} /> - ) : ( - <UnableToPreview /> - )} <div className={styles.linkPreview__bottom}> <Typography.Caption>{urlObject.hostname}</Typography.Caption> <Typography.BodyBold>{previewData.data?.title || ''}</Typography.BodyBold> From 3c6d99418094689a6b4542031488167f8211b5c9 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Fri, 21 Jun 2024 10:16:56 +0700 Subject: [PATCH 153/300] fix: ASC-21494 - non member can react in comment tray (#433) * fix: story tab background color * fix: update navigate * fix: styled to css module * fix: remove console.log * fix: remove console.log * fix: story tab * fix: type * fix: remove console.log * fix: story tab type * fix: noti css * fix: story avatar * fix: remove unused * fix: remove unused * fix: share story button use amityElement * fix: icon * fix: add default communityProfileImageBackground * fix: story tab community * fix: story tab * fix: navigate * fix: story navigate * fix: remove unused * fix: red border story tab * fix: import and type * fix: import and type * fix: remove error icon z-index * fix: official badge condition * fix: reaction list * fix: remove console.log * fix: import * fix: pass pageId and componentId * fix: avatar * fix: community avatar * fix: play pause button * fix: story tab condition * fix: remove unused * fix: remove unused * fix: story tab * fix: onPress * fix: wrapper * fix: rem * fix: remove package manager --- package.json | 1 + pnpm-lock.yaml | 31 +++---- src/social/components/StoryTab/StoryTab.tsx | 3 - src/v4/core/IconComponent.tsx | 9 ++- src/v4/core/components/Avatar/Avatar.tsx | 1 - .../components/CommentTray/CommentTray.tsx | 13 ++- .../components/ReactionList/ReactionList.tsx | 9 ++- .../ReactionList/ReactionListPanel.tsx | 41 +++++----- .../components/StoryTab/StoryTabCommunity.tsx | 47 +++-------- .../StoryTab/StoryTabGlobalFeed.tsx | 8 +- src/v4/social/constants/default-avatar.ts | 1 - .../AspectRatioButton/AspectRatioButton.tsx | 6 +- .../social/elements/BackButton/BackButton.tsx | 6 +- .../elements/ClearButton/ClearButton.tsx | 12 +-- .../elements/CloseButton/CloseButton.tsx | 6 +- .../elements/CommentButton/CommentButton.tsx | 6 +- .../CreateNewStoryButton.tsx | 6 +- .../HyperLinkButton/HyperLinkButton.tsx | 6 +- .../ImpressionButton/ImpressionButton.tsx | 2 +- .../OverflowMenuButton/OverflowMenuButton.tsx | 6 +- .../elements/ReactButton/ReactButton.tsx | 81 ------------------- src/v4/social/elements/ReactButton/index.ts | 1 - src/v4/social/elements/ReactButton/styles.tsx | 29 ------- .../elements/SpeakerButton/SpeakerButton.tsx | 6 +- src/v4/social/elements/index.ts | 2 +- .../Comment/UIComment.module.css | 2 +- .../internal-components/Comment/index.tsx | 16 +++- .../CommentList/CommentList.tsx | 6 ++ .../StoryViewer/Renderers/Image.tsx | 6 +- .../Wrappers/Footer/Footer.module.css | 4 +- .../Renderers/Wrappers/Footer/index.tsx | 22 ++--- .../Wrappers/Header/Header.module.css | 4 +- .../Renderers/Wrappers/Header/index.tsx | 4 +- src/v4/social/pages/DraftsPage/DraftsPage.tsx | 6 +- .../pages/StoryPage/GlobalFeedStory.tsx | 12 +-- .../pages/StoryPage/StoryPage.module.css | 7 +- 36 files changed, 168 insertions(+), 260 deletions(-) delete mode 100644 src/v4/social/constants/default-avatar.ts delete mode 100644 src/v4/social/elements/ReactButton/ReactButton.tsx delete mode 100644 src/v4/social/elements/ReactButton/index.ts delete mode 100644 src/v4/social/elements/ReactButton/styles.tsx diff --git a/package.json b/package.json index 99a0348e7..578d25e00 100644 --- a/package.json +++ b/package.json @@ -120,6 +120,7 @@ "millify": "^6.1.0", "modern-normalize": "^2.0.0", "polished": "^4.3.1", + "react-aria": "^3.33.1", "react-aria-components": "^1.2.1", "react-hook-form": "^7.49.2", "react-infinite-scroll-component": "^6.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 86303c353..b410acbec 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,6 +65,9 @@ importers: polished: specifier: ^4.3.1 version: 4.3.1 + react-aria: + specifier: ^3.33.1 + version: 3.33.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-aria-components: specifier: ^1.2.1 version: 1.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -266,10 +269,10 @@ importers: version: 8.0.1(stylelint@16.6.1(typescript@4.9.5)) svg-url-loader: specifier: ^7.1.1 - version: 7.1.1(webpack@5.92.0(esbuild@0.19.12)) + version: 7.1.1(webpack@5.92.0(esbuild@0.18.20)) ts-jest: specifier: ^29.1.1 - version: 29.1.5(@babel/core@7.24.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(esbuild@0.19.12)(jest@29.7.0(@types/node@20.14.4))(typescript@4.9.5) + version: 29.1.5(@babel/core@7.24.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(esbuild@0.18.20)(jest@29.7.0(@types/node@20.14.4))(typescript@4.9.5) tsup: specifier: ^7.3.0 version: 7.3.0(postcss@8.4.38)(typescript@4.9.5) @@ -12917,11 +12920,11 @@ snapshots: dependencies: flat-cache: 5.0.0 - file-loader@6.2.0(webpack@5.92.0(esbuild@0.19.12)): + file-loader@6.2.0(webpack@5.92.0(esbuild@0.18.20)): dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.92.0(esbuild@0.19.12) + webpack: 5.92.0(esbuild@0.18.20) file-system-cache@2.3.0: dependencies: @@ -15788,11 +15791,11 @@ snapshots: svg-tags@1.0.0: {} - svg-url-loader@7.1.1(webpack@5.92.0(esbuild@0.19.12)): + svg-url-loader@7.1.1(webpack@5.92.0(esbuild@0.18.20)): dependencies: - file-loader: 6.2.0(webpack@5.92.0(esbuild@0.19.12)) + file-loader: 6.2.0(webpack@5.92.0(esbuild@0.18.20)) loader-utils: 2.0.4 - webpack: 5.92.0(esbuild@0.19.12) + webpack: 5.92.0(esbuild@0.18.20) synchronous-promise@2.0.17: {} @@ -15848,16 +15851,16 @@ snapshots: type-fest: 0.16.0 unique-string: 2.0.0 - terser-webpack-plugin@5.3.10(esbuild@0.19.12)(webpack@5.92.0(esbuild@0.19.12)): + terser-webpack-plugin@5.3.10(esbuild@0.18.20)(webpack@5.92.0(esbuild@0.18.20)): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 terser: 5.31.1 - webpack: 5.92.0(esbuild@0.19.12) + webpack: 5.92.0(esbuild@0.18.20) optionalDependencies: - esbuild: 0.19.12 + esbuild: 0.18.20 terser@5.31.1: dependencies: @@ -15942,7 +15945,7 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.1.5(@babel/core@7.24.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(esbuild@0.19.12)(jest@29.7.0(@types/node@20.14.4))(typescript@4.9.5): + ts-jest@29.1.5(@babel/core@7.24.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(esbuild@0.18.20)(jest@29.7.0(@types/node@20.14.4))(typescript@4.9.5): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 @@ -15959,7 +15962,7 @@ snapshots: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 babel-jest: 29.7.0(@babel/core@7.24.7) - esbuild: 0.19.12 + esbuild: 0.18.20 tsconfck@3.1.0(typescript@4.9.5): optionalDependencies: @@ -16288,7 +16291,7 @@ snapshots: webpack-virtual-modules@0.6.2: {} - webpack@5.92.0(esbuild@0.19.12): + webpack@5.92.0(esbuild@0.18.20): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.5 @@ -16311,7 +16314,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(esbuild@0.19.12)(webpack@5.92.0(esbuild@0.19.12)) + terser-webpack-plugin: 5.3.10(esbuild@0.18.20)(webpack@5.92.0(esbuild@0.18.20)) watchpack: 2.4.1 webpack-sources: 3.2.3 transitivePeerDependencies: diff --git a/src/social/components/StoryTab/StoryTab.tsx b/src/social/components/StoryTab/StoryTab.tsx index 016e249cd..c928efe51 100644 --- a/src/social/components/StoryTab/StoryTab.tsx +++ b/src/social/components/StoryTab/StoryTab.tsx @@ -12,7 +12,6 @@ type StoryTabProps<T extends StoryTabType> = { }; export const StoryTab = <T extends StoryTabType>({ type, communityId }: StoryTabProps<T>) => { - const pageId = '*'; const componentId = 'story_tab_component'; const { onClickStory, goToDraftStoryPage } = useNavigation(); @@ -23,7 +22,6 @@ export const StoryTab = <T extends StoryTabType>({ type, communityId }: StoryTab case 'communityFeed': return ( <StoryTabCommunityFeed - pageId={pageId} componentId={componentId} communityId={communityId || ''} onFileChange={(file) => { @@ -45,7 +43,6 @@ export const StoryTab = <T extends StoryTabType>({ type, communityId }: StoryTab case 'globalFeed': return ( <StoryTabGlobalFeed - pageId={pageId} componentId={componentId} goToViewStoryPage={({ storyTarget, storyTargets }) => { onClickStory( diff --git a/src/v4/core/IconComponent.tsx b/src/v4/core/IconComponent.tsx index 721f3b72b..1755baddb 100644 --- a/src/v4/core/IconComponent.tsx +++ b/src/v4/core/IconComponent.tsx @@ -1,9 +1,10 @@ import React from 'react'; +import { Button, PressEvent } from 'react-aria-components'; export interface IconComponentProps { defaultIcon: () => JSX.Element; imgIcon: () => JSX.Element; - onClick?: (e: React.MouseEvent) => void; + onPress?: (e: PressEvent) => void; defaultIconName?: string; configIconName?: string; 'data-qa-anchor'?: string; @@ -14,15 +15,15 @@ export interface IconComponentProps { export const IconComponent = ({ defaultIcon, imgIcon, - onClick, + onPress, style, defaultIconName, configIconName, className, }: IconComponentProps) => { return ( - <button className={className} data-qa-anchor={'data-qa-anchor'} onClick={onClick} style={style}> + <Button className={className} data-qa-anchor={'data-qa-anchor'} onPress={onPress} style={style}> {defaultIconName === configIconName ? defaultIcon() : imgIcon()} - </button> + </Button> ); }; diff --git a/src/v4/core/components/Avatar/Avatar.tsx b/src/v4/core/components/Avatar/Avatar.tsx index 35c560958..8a324f3dd 100644 --- a/src/v4/core/components/Avatar/Avatar.tsx +++ b/src/v4/core/components/Avatar/Avatar.tsx @@ -42,7 +42,6 @@ export const Avatar = ({ styles[size], )} onClick={onClick} - style={{ backgroundImage: backgroundImage ? backgroundImage : undefined }} {...props} > {loading ? ( diff --git a/src/v4/social/components/CommentTray/CommentTray.tsx b/src/v4/social/components/CommentTray/CommentTray.tsx index 0e8c62583..2940bcf6e 100644 --- a/src/v4/social/components/CommentTray/CommentTray.tsx +++ b/src/v4/social/components/CommentTray/CommentTray.tsx @@ -1,4 +1,5 @@ import React, { useState } from 'react'; +import { useAmityComponent } from '~/v4/core/hooks/uikit/index'; import { CommentList } from '~/v4/social/internal-components/CommentList'; import { StoryCommentComposeBar } from '~/v4/social/internal-components/StoryCommentComposeBar'; import styles from './CommentTray.module.css'; @@ -11,15 +12,23 @@ interface CommentTrayProps { community: Amity.Community; shouldAllowInteraction: boolean; shouldAllowCreation?: boolean; + pageId?: string; } export const CommentTray = ({ + pageId = '*', referenceType, referenceId, community = {} as Amity.Community, shouldAllowInteraction = true, - shouldAllowCreation = false, + shouldAllowCreation = true, }: CommentTrayProps) => { + const componentId = 'comment_tray_component'; + const { config } = useAmityComponent({ + pageId, + componentId, + }); + const [isReplying, setIsReplying] = useState(false); const [replyTo, setReplyTo] = useState<Amity.Comment | null>(null); @@ -37,6 +46,8 @@ export const CommentTray = ({ <div className={styles.commentTrayContainer}> <div className={styles.commentListContainer}> <CommentList + pageId={pageId} + componentId={componentId} referenceId={referenceId} referenceType={referenceType} onClickReply={onClickReply} diff --git a/src/v4/social/components/ReactionList/ReactionList.tsx b/src/v4/social/components/ReactionList/ReactionList.tsx index 83e2d86e5..74dfb0eaf 100644 --- a/src/v4/social/components/ReactionList/ReactionList.tsx +++ b/src/v4/social/components/ReactionList/ReactionList.tsx @@ -1,5 +1,4 @@ import React, { useMemo, useRef, useState } from 'react'; -import styles from './ReactionList.module.css'; import { useReactionsCollection } from '~/v4/social/hooks/collections/useReactionsCollection'; import { Typography } from '~/v4/core/components'; import { useCustomReaction } from '~/v4/core/providers/CustomReactionProvider'; @@ -12,7 +11,9 @@ import { ReactionListLoadingState } from './ReactionListLoadingState'; import useReaction from '~/v4/chat/hooks/useReaction'; import useReactionByReference from '~/v4/chat/hooks/useReactionByReference'; import FallbackReaction from '~/v4/icons/FallbackReaction'; -import { useAmityComponent } from '~/v4/core/hooks/uikit/index'; +import { useAmityComponent } from '~/v4/core/hooks/uikit'; + +import styles from './ReactionList.module.css'; interface ReactionListProps { pageId: string; @@ -42,6 +43,7 @@ const RenderCondition = ({ removeReaction, error, currentRef, + showReactionUserDetails = false, }: { filteredReactions: Amity.Reactor[]; isLoading: boolean; @@ -50,6 +52,7 @@ const RenderCondition = ({ removeReaction: (reaction: string) => Promise<void>; error: Error | null; currentRef: HTMLDivElement | null; + showReactionUserDetails?: boolean; }) => { if (isLoading) { return <ReactionListLoadingState />; @@ -75,6 +78,7 @@ const RenderCondition = ({ isLoading={isLoading} filteredReactions={filteredReactions} removeReaction={removeReaction} + showReactionUserDetails={showReactionUserDetails} /> ); }; @@ -85,6 +89,7 @@ export const ReactionList = ({ pageId = '*', referenceId, referenceType }: React pageId, componentId, }); + const { reactions, error, isLoading, hasMore, loadMore } = useReactionsCollection({ referenceId, referenceType, diff --git a/src/v4/social/components/ReactionList/ReactionListPanel.tsx b/src/v4/social/components/ReactionList/ReactionListPanel.tsx index 6bfbe4f99..c3ff78ee2 100644 --- a/src/v4/social/components/ReactionList/ReactionListPanel.tsx +++ b/src/v4/social/components/ReactionList/ReactionListPanel.tsx @@ -1,12 +1,13 @@ -import styles from './ReactionList.module.css'; import React, { Fragment, useMemo } from 'react'; import { Avatar, Typography } from '~/v4/core/components'; import FallbackReaction from '~/v4/icons/FallbackReaction'; import { ReactionIcon } from '~/v4/social/components/ReactionList/ReactionIcon'; import { useCustomReaction } from '~/v4/core/providers/CustomReactionProvider'; -import useSDK from '~/core/hooks/useSDK'; -import { FormattedMessage } from 'react-intl'; import InfiniteScroll from 'react-infinite-scroll-component'; +import { AVATAR_SIZE } from '~/v4/core/components/Avatar/Avatar'; +import useSDK from '~/v4/core/hooks/useSDK'; + +import styles from './ReactionList.module.css'; export const ReactionListPanel = ({ filteredReactions, @@ -15,6 +16,7 @@ export const ReactionListPanel = ({ loadMore, isLoading, currentRef, + showReactionUserDetails, }: { filteredReactions: Amity.Reactor[]; removeReaction: (reaction: string) => Promise<void>; @@ -22,6 +24,7 @@ export const ReactionListPanel = ({ loadMore: () => void; isLoading: boolean; currentRef: HTMLDivElement | null; + showReactionUserDetails?: boolean; }) => { const { currentUserId } = useSDK(); const { config } = useCustomReaction(); @@ -50,17 +53,17 @@ export const ReactionListPanel = ({ <div className={styles.userDetailsProfile}> <Avatar data-qa-anchor="user_avatar_view" - size="small" + size={AVATAR_SIZE.SMALL} avatar={reaction.user?.avatar?.fileUrl} /> <Typography.BodyBold data-qa-anchor="user_display_name"> {reaction.user?.displayName} - {currentUserId === reaction.user?.userId && ( + {currentUserId === reaction.user?.userId && showReactionUserDetails && ( <> <br /> <div onClick={() => removeReaction(reaction.reactionName)}> <Typography.Caption className={styles.removeBtn}> - <FormattedMessage id="livechat.reaction.label.removeReaction" /> + Click to remove reaction </Typography.Caption> </div> </> @@ -68,18 +71,20 @@ export const ReactionListPanel = ({ </Typography.BodyBold> </div> - <div className={styles.userDetailsReaction}> - {reactionList.includes(reaction.reactionName) ? ( - <ReactionIcon - reactionConfigItem={ - config.find(({ name }) => name === reaction.reactionName)! - } - className={styles.reactionIcon} - /> - ) : ( - <FallbackReaction className={styles.reactionIcon} /> - )} - </div> + {showReactionUserDetails && ( + <div className={styles.userDetailsReaction}> + {reactionList.includes(reaction.reactionName) ? ( + <ReactionIcon + reactionConfigItem={ + config.find(({ name }) => name === reaction.reactionName)! + } + className={styles.reactionIcon} + /> + ) : ( + <FallbackReaction className={styles.reactionIcon} /> + )} + </div> + )} </div> </div> </Fragment> diff --git a/src/v4/social/components/StoryTab/StoryTabCommunity.tsx b/src/v4/social/components/StoryTab/StoryTabCommunity.tsx index 7a64e72ce..02f2326cf 100644 --- a/src/v4/social/components/StoryTab/StoryTabCommunity.tsx +++ b/src/v4/social/components/StoryTab/StoryTabCommunity.tsx @@ -1,7 +1,6 @@ import React from 'react'; import Truncate from 'react-truncate-markup'; -import { FileTrigger, Button } from 'react-aria-components'; -import { FormattedMessage } from 'react-intl'; +import { FileTrigger } from 'react-aria-components'; import { StoryRing } from '~/v4/social/elements/StoryRing/StoryRing'; import clsx from 'clsx'; import { useGetActiveStoriesByTarget } from '~/v4/social/hooks/useGetActiveStories'; @@ -13,25 +12,7 @@ import { useCommunityInfo } from '~/v4/social/hooks/useCommunityInfo'; import styles from './StoryTabCommunity.module.css'; import { CreateNewStoryButton } from '~/v4/social/elements/CreateNewStoryButton'; - -const AddStoryIcon = (props: React.SVGProps<SVGSVGElement>) => { - return ( - <svg - xmlns="http://www.w3.org/2000/svg" - width="24" - height="24" - viewBox="0 0 24 24" - fill="none" - stroke="currentColor" - strokeWidth="2" - strokeLinecap="round" - strokeLinejoin="round" - {...props} - > - <path d="M12 5v14M5 12h14"></path> - </svg> - ); -}; +import { CommunityAvatar } from '~/v4/social/elements/CommunityAvatar'; const ErrorIcon = (props: React.SVGProps<SVGSVGElement>) => { return ( @@ -53,16 +34,16 @@ const ErrorIcon = (props: React.SVGProps<SVGSVGElement>) => { }; interface StoryTabCommunityFeedProps { - pageId: string; - componentId: string; + pageId?: string; + componentId?: string; communityId: string; onStoryClick: () => void; onFileChange: (file: File | null) => void; } export const StoryTabCommunityFeed: React.FC<StoryTabCommunityFeedProps> = ({ - pageId, - componentId, + pageId = '*', + componentId = '*', communityId, onFileChange, onStoryClick, @@ -72,7 +53,7 @@ export const StoryTabCommunityFeed: React.FC<StoryTabCommunityFeedProps> = ({ targetType: 'community', options: { orderBy: 'asc', sortBy: 'createdAt' }, }); - const { avatarFileUrl, community } = useCommunityInfo(communityId); + const { community } = useCommunityInfo(communityId); const { currentUserId, client } = useSDK(); const { user } = useUser(currentUserId); @@ -90,7 +71,7 @@ export const StoryTabCommunityFeed: React.FC<StoryTabCommunityFeedProps> = ({ onStoryClick(); }; - if (!community?.isJoined || !hasStories) return null; + if (!hasStories && !hasStoryPermission) return null; return ( <div className={clsx(styles.storyTabContainer)}> @@ -106,9 +87,9 @@ export const StoryTabCommunityFeed: React.FC<StoryTabCommunityFeedProps> = ({ /> )} - <div className={clsx(styles.storyAvatarContainer)}> - <img src={avatarFileUrl} className={clsx(styles.storyAvatar)} onClick={handleOnClick} /> - </div> + <button className={clsx(styles.storyAvatarContainer)} onClick={handleOnClick}> + <CommunityAvatar pageId={pageId} componentId={componentId} community={community} /> + </button> {hasStoryPermission && ( <> @@ -118,16 +99,14 @@ export const StoryTabCommunityFeed: React.FC<StoryTabCommunityFeedProps> = ({ onFileChange(files[0]); }} > - <CreateNewStoryButton pageId="story_page" componentId="*" /> + <CreateNewStoryButton pageId={pageId} componentId={componentId} /> </FileTrigger> </> )} {isErrored && <ErrorIcon className={clsx(styles.errorIcon)} />} </div> <Truncate lines={1}> - <div className={clsx(styles.storyTitle)}> - <FormattedMessage id="storyTab.title" /> - </div> + <div className={clsx(styles.storyTitle)}>Story</div> </Truncate> </div> ); diff --git a/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx b/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx index 5d054da5d..18bf070ef 100644 --- a/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx +++ b/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx @@ -6,8 +6,8 @@ import { useGlobalStoryTargets } from '~/v4/social/hooks/collections/useGlobalSt const STORIES_PER_PAGE = 10; interface StoryTabGlobalFeedProps { - pageId: string; - componentId: string; + pageId?: string; + componentId?: string; goToViewStoryPage: (data: { storyTarget: Amity.StoryTarget; storyTargets: Amity.StoryTarget[]; @@ -15,8 +15,8 @@ interface StoryTabGlobalFeedProps { } export const StoryTabGlobalFeed = ({ - pageId, - componentId, + pageId = '*', + componentId = '*', goToViewStoryPage, }: StoryTabGlobalFeedProps) => { const { stories, isLoading, hasMore, loadMoreStories } = useGlobalStoryTargets({ diff --git a/src/v4/social/constants/default-avatar.ts b/src/v4/social/constants/default-avatar.ts deleted file mode 100644 index 8abc8af88..000000000 --- a/src/v4/social/constants/default-avatar.ts +++ /dev/null @@ -1 +0,0 @@ -export const communityProfileImageBackground = `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100%' height='100%' viewBox='0 0 64 64' fill='none'%3E%3Crect width='64' height='64' rx='32' fill='%23D9E5FC'/%3E%3Cpath d='M31.7539 19.2C33.2201 19.2 34.6262 19.7696 35.6629 20.7836C36.6997 21.7976 37.2821 23.1728 37.2821 24.6068C37.2821 26.0408 36.6997 27.4161 35.6629 28.4301C34.6262 29.4441 33.2201 30.0137 31.7539 30.0137C30.2877 30.0137 28.8816 29.4441 27.8449 28.4301C26.8081 27.4161 26.2257 26.0408 26.2257 24.6068C26.2257 23.1728 26.8081 21.7976 27.8449 20.7836C28.8816 19.7696 30.2877 19.2 31.7539 19.2ZM20.6975 23.062C21.582 23.062 22.4033 23.2937 23.1141 23.7108C22.8772 25.92 23.5406 28.1136 24.8989 29.8284C24.1092 31.3114 22.5297 32.331 20.6975 32.331C19.4408 32.331 18.2355 31.8427 17.3469 30.9736C16.4583 30.1044 15.959 28.9256 15.959 27.6965C15.959 26.4674 16.4583 25.2886 17.3469 24.4194C18.2355 23.5503 19.4408 23.062 20.6975 23.062ZM42.8103 23.062C44.067 23.062 45.2723 23.5503 46.1609 24.4194C47.0495 25.2886 47.5488 26.4674 47.5488 27.6965C47.5488 28.9256 47.0495 30.1044 46.1609 30.9736C45.2723 31.8427 44.067 32.331 42.8103 32.331C40.9781 32.331 39.3986 31.3114 38.6089 29.8284C39.9672 28.1136 40.6306 25.92 40.3937 23.7108C41.1045 23.2937 41.9258 23.062 42.8103 23.062ZM21.4872 38.8965C21.4872 35.6987 26.0835 33.1034 31.7539 33.1034C37.4243 33.1034 42.0206 35.6987 42.0206 38.8965V41.5999H21.4872V38.8965ZM12.8 41.5999V39.2827C12.8 37.1354 15.7853 35.3279 19.8288 34.8027C18.8969 35.8532 18.3283 37.3053 18.3283 38.8965V41.5999H12.8ZM50.7077 41.5999H45.1795V38.8965C45.1795 37.3053 44.6109 35.8532 43.679 34.8027C47.7225 35.3279 50.7077 37.1354 50.7077 39.2827V41.5999Z' fill='white'/%3E%3C/svg%3E")`; diff --git a/src/v4/social/elements/AspectRatioButton/AspectRatioButton.tsx b/src/v4/social/elements/AspectRatioButton/AspectRatioButton.tsx index 98bed4d63..cc1a057de 100644 --- a/src/v4/social/elements/AspectRatioButton/AspectRatioButton.tsx +++ b/src/v4/social/elements/AspectRatioButton/AspectRatioButton.tsx @@ -20,7 +20,7 @@ interface AspectRatioButtonProps { componentId?: string; defaultIconClassName?: string; imgIconClassName?: string; - onClick: () => void; + onPress: () => void; } export function AspectRatioButton({ @@ -28,7 +28,7 @@ export function AspectRatioButton({ componentId = '*', defaultIconClassName, imgIconClassName, - onClick, + onPress, }: AspectRatioButtonProps) { const elementId = 'aspect_ratio_button'; const { accessibilityId, config, defaultConfig, isExcluded, uiReference } = useAmityElement({ @@ -43,7 +43,7 @@ export function AspectRatioButton({ <IconComponent data-qa-anchor={accessibilityId} className={clsx(styles.aspectRatioButton)} - onClick={onClick} + onPress={onPress} defaultIcon={() => <AspectRatioSvg className={clsx(defaultIconClassName)} />} imgIcon={() => <img src={config.icon} alt={uiReference} className={clsx(imgIconClassName)} />} defaultIconName={defaultConfig.icon} diff --git a/src/v4/social/elements/BackButton/BackButton.tsx b/src/v4/social/elements/BackButton/BackButton.tsx index 888d0b6f3..f752063be 100644 --- a/src/v4/social/elements/BackButton/BackButton.tsx +++ b/src/v4/social/elements/BackButton/BackButton.tsx @@ -27,7 +27,7 @@ interface BackButtonProps { componentId?: string; defaultClassName?: string; imgClassName?: string; - onClick?: (e: React.MouseEvent) => void; + onPress?: () => void; } export const BackButton = ({ @@ -35,7 +35,7 @@ export const BackButton = ({ componentId = '*', defaultClassName, imgClassName, - onClick = () => {}, + onPress = () => {}, }: BackButtonProps) => { const elementId = 'back_button'; const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = @@ -55,7 +55,7 @@ export const BackButton = ({ imgIcon={() => <img src={config.icon} alt={uiReference} className={clsx(imgClassName)} />} defaultIconName={defaultConfig.icon} configIconName={config.icon} - onClick={onClick} + onPress={onPress} style={themeStyles} /> ); diff --git a/src/v4/social/elements/ClearButton/ClearButton.tsx b/src/v4/social/elements/ClearButton/ClearButton.tsx index 8149990de..736bc6ff9 100644 --- a/src/v4/social/elements/ClearButton/ClearButton.tsx +++ b/src/v4/social/elements/ClearButton/ClearButton.tsx @@ -24,7 +24,7 @@ interface ClearButtonProps { componentId?: string; defaultClassName?: string; imgClassName?: string; - onClick?: (e: React.MouseEvent) => void; + onPress?: () => void; } export const ClearButton = ({ @@ -32,7 +32,7 @@ export const ClearButton = ({ componentId = '*', defaultClassName, imgClassName, - onClick = () => {}, + onPress = () => {}, }: ClearButtonProps) => { const elementId = 'clear_button'; const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = @@ -45,13 +45,9 @@ export const ClearButton = ({ if (isExcluded) return null; return ( - <button - data-qa-anchor={accessibilityId} - className={styles.clearButton} - onClick={onClick} - style={themeStyles} - > + <button data-qa-anchor={accessibilityId} className={styles.clearButton} style={themeStyles}> <IconComponent + onPress={onPress} defaultIcon={() => <ClearButtonSvg className={defaultClassName} />} imgIcon={() => <img src={config.icon} alt={uiReference} className={imgClassName} />} defaultIconName={defaultConfig.icon} diff --git a/src/v4/social/elements/CloseButton/CloseButton.tsx b/src/v4/social/elements/CloseButton/CloseButton.tsx index 3aee16611..ce4d1492e 100644 --- a/src/v4/social/elements/CloseButton/CloseButton.tsx +++ b/src/v4/social/elements/CloseButton/CloseButton.tsx @@ -27,7 +27,7 @@ const CloseIconSVG = (props: React.SVGProps<SVGSVGElement>) => ( interface CloseButtonProps { pageId?: string; componentId?: string; - onClick?: (e: React.MouseEvent) => void; + onPress?: () => void; 'data-qa-anchor'?: string; defaultClassName?: string; imgClassName?: string; @@ -36,7 +36,7 @@ interface CloseButtonProps { export const CloseButton = ({ pageId = '*', componentId = '*', - onClick = () => {}, + onPress = () => {}, defaultClassName, imgClassName, }: CloseButtonProps) => { @@ -51,7 +51,7 @@ export const CloseButton = ({ return ( <IconComponent - onClick={onClick} + onPress={onPress} data-qa-anchor="close_button" defaultIcon={() => <CloseIconSVG className={clsx(styles.closeButton, defaultClassName)} />} imgIcon={() => <img src={config.icon} alt={uiReference} className={imgClassName} />} diff --git a/src/v4/social/elements/CommentButton/CommentButton.tsx b/src/v4/social/elements/CommentButton/CommentButton.tsx index 66b3d1796..e8f3d873e 100644 --- a/src/v4/social/elements/CommentButton/CommentButton.tsx +++ b/src/v4/social/elements/CommentButton/CommentButton.tsx @@ -26,7 +26,7 @@ interface CommentButtonProps { className?: string; defaultIconClassName?: string; imgIconClassName?: string; - onClick?: (e: React.MouseEvent) => void; + onPress?: () => void; } export function CommentButton({ @@ -36,7 +36,7 @@ export function CommentButton({ className = '', defaultIconClassName, imgIconClassName, - onClick = () => {}, + onPress = () => {}, }: CommentButtonProps) { const elementId = 'comment_button'; const { accessibilityId, config, defaultConfig, isExcluded, uiReference } = useAmityElement({ @@ -51,7 +51,7 @@ export function CommentButton({ <IconComponent className={clsx(className)} data-qa-anchor={accessibilityId} - onClick={onClick} + onPress={onPress} defaultIcon={() => ( <div className={clsx(styles.commentButton)}> <CommentSvg className={clsx(styles.commentButton__icon, defaultIconClassName)} /> diff --git a/src/v4/social/elements/CreateNewStoryButton/CreateNewStoryButton.tsx b/src/v4/social/elements/CreateNewStoryButton/CreateNewStoryButton.tsx index 40db14d9f..9c607c12f 100644 --- a/src/v4/social/elements/CreateNewStoryButton/CreateNewStoryButton.tsx +++ b/src/v4/social/elements/CreateNewStoryButton/CreateNewStoryButton.tsx @@ -27,7 +27,7 @@ interface BackButtonProps { componentId?: string; defaultClassName?: string; imgClassName?: string; - onClick?: (e: React.MouseEvent) => void; + onPress?: () => void; } export const CreateNewStoryButton = ({ @@ -35,7 +35,7 @@ export const CreateNewStoryButton = ({ componentId = '*', defaultClassName, imgClassName, - onClick = () => {}, + onPress = () => {}, }: BackButtonProps) => { const elementId = 'create_new_story_button'; const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = @@ -55,7 +55,7 @@ export const CreateNewStoryButton = ({ imgIcon={() => <img src={config.icon} alt={uiReference} className={clsx(imgClassName)} />} defaultIconName={defaultConfig.icon} configIconName={config.icon} - onClick={onClick} + onPress={onPress} style={themeStyles} /> ); diff --git a/src/v4/social/elements/HyperLinkButton/HyperLinkButton.tsx b/src/v4/social/elements/HyperLinkButton/HyperLinkButton.tsx index 006363fad..d99fb8841 100644 --- a/src/v4/social/elements/HyperLinkButton/HyperLinkButton.tsx +++ b/src/v4/social/elements/HyperLinkButton/HyperLinkButton.tsx @@ -29,7 +29,7 @@ interface HyperLinkButtonProps { componentId?: string; defaultClassName?: string; imgClassName?: string; - onClick: (e: React.MouseEvent) => void; + onPress: (e: React.MouseEvent) => void; } export const HyperLinkButton = ({ @@ -37,7 +37,7 @@ export const HyperLinkButton = ({ componentId = '*', defaultClassName, imgClassName, - onClick = () => {}, + onPress = () => {}, }: HyperLinkButtonProps) => { const elementId = 'story_hyperlink_button'; const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = @@ -57,7 +57,7 @@ export const HyperLinkButton = ({ imgIcon={() => <img src={config.icon} alt={uiReference} className={clsx(imgClassName)} />} defaultIconName={defaultConfig.icon} configIconName={config.icon} - onClick={onClick} + onPress={onPress} style={themeStyles} /> ); diff --git a/src/v4/social/elements/ImpressionButton/ImpressionButton.tsx b/src/v4/social/elements/ImpressionButton/ImpressionButton.tsx index 3e1a5a173..66362e22d 100644 --- a/src/v4/social/elements/ImpressionButton/ImpressionButton.tsx +++ b/src/v4/social/elements/ImpressionButton/ImpressionButton.tsx @@ -25,7 +25,7 @@ const ImpressionSvg = (props: React.SVGProps<SVGSVGElement>) => ( interface CommentButtonProps { pageId?: string; componentId?: string; - reach?: number; + reach?: number | null; defaultIconClassName?: string; imgIconClassName?: string; } diff --git a/src/v4/social/elements/OverflowMenuButton/OverflowMenuButton.tsx b/src/v4/social/elements/OverflowMenuButton/OverflowMenuButton.tsx index 551aa8abe..51f12498a 100644 --- a/src/v4/social/elements/OverflowMenuButton/OverflowMenuButton.tsx +++ b/src/v4/social/elements/OverflowMenuButton/OverflowMenuButton.tsx @@ -25,7 +25,7 @@ const OverflowMenuSvg = (props: React.SVGProps<SVGSVGElement>) => { interface OverflowMenuButtonProps { pageId?: string; componentId?: string; - onClick?: (e: React.MouseEvent) => void; + onPress?: () => void; defaultClassName?: string; imgClassName?: string; 'data-qa-anchor'?: string; @@ -34,7 +34,7 @@ interface OverflowMenuButtonProps { export const OverflowMenuButton = ({ pageId = '*', componentId = '*', - onClick = () => {}, + onPress = () => {}, defaultClassName, imgClassName, }: OverflowMenuButtonProps) => { @@ -47,7 +47,7 @@ export const OverflowMenuButton = ({ return ( <IconComponent - onClick={onClick} + onPress={onPress} defaultIcon={() => ( <OverflowMenuSvg className={clsx(styles.overflowMenuIcon, defaultClassName)} /> )} diff --git a/src/v4/social/elements/ReactButton/ReactButton.tsx b/src/v4/social/elements/ReactButton/ReactButton.tsx deleted file mode 100644 index e0686c2b0..000000000 --- a/src/v4/social/elements/ReactButton/ReactButton.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import React from 'react'; -import { Icon } from '~/v4/core/components/'; -import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; -import { UIReactButton, UIRemoteImageButton } from './styles'; -import { useTheme } from 'styled-components'; - -interface ReactButtonProps { - isLiked: boolean; - pageId?: 'story_page'; - componentId?: string; - onClick?: (e: React.MouseEvent) => void; - style?: React.CSSProperties; - children?: React.ReactNode; - 'data-qa-anchor'?: string; -} - -export const ReactButton = ({ - isLiked, - pageId = 'story_page', - componentId = '*', - onClick = () => {}, - style, - children, - ...props -}: ReactButtonProps) => { - const theme = useTheme(); - const { getConfig, isExcluded } = useCustomization(); - const elementConfig = getConfig(`${pageId}/*/story_reaction_button`); - const isElementExcluded = isExcluded(`${pageId}/*/story_reaction_button`); - const backgroundColor = elementConfig.background_color; - const likedIcon = elementConfig.liked_icon; - const unlikedIcon = elementConfig.unliked_icon; - - if (isElementExcluded) return null; - - const isLikedRemoteImage = - likedIcon && (likedIcon.startsWith('http://') || likedIcon.startsWith('https://')); - - const isUnlikedRemoteImage = - unlikedIcon && (unlikedIcon.startsWith('http://') || unlikedIcon.startsWith('https://')); - - return isLiked ? ( - isLikedRemoteImage ? ( - <UIRemoteImageButton - data-qa-anchor="reaction_button" - src={likedIcon} - onClick={onClick} - style={{ - ...style, - backgroundColor: backgroundColor || theme.v4.colors.secondary.default, - }} - {...props} - > - <span data-qa-anchor="reaction_button_text_view">{children}</span> - </UIRemoteImageButton> - ) : ( - <UIReactButton data-qa-anchor="reaction_button" onClick={onClick} {...props}> - <Icon name={likedIcon || 'LikedIcon'} /> - <span data-qa-anchor="reaction_button_text_view">{children}</span> - </UIReactButton> - ) - ) : isUnlikedRemoteImage ? ( - <UIRemoteImageButton - data-qa-anchor="reaction_button" - src={unlikedIcon} - onClick={onClick} - style={{ - ...style, - backgroundColor: backgroundColor || theme.v4.colors.secondary.default, - }} - {...props} - > - <span data-qa-anchor="reaction_button_text_view">{children}</span> - </UIRemoteImageButton> - ) : ( - <UIReactButton data-qa-anchor="reaction_button" onClick={onClick} {...props}> - <Icon name={unlikedIcon || 'LikeIcon'} /> - <span data-qa-anchor="reaction_button_text_view">{children}</span> - </UIReactButton> - ); -}; diff --git a/src/v4/social/elements/ReactButton/index.ts b/src/v4/social/elements/ReactButton/index.ts deleted file mode 100644 index adb77263e..000000000 --- a/src/v4/social/elements/ReactButton/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ReactButton } from './ReactButton'; diff --git a/src/v4/social/elements/ReactButton/styles.tsx b/src/v4/social/elements/ReactButton/styles.tsx deleted file mode 100644 index 467d54247..000000000 --- a/src/v4/social/elements/ReactButton/styles.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import styled from 'styled-components'; - -export const UIReactButton = styled.button` - ${({ theme }) => theme.typography.bodyBold}; - display: flex; - align-items: center; - justify-content: space-between; - gap: 0.25rem; - border-radius: 1.5rem; - padding: 0.5rem 0.625rem; - background-color: #292b32; - cursor: pointer; - border: none; - color: ${({ theme }) => theme.v4.colors.baseInverse.default}; -`; - -export const UIRemoteImageButton = styled.img` - width: 1.5rem; - height: 1.5rem; - cursor: pointer; - border: none; - outline: none; - padding: 0; - margin: 0; - display: flex; - align-items: center; - justify-content: center; - color: ${({ theme }) => theme.colors.baseInverse.default}; -`; diff --git a/src/v4/social/elements/SpeakerButton/SpeakerButton.tsx b/src/v4/social/elements/SpeakerButton/SpeakerButton.tsx index d2bc88edc..e09771408 100644 --- a/src/v4/social/elements/SpeakerButton/SpeakerButton.tsx +++ b/src/v4/social/elements/SpeakerButton/SpeakerButton.tsx @@ -47,7 +47,7 @@ interface SpeakerButtonProps { componentId?: string; defaultIconClassName?: string; imgIconClassName?: string; - onClick: () => void; + onPress: () => void; } export function SpeakerButton({ @@ -56,7 +56,7 @@ export function SpeakerButton({ componentId = '*', defaultIconClassName, imgIconClassName, - onClick, + onPress, }: SpeakerButtonProps) { const elementId = 'speaker_button'; const { accessibilityId, config, defaultConfig, isExcluded, uiReference } = useAmityElement({ @@ -71,7 +71,7 @@ export function SpeakerButton({ <IconComponent data-qa-anchor={accessibilityId} className={clsx(styles.speakerButton)} - onClick={onClick} + onPress={onPress} defaultIcon={() => (isMuted ? <SpeakerMuteSvg /> : <SpeakerUnmuteSvg />)} imgIcon={() => <img src={config.icon} alt={uiReference} className={clsx(imgIconClassName)} />} defaultIconName={defaultConfig.icon} diff --git a/src/v4/social/elements/index.ts b/src/v4/social/elements/index.ts index 1d5637e63..7acf1d2b7 100644 --- a/src/v4/social/elements/index.ts +++ b/src/v4/social/elements/index.ts @@ -6,7 +6,7 @@ export { CreateStoryButton } from './CreateStoryButton/CreateStoryButton'; export { HyperLinkButton } from './HyperLinkButton/HyperLinkButton'; export { ImpressionButton } from './ImpressionButton'; export { OverflowMenuButton } from './OverflowMenuButton/OverflowMenuButton'; -export { ReactButton } from './ReactButton'; +export { ReactionButton } from './ReactionButton'; export { SaveButton } from './SaveButton'; export { ShareStoryButton } from './ShareStoryButton/ShareStoryButton'; export { SpeakerButton } from './SpeakerButton'; diff --git a/src/v4/social/internal-components/Comment/UIComment.module.css b/src/v4/social/internal-components/Comment/UIComment.module.css index 8245f5366..84035a6f7 100644 --- a/src/v4/social/internal-components/Comment/UIComment.module.css +++ b/src/v4/social/internal-components/Comment/UIComment.module.css @@ -127,7 +127,7 @@ .reactionListButton { border: none; color: var(--asc-color-primary-default); - padding: 0; + padding: var(--asc-spacing-xxs2); } .content { diff --git a/src/v4/social/internal-components/Comment/index.tsx b/src/v4/social/internal-components/Comment/index.tsx index 326d052ab..573f7dbd2 100644 --- a/src/v4/social/internal-components/Comment/index.tsx +++ b/src/v4/social/internal-components/Comment/index.tsx @@ -49,6 +49,8 @@ function getCommentData(comment: Amity.Comment | null) { } interface CommentProps { + pageId?: string; + componentId: string; commentId: string; readonly?: boolean; userRoles?: string[]; @@ -62,7 +64,13 @@ interface CommentProps { shouldAllowInteraction?: boolean; } -export const Comment = ({ commentId, readonly, onClickReply }: CommentProps) => { +export const Comment = ({ + pageId = '*', + componentId = '*', + commentId, + readonly, + onClickReply, +}: CommentProps) => { const comment = useComment(commentId); const story = useGetStoryByStoryId(comment?.referenceId); const { members } = useCommunityMembersCollection(story?.community?.communityId); @@ -74,7 +82,7 @@ export const Comment = ({ commentId, readonly, onClickReply }: CommentProps) => const commentAuthor = useUser(comment?.userId); const commentAuthorAvatar = useImage({ fileId: commentAuthor?.avatarFileId, imageSize: 'small' }); - const { userRoles } = useSDK(); + const { userRoles, currentUserId } = useSDK(); const { toggleFlagComment, isFlaggedByMe } = useCommentFlaggedByMe(commentId); const [isEditing, setIsEditing] = useState(false); @@ -175,6 +183,8 @@ export const Comment = ({ commentId, readonly, onClickReply }: CommentProps) => const title = isReplyComment ? 'reply.delete' : 'comment.delete'; const content = isReplyComment ? 'reply.deleteBody' : 'comment.deleteBody'; confirm({ + pageId, + componentId, title: <FormattedMessage id={title} />, content: <FormattedMessage id={content} />, cancelText: formatMessage({ id: 'comment.deleteConfirmCancelText' }), @@ -183,7 +193,7 @@ export const Comment = ({ commentId, readonly, onClickReply }: CommentProps) => }); }; - const currentMember = members.find((member) => member.userId === comment?.userId); + const currentMember = members.find((member) => member.userId === currentUserId); const isCommunityModerator = isModerator(currentMember?.roles); const isMember = isCommunityMember(currentMember); diff --git a/src/v4/social/internal-components/CommentList/CommentList.tsx b/src/v4/social/internal-components/CommentList/CommentList.tsx index cf89b7681..cd9cf1ca0 100644 --- a/src/v4/social/internal-components/CommentList/CommentList.tsx +++ b/src/v4/social/internal-components/CommentList/CommentList.tsx @@ -9,6 +9,8 @@ import { CommentBubbleDeleted } from '~/v4/social/elements/CommentBubbleDeleted' interface CommentListProps { parentId?: string; + pageId?: string; + componentId?: string; referenceId?: string; referenceType: Amity.CommentReferenceType; readonly?: boolean; @@ -21,6 +23,8 @@ interface CommentListProps { } export const CommentList = ({ + pageId = '*', + componentId = '*', parentId, referenceId, referenceType, @@ -64,6 +68,8 @@ export const CommentList = ({ return comments.map((comment) => ( <Comment key={comment.commentId} + pageId={pageId} + componentId={componentId} commentId={comment.commentId} readonly={readonly} onClickReply={() => onClickReply?.(comment as Amity.Comment)} diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index 344b5529f..e8f4898dc 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -39,7 +39,8 @@ export const renderer: CustomRenderer = ({ const { client } = useSDK(); const isLiked = !!(story && story.myReactions && story.myReactions.includes(LIKE_REACTION_KEY)); - const totalLikes = story.reactions[LIKE_REACTION_KEY] || 0; + const reactionsCount = story.reactionsCount || 0; + const myReactions = story.myReactions || []; const { storyId, @@ -274,8 +275,9 @@ export const renderer: CustomRenderer = ({ syncState={syncState} reach={reach} commentsCount={commentsCount} - totalLikes={totalLikes} + reactionsCount={reactionsCount} isLiked={isLiked} + myReactions={myReactions} onClickComment={openCommentSheet} showImpression={isCreator || haveStoryPermission} isMember={isMember} diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/Footer.module.css b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/Footer.module.css index a77ad0ad2..86df665b9 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/Footer.module.css +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/Footer.module.css @@ -61,9 +61,9 @@ display: flex; padding: 8px 10px; align-items: center; - gap: 8px; + gap: 0.5rem; border-radius: 24px; - background: var(--asc-color-base-default); + background: var(--asc-color-secondary-default); } .viewStoryCommentIcon { diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx index b3b7f741a..02e771444 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx @@ -2,11 +2,10 @@ import React from 'react'; import { DotsIcon, ErrorIcon } from '~/icons'; import { useIntl } from 'react-intl'; -import millify from 'millify'; import { ReactionRepository } from '@amityco/ts-sdk'; import { LIKE_REACTION_KEY } from '~/constants'; import Spinner from '~/social/components/Spinner'; -import { ImpressionButton, ReactButton } from '~/v4/social/elements'; +import { ImpressionButton, ReactionButton } from '~/v4/social/elements'; import { useNotifications } from '~/v4/core/providers/NotificationProvider'; import { CommentButton } from '~/v4/social/elements/CommentButton/CommentButton'; @@ -19,27 +18,29 @@ const Footer: React.FC< showImpression: boolean; reach: number | null; commentsCount: number; - totalLikes: number; + reactionsCount: number; isLiked: boolean; onClickComment: () => void; syncState?: Amity.SyncState; isMember?: boolean; + myReactions: string[]; }> > = ({ syncState, reach, commentsCount, - totalLikes, + reactionsCount, isLiked, storyId, onClickComment, showImpression, isMember, + myReactions, }) => { const notification = useNotifications(); const { formatMessage } = useIntl(); - const handleLike = async () => { + const handleClickReaction = async () => { try { if (!isMember) { notification.show({ @@ -95,11 +96,14 @@ const Footer: React.FC< defaultIconClassName={clsx(styles.viewStoryCommentIcon)} pageId="story_page" commentsCount={commentsCount} - onClick={onClickComment} + onPress={onClickComment} + /> + <ReactionButton + onReactionClick={handleClickReaction} + pageId="story_page" + myReactions={myReactions} + reactionsCount={reactionsCount} /> - <ReactButton onClick={handleLike} pageId="story_page" isLiked={isLiked}> - {millify(totalLikes || 0)} - </ReactButton> </div> </div> ); diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/Header.module.css b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/Header.module.css index e0ad752d7..5c6bf4bdb 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/Header.module.css +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/Header.module.css @@ -53,12 +53,12 @@ } .playStoryButton { - color: var(--asc-color-white); + fill: var(--asc-color-white); cursor: pointer; } .pauseStoryButton { - color: var(--asc-color-white); + fill: var(--asc-color-white); cursor: pointer; } diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/index.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/index.tsx index 1900d36f0..febef076b 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/index.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/index.tsx @@ -80,11 +80,11 @@ const Header: React.FC< onClick={onPause} /> )} - {isHaveActions && <OverflowMenuButton pageId="story_page" onClick={onAction} />} + {isHaveActions && <OverflowMenuButton pageId="story_page" onPress={onAction} />} <CloseButton defaultClassName={clsx(styles.closeButton)} pageId="story_page" - onClick={onClose} + onPress={onClose} /> </div> </div> diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index 7ca171cec..c35c329aa 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -222,12 +222,12 @@ export const PlainDraftStoryPage = ({ <div id="asc-uikit-create-story" className={styles.draftPageContainer}> <div className={styles.headerContainer}> <div className={styles.header}> - <BackButton pageId={pageId} onClick={discardCreateStory} /> + <BackButton pageId={pageId} onPress={discardCreateStory} /> <div className={styles.topRightButtons}> {mediaType?.type === 'image' && ( - <AspectRatioButton pageId={pageId} onClick={onClickImageMode} /> + <AspectRatioButton pageId={pageId} onPress={onClickImageMode} /> )} - <HyperLinkButton pageId={pageId} onClick={handleOnClickHyperLinkActionButton} /> + <HyperLinkButton pageId={pageId} onPress={handleOnClickHyperLinkActionButton} /> </div> </div> </div> diff --git a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx index b824eb224..2a5b01678 100644 --- a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx @@ -31,7 +31,7 @@ interface GlobalFeedStoryProps { pageId?: string; targetId: string; targetIds: string[]; - onChangePage: () => void; + onChangePage?: () => void; onClickStory: (targetId: string) => void; goToDraftStoryPage: (data: { mediaType: { type: 'image' | 'video'; url: string }; @@ -125,7 +125,7 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ okText: formatMessage({ id: 'delete' }), onOk: async () => { previousStory(); - if (isLastStory) onChangePage(); + if (isLastStory) onChangePage?.(); await StoryRepository.softDeleteStory(storyId); notification.success({ content: formatMessage({ id: 'storyViewer.notification.deleted' }), @@ -133,7 +133,7 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ if (isLastStory && stories.length > 1) { setCurrentIndex(currentIndex - 1); } else if (stories.length === 1) { - onChangePage(); + onChangePage?.(); } }, }); @@ -198,7 +198,7 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ setFile(null); }; - const addStoryButton = <CreateNewStoryButton pageId={pageId} onClick={handleAddIconClick} />; + const addStoryButton = <CreateNewStoryButton pageId={pageId} onPress={handleAddIconClick} />; const formattedStories = stories?.map((story) => { const isImage = story?.dataType === 'image'; @@ -240,7 +240,7 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ const nextTargetId = targetIds[nextTargetIndex]; onClickStory(nextTargetId); } else { - onChangePage(); + onChangePage?.(); } setCurrentIndex(0); return; @@ -257,7 +257,7 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ const previousTargetId = targetIds[previousTargetIndex]; onClickStory(previousTargetId); } else { - onChangePage(); + onChangePage?.(); } setCurrentIndex(0); return; diff --git a/src/v4/social/pages/StoryPage/StoryPage.module.css b/src/v4/social/pages/StoryPage/StoryPage.module.css index fd9282080..7036c8178 100644 --- a/src/v4/social/pages/StoryPage/StoryPage.module.css +++ b/src/v4/social/pages/StoryPage/StoryPage.module.css @@ -43,8 +43,9 @@ flex-direction: column; align-items: center; justify-content: center; - width: 100%; - height: 100%; + max-width: 23.4375rem; + max-height: 43.5625rem; + flex-shrink: 0; } .storyWrapper { @@ -52,7 +53,7 @@ justify-content: center; align-items: center; width: 100%; - height: 100vh; + height: 100%; gap: 1rem; overflow: hidden; } From 3828a6911c1ddc364a1e1ad10cf3ebd8fd7bb128 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Fri, 21 Jun 2024 15:50:38 +0700 Subject: [PATCH 154/300] feat: skeleton loader (#407) --- .../CommunityItem.module.css | 58 +++++++++++ .../CommunitySearchResult/CommunityItem.tsx | 85 +++++++++++++++++ .../CommunityItemSkeleton.module.css | 74 +++++++++++++++ .../CommunityItemSkeleton.tsx | 56 +++++++++++ .../CommunitySearchResult.tsx | 95 ++++--------------- .../MyCommunities/MyCommunities.tsx | 1 + .../UserSearchItem.module.css | 41 ++++++++ .../UserSearchResult/UserSearchItem.tsx | 25 +++++ .../UserSearchItemSkeleton.module.css | 74 +++++++++++++++ .../UserSearchItemSkeleton.tsx | 54 +++++++++++ .../UserSearchResult.module.css | 42 -------- .../UserSearchResult/UserSearchResult.tsx | 27 ++---- .../UserAvatar/UserAvatar.tsx | 6 +- .../SocialGlobalSearchPage.tsx | 2 + 14 files changed, 500 insertions(+), 140 deletions(-) create mode 100644 src/v4/social/components/CommunitySearchResult/CommunityItem.module.css create mode 100644 src/v4/social/components/CommunitySearchResult/CommunityItem.tsx create mode 100644 src/v4/social/components/CommunitySearchResult/CommunityItemSkeleton.module.css create mode 100644 src/v4/social/components/CommunitySearchResult/CommunityItemSkeleton.tsx create mode 100644 src/v4/social/components/UserSearchResult/UserSearchItem.module.css create mode 100644 src/v4/social/components/UserSearchResult/UserSearchItem.tsx create mode 100644 src/v4/social/components/UserSearchResult/UserSearchItemSkeleton.module.css create mode 100644 src/v4/social/components/UserSearchResult/UserSearchItemSkeleton.tsx diff --git a/src/v4/social/components/CommunitySearchResult/CommunityItem.module.css b/src/v4/social/components/CommunitySearchResult/CommunityItem.module.css new file mode 100644 index 000000000..f2d368a78 --- /dev/null +++ b/src/v4/social/components/CommunitySearchResult/CommunityItem.module.css @@ -0,0 +1,58 @@ +.communityItem { + display: grid; + grid-template-columns: 2.5rem minmax(0, 1fr); + gap: 1rem; + padding-top: 1rem; + padding-bottom: 1rem; + width: 100%; +} + +.communityItem:not(:first-child) { + border-top: 0.0625rem solid var(--asc-color-base-shade4); +} + +.communityItem__leftPane { + display: flex; + justify-content: center; + align-items: center; +} + +.communityItem__rightPane { + display: flex; + justify-content: center; + align-items: start; + flex-direction: column; + gap: 0.25rem; + width: 100%; +} + +.communityItem__communityName { + display: flex; + justify-content: start; + align-items: center; + gap: 0.25rem; + width: 100%; +} + +.communityItem__communityName__private { + width: 1.25rem; + height: 1.25rem; + display: flex; + justify-content: center; + align-items: center; + padding-top: 0.22rem; + padding-bottom: 0.28rem; +} + +.communityItem__communityCategory { + display: flex; + justify-content: center; + align-items: center; + gap: 0.25rem; +} + +.communityItem__communityMemberCount { + display: flex; + justify-content: center; + align-items: center; +} diff --git a/src/v4/social/components/CommunitySearchResult/CommunityItem.tsx b/src/v4/social/components/CommunitySearchResult/CommunityItem.tsx new file mode 100644 index 000000000..420ccf68a --- /dev/null +++ b/src/v4/social/components/CommunitySearchResult/CommunityItem.tsx @@ -0,0 +1,85 @@ +import React from 'react'; +import useCategoriesByIds from '~/social/hooks/useCategoriesByIds'; +import { CommunityAvatar } from '~/v4/social/elements/CommunityAvatar'; +import { CommunityDisplayName } from '~/v4/social/elements/CommunityDisplayName'; +import { CommunityOfficialBadge } from '~/v4/social/elements/CommunityOfficialBadge'; +import { CommunityPrivateBadge } from '~/v4/social/elements/CommunityPrivateBadge'; +import { CommunityCategoryName } from '~/v4/social/elements/CommunityCategoryName'; +import { CommunityMembersCount } from '~/v4/social/elements/CommunityMembersCount'; +import styles from './CommunityItem.module.css'; + +const CommunityCategories = ({ + community, + pageId, + componentId, +}: { + community: Amity.Community; + pageId: string; + componentId: string; +}) => { + const categories = useCategoriesByIds(community.categoryIds); + + const maxCategoriesLength = 3; + + const overflowCategoriesLength = categories.length - maxCategoriesLength; + + return ( + <> + {categories.slice(0, 3).map((category) => ( + <CommunityCategoryName + pageId={pageId} + componentId={componentId} + categoryName={category.name} + /> + ))} + {overflowCategoriesLength > 0 && ( + <CommunityCategoryName + pageId={pageId} + componentId={componentId} + categoryName={`+${overflowCategoriesLength}`} + /> + )} + </> + ); +}; + +export const CommunityItem = ({ + community, + pageId = '*', + componentId = '*', +}: { + community: Amity.Community; + pageId: string; + componentId: string; +}) => { + return ( + <div key={community.communityId} className={styles.communityItem}> + <div className={styles.communityItem__leftPane}> + <CommunityAvatar pageId={pageId} componentId={componentId} community={community} /> + </div> + <div className={styles.communityItem__rightPane}> + <div className={styles.communityItem__communityName}> + {!community.isPublic && ( + <div className={styles.communityItem__communityName__private}> + <CommunityPrivateBadge pageId={pageId} componentId={componentId} /> + </div> + )} + <CommunityDisplayName pageId={pageId} componentId={componentId} community={community} /> + {community.isOfficial && ( + <CommunityOfficialBadge pageId={pageId} componentId={componentId} /> + )} + </div> + <div className={styles.communityItem__communityCategory}> + <CommunityCategories pageId={pageId} componentId={componentId} community={community} /> + </div> + <div className={styles.communityItem__communityMemberCount}> + <CommunityMembersCount + pageId={pageId} + componentId={componentId} + memberCount={community.membersCount} + /> + </div> + </div> + </div> + ); +}; diff --git a/src/v4/social/components/CommunitySearchResult/CommunityItemSkeleton.module.css b/src/v4/social/components/CommunitySearchResult/CommunityItemSkeleton.module.css new file mode 100644 index 000000000..276625c95 --- /dev/null +++ b/src/v4/social/components/CommunitySearchResult/CommunityItemSkeleton.module.css @@ -0,0 +1,74 @@ +.communityItemSkeleton { + display: grid; + grid-template-columns: 2.5rem minmax(0, 1fr); + gap: 1rem; + padding-top: 1rem; + margin-bottom: 1.5rem; + height: 3.5rem; + background-color: var(--asc-color-base-background); + width: 100%; +} + +.communityItemSkeleton:not(:first-child) { + border-top: 0.0625rem solid var(--asc-color-base-shade4); +} + +.communityItemSkeleton__leftPane { + display: flex; + justify-content: center; + align-items: center; +} + +.communityItemSkeleton__information { + display: flex; + flex-direction: column; + justify-content: center; + align-items: start; + gap: 0.75rem; + width: 100%; +} + +.communityItemSkeleton__content { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.communityItemSkeleton__animation { + animation: skeleton-pulse 1.5s ease-in-out infinite; +} + +.communityItemSkeleton__userAvatar { + width: 2.5rem; + height: 2.5rem; + border-radius: 50%; + background-color: var(--asc-color-base-shade4); +} + +.communityItemSkeleton__information__title { + border-radius: 0.75rem; + background-color: var(--asc-color-base-shade4); + width: 8.75rem; + height: 0.5rem; +} + +.communityItemSkeleton__information__subtitle { + border-radius: 0.75rem; + background-color: var(--asc-color-base-shade4); + width: 6.75rem; + height: 0.5rem; +} + +@keyframes skeleton-pulse { + 0% { + opacity: 0.6; + } + + 50% { + opacity: 1; + } + + 100% { + opacity: 0.6; + } +} diff --git a/src/v4/social/components/CommunitySearchResult/CommunityItemSkeleton.tsx b/src/v4/social/components/CommunitySearchResult/CommunityItemSkeleton.tsx new file mode 100644 index 000000000..f9f555ba5 --- /dev/null +++ b/src/v4/social/components/CommunitySearchResult/CommunityItemSkeleton.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import clsx from 'clsx'; +import styles from './CommunityItemSkeleton.module.css'; +import { useAmityComponent } from '~/v4/core/hooks/uikit'; + +interface CommunityItemSkeletonProps { + pageId?: string; + componentId?: string; +} + +export const CommunityItemSkeleton = ({ + pageId = '*', + componentId = '*', +}: CommunityItemSkeletonProps) => { + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityComponent({ + pageId, + componentId, + }); + + return ( + <div + data-qa-anchor={accessibilityId} + style={themeStyles} + className={clsx(styles.communityItemSkeleton, styles.communityItemSkeleton__animation)} + > + <div className={styles.communityItemSkeleton__leftPane}> + <div + className={clsx( + styles.communityItemSkeleton__userAvatar, + styles.communityItemSkeleton__animation, + )} + /> + </div> + <div + className={clsx( + styles.communityItemSkeleton__information, + styles.communityItemSkeleton__animation, + )} + > + <div + className={clsx( + styles.communityItemSkeleton__information__title, + styles.communityItemSkeleton__animation, + )} + /> + <div + className={clsx( + styles.communityItemSkeleton__information__subtitle, + styles.communityItemSkeleton__animation, + )} + /> + </div> + </div> + ); +}; diff --git a/src/v4/social/components/CommunitySearchResult/CommunitySearchResult.tsx b/src/v4/social/components/CommunitySearchResult/CommunitySearchResult.tsx index 2b332bd11..f17490a88 100644 --- a/src/v4/social/components/CommunitySearchResult/CommunitySearchResult.tsx +++ b/src/v4/social/components/CommunitySearchResult/CommunitySearchResult.tsx @@ -1,59 +1,21 @@ import React, { useRef } from 'react'; import styles from './CommunitySearchResult.module.css'; -import { CommunityAvatar } from '~/v4/social/elements/CommunityAvatar'; -import { CommunityDisplayName } from '~/v4/social/elements/CommunityDisplayName'; -import { CommunityOfficialBadge } from '~/v4/social/elements/CommunityOfficialBadge'; -import { CommunityPrivateBadge } from '~/v4/social/elements/CommunityPrivateBadge'; -import { CommunityCategoryName } from '~/v4/social/elements/CommunityCategoryName'; -import { CommunityMembersCount } from '~/v4/social/elements/CommunityMembersCount'; import useIntersectionObserver from '~/v4/core/hooks/useIntersectionObserver'; import { useAmityComponent } from '~/v4/core/hooks/uikit'; -import useCategoriesByIds from '~/social/hooks/useCategoriesByIds'; - -const CommunityCategories = ({ - community, - pageId, - componentId, -}: { - community: Amity.Community; - pageId: string; - componentId: string; -}) => { - const categories = useCategoriesByIds(community.categoryIds); - - const maxCategoriesLength = 3; - - const overflowCategoriesLength = categories.length - maxCategoriesLength; - - return ( - <> - {categories.slice(0, 3).map((category) => ( - <CommunityCategoryName - pageId={pageId} - componentId={componentId} - categoryName={category.name} - /> - ))} - {overflowCategoriesLength > 0 && ( - <CommunityCategoryName - pageId={pageId} - componentId={componentId} - categoryName={`+${overflowCategoriesLength}`} - /> - )} - </> - ); -}; +import { CommunityItem } from './CommunityItem'; +import { CommunityItemSkeleton } from './CommunityItemSkeleton'; interface CommunitySearchResultProps { pageId?: string; communityCollection: Amity.Community[]; + isLoading: boolean; onLoadMore: () => void; } export const CommunitySearchResult = ({ pageId = '*', communityCollection = [], + // isLoading, onLoadMore, }: CommunitySearchResultProps) => { const componentId = 'community_search_result'; @@ -65,48 +27,25 @@ export const CommunitySearchResult = ({ const intersectionRef = useRef<HTMLDivElement>(null); + const isLoading = true; + useIntersectionObserver({ onIntersect: () => onLoadMore(), ref: intersectionRef }); return ( <div className={styles.communitySearchResult} style={themeStyles}> {communityCollection.map((community: Amity.Community) => ( - <div key={community.communityId} className={styles.communitySearchResult__communityItem}> - <div className={styles.communitySearchResult__communityItem__leftPane}> - <CommunityAvatar pageId={pageId} componentId={componentId} community={community} /> - </div> - <div className={styles.communitySearchResult__communityItem__rightPane}> - <div className={styles.communityItem__communityName}> - {!community.isPublic && ( - <div className={styles.communityItem__communityName__private}> - <CommunityPrivateBadge pageId={pageId} componentId={componentId} /> - </div> - )} - <CommunityDisplayName - pageId={pageId} - componentId={componentId} - community={community} - /> - {community.isOfficial && ( - <CommunityOfficialBadge pageId={pageId} componentId={componentId} /> - )} - </div> - <div className={styles.communityItem__communityCategory}> - <CommunityCategories - pageId={pageId} - componentId={componentId} - community={community} - /> - </div> - <div className={styles.communityItem__communityMemberCount}> - <CommunityMembersCount - pageId={pageId} - componentId={componentId} - memberCount={community.membersCount} - /> - </div> - </div> - </div> + <CommunityItem + key={community.communityId} + community={community} + pageId={pageId} + componentId={componentId} + /> ))} + {isLoading + ? Array.from({ length: 5 }).map((_, index) => ( + <CommunityItemSkeleton key={index} pageId={pageId} componentId={componentId} /> + )) + : null} <div ref={intersectionRef} /> </div> ); diff --git a/src/v4/social/components/MyCommunities/MyCommunities.tsx b/src/v4/social/components/MyCommunities/MyCommunities.tsx index 638a7c993..093ef0e0d 100644 --- a/src/v4/social/components/MyCommunities/MyCommunities.tsx +++ b/src/v4/social/components/MyCommunities/MyCommunities.tsx @@ -26,6 +26,7 @@ export const MyCommunities = ({ pageId = '*' }: MyCommunitiesProps) => { <CommunitySearchResult pageId={pageId} communityCollection={communities} + isLoading={isLoading} onLoadMore={() => { if (hasMore && isLoading === false) { loadMore(); diff --git a/src/v4/social/components/UserSearchResult/UserSearchItem.module.css b/src/v4/social/components/UserSearchResult/UserSearchItem.module.css new file mode 100644 index 000000000..57a641301 --- /dev/null +++ b/src/v4/social/components/UserSearchResult/UserSearchItem.module.css @@ -0,0 +1,41 @@ +.userItem { + display: grid; + grid-template-columns: 3.75rem minmax(0, 1fr); + padding-top: 0.5rem; + padding-bottom: 0.5rem; + width: 100%; +} + +.userItem__leftPane { + display: flex; + justify-content: center; + align-items: center; +} + +.userItem__rightPane { + display: flex; + justify-content: center; + align-items: center; + width: 100%; +} + +.userItem__avatar { + width: 2.5rem; + height: 2.5rem; +} + +.userItem__userName { + display: flex; + justify-content: start; + align-items: last baseline; + gap: 0.25rem; + width: 100%; + color: var(--asc-color-base-default); + overflow: hidden; + text-overflow: ellipsis; +} + +.userItem__userName__text { + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/src/v4/social/components/UserSearchResult/UserSearchItem.tsx b/src/v4/social/components/UserSearchResult/UserSearchItem.tsx new file mode 100644 index 000000000..a1fc8cf74 --- /dev/null +++ b/src/v4/social/components/UserSearchResult/UserSearchItem.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { UserAvatar } from '~/v4/social/internal-components/UserAvatar/UserAvatar'; +import { Typography } from '~/v4/core/components/index'; +import styles from './UserSearchItem.module.css'; + +interface UserSearchItemProps { + user: Amity.User; +} + +export const UserSearchItem = ({ user }: UserSearchItemProps) => { + return ( + <div key={user.userId} className={styles.userItem}> + <div className={styles.userItem__leftPane}> + <UserAvatar userId={user.userId} className={styles.userItem__avatar} /> + </div> + <div className={styles.userItem__rightPane}> + <div className={styles.userItem__userName}> + <Typography.BodyBold className={styles.userItem__userName__text}> + {user.displayName} + </Typography.BodyBold> + </div> + </div> + </div> + ); +}; diff --git a/src/v4/social/components/UserSearchResult/UserSearchItemSkeleton.module.css b/src/v4/social/components/UserSearchResult/UserSearchItemSkeleton.module.css new file mode 100644 index 000000000..77035e40b --- /dev/null +++ b/src/v4/social/components/UserSearchResult/UserSearchItemSkeleton.module.css @@ -0,0 +1,74 @@ +.userSearchItemSkeleton { + display: grid; + grid-template-columns: 2.5rem minmax(0, 1fr); + gap: 1rem; + padding-top: 1rem; + margin-bottom: 1.5rem; + height: 3.5rem; + background-color: var(--asc-color-base-background); + width: 100%; +} + +.userSearchItemSkeleton:not(:first-child) { + border-top: 0.0625rem solid var(--asc-color-base-shade4); +} + +.userSearchItemSkeleton__leftPane { + display: flex; + justify-content: center; + align-items: center; +} + +.userSearchItemSkeleton__information { + display: flex; + flex-direction: column; + justify-content: center; + align-items: start; + gap: 0.75rem; + width: 100%; +} + +.userSearchItemSkeleton__content { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.userSearchItemSkeleton__animation { + animation: skeleton-pulse 1.5s ease-in-out infinite; +} + +.userSearchItemSkeleton__userAvatar { + width: 2.5rem; + height: 2.5rem; + border-radius: 50%; + background-color: var(--asc-color-base-shade4); +} + +.userSearchItemSkeleton__information__title { + border-radius: 0.75rem; + background-color: var(--asc-color-base-shade4); + width: 8.75rem; + height: 0.5rem; +} + +.userSearchItemSkeleton__information__subtitle { + border-radius: 0.75rem; + background-color: var(--asc-color-base-shade4); + width: 6.75rem; + height: 0.5rem; +} + +@keyframes skeleton-pulse { + 0% { + opacity: 0.6; + } + + 50% { + opacity: 1; + } + + 100% { + opacity: 0.6; + } +} diff --git a/src/v4/social/components/UserSearchResult/UserSearchItemSkeleton.tsx b/src/v4/social/components/UserSearchResult/UserSearchItemSkeleton.tsx new file mode 100644 index 000000000..db2450f6f --- /dev/null +++ b/src/v4/social/components/UserSearchResult/UserSearchItemSkeleton.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import clsx from 'clsx'; +import styles from './UserSearchItemSkeleton.module.css'; +import { useAmityComponent } from '~/v4/core/hooks/uikit'; + +interface UserSearchItemSkeletonProps { + pageId?: string; + componentId?: string; +} + +export const UserSearchItemSkeleton = ({ + pageId = '*', + componentId = '*', +}: UserSearchItemSkeletonProps) => { + const { accessibilityId } = useAmityComponent({ + pageId, + componentId, + }); + + return ( + <div + data-qa-anchor={accessibilityId} + className={clsx(styles.userSearchItemSkeleton, styles.userSearchItemSkeleton__animation)} + > + <div className={styles.userSearchItemSkeleton__leftPane}> + <div + className={clsx( + styles.userSearchItemSkeleton__userAvatar, + styles.userSearchItemSkeleton__animation, + )} + /> + </div> + <div + className={clsx( + styles.userSearchItemSkeleton__information, + styles.userSearchItemSkeleton__animation, + )} + > + <div + className={clsx( + styles.userSearchItemSkeleton__information__title, + styles.userSearchItemSkeleton__animation, + )} + /> + <div + className={clsx( + styles.userSearchItemSkeleton__information__subtitle, + styles.userSearchItemSkeleton__animation, + )} + /> + </div> + </div> + ); +}; diff --git a/src/v4/social/components/UserSearchResult/UserSearchResult.module.css b/src/v4/social/components/UserSearchResult/UserSearchResult.module.css index d6f933f8d..c1001919d 100644 --- a/src/v4/social/components/UserSearchResult/UserSearchResult.module.css +++ b/src/v4/social/components/UserSearchResult/UserSearchResult.module.css @@ -7,45 +7,3 @@ background-color: var(--asc-color-base-background); overflow-x: hidden; } - -.userSearchResult__userItem { - display: grid; - grid-template-columns: 3.75rem minmax(0, 1fr); - padding-top: 0.5rem; - padding-bottom: 0.5rem; - width: 100%; -} - -.userSearchResult__userItem__leftPane { - display: flex; - justify-content: center; - align-items: center; -} - -.userSearchResult__userItem__rightPane { - display: flex; - justify-content: center; - align-items: center; - width: 100%; -} - -.userSearchResult__userItem__avatar { - width: 2.5rem; - height: 2.5rem; -} - -.userItem__userName { - display: flex; - justify-content: start; - align-items: last baseline; - gap: 0.25rem; - width: 100%; - color: var(--asc-color-base-default); - overflow: hidden; - text-overflow: ellipsis; -} - -.userItem__userName__text { - overflow: hidden; - text-overflow: ellipsis; -} diff --git a/src/v4/social/components/UserSearchResult/UserSearchResult.tsx b/src/v4/social/components/UserSearchResult/UserSearchResult.tsx index 7e5de68c6..3856b1074 100644 --- a/src/v4/social/components/UserSearchResult/UserSearchResult.tsx +++ b/src/v4/social/components/UserSearchResult/UserSearchResult.tsx @@ -2,18 +2,20 @@ import React, { useRef } from 'react'; import styles from './UserSearchResult.module.css'; import useIntersectionObserver from '~/v4/core/hooks/useIntersectionObserver'; import { useAmityComponent } from '~/v4/core/hooks/uikit'; -import { UserAvatar } from '../../internal-components/UserAvatar/UserAvatar'; -import { Typography } from '~/v4/core/components/index'; +import { UserSearchItem } from './UserSearchItem'; +import { UserSearchItemSkeleton } from './UserSearchItemSkeleton'; interface UserSearchResultProps { pageId?: string; userCollection: Amity.User[]; + isLoading: boolean; onLoadMore: () => void; } export const UserSearchResult = ({ pageId = '*', userCollection = [], + isLoading, onLoadMore, }: UserSearchResultProps) => { const componentId = 'user_search_result'; @@ -30,22 +32,13 @@ export const UserSearchResult = ({ return ( <div className={styles.userSearchResult} style={themeStyles}> {userCollection.map((user) => ( - <div key={user.userId} className={styles.userSearchResult__userItem}> - <div className={styles.userSearchResult__userItem__leftPane}> - <UserAvatar - userId={user.userId} - className={styles.userSearchResult__userItem__avatar} - /> - </div> - <div className={styles.userSearchResult__userItem__rightPane}> - <div className={styles.userItem__userName}> - <Typography.BodyBold className={styles.userItem__userName__text}> - {user.displayName} - </Typography.BodyBold> - </div> - </div> - </div> + <UserSearchItem key={user.userId} user={user} /> ))} + {isLoading + ? Array.from({ length: 5 }).map((_, index) => ( + <UserSearchItemSkeleton key={index} pageId={pageId} componentId={componentId} /> + )) + : null} <div ref={intersectionRef} /> </div> ); diff --git a/src/v4/social/internal-components/UserAvatar/UserAvatar.tsx b/src/v4/social/internal-components/UserAvatar/UserAvatar.tsx index dab9a2934..bd94f799d 100644 --- a/src/v4/social/internal-components/UserAvatar/UserAvatar.tsx +++ b/src/v4/social/internal-components/UserAvatar/UserAvatar.tsx @@ -1,7 +1,7 @@ import clsx from 'clsx'; import React from 'react'; -import useImage from '~/core/hooks/useImage'; -import useUser from '~/core/hooks/useUser'; +import useUser from '~/v4/core/hooks/objects/useUser'; +import useImage from '~/v4/core/hooks/useImage'; import styles from './UserAvatar.module.css'; const UserSvg = ({ className, ...props }: React.SVGProps<SVGSVGElement>) => ( @@ -32,7 +32,7 @@ interface UserAvatarProps { } export function UserAvatar({ userId, className }: UserAvatarProps) { - const user = useUser(userId); + const { user } = useUser(userId); const userImage = useImage({ fileId: user?.avatar?.fileId }); diff --git a/src/v4/social/pages/SocialGlobalSearchPage/SocialGlobalSearchPage.tsx b/src/v4/social/pages/SocialGlobalSearchPage/SocialGlobalSearchPage.tsx index 0f82dad3e..bf9e8bb74 100644 --- a/src/v4/social/pages/SocialGlobalSearchPage/SocialGlobalSearchPage.tsx +++ b/src/v4/social/pages/SocialGlobalSearchPage/SocialGlobalSearchPage.tsx @@ -59,6 +59,7 @@ export function SocialGlobalSearchPage() { <CommunitySearchResult pageId={pageId} communityCollection={communityCollection.communities} + isLoading={communityCollection.isLoading} onLoadMore={() => { if (communityCollection.hasMore && communityCollection.isLoading === false) { communityCollection.loadMore(); @@ -74,6 +75,7 @@ export function SocialGlobalSearchPage() { <UserSearchResult pageId={pageId} userCollection={userCollection.users} + isLoading={userCollection.isLoading} onLoadMore={() => { if (userCollection.hasMore && userCollection.isLoading === false) { userCollection.loadMore(); From aa4579b730bd0372c6d82f27e14e5001c59e2872 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Fri, 21 Jun 2024 19:14:51 +0700 Subject: [PATCH 155/300] fix: ASC-23278 - view story container (#435) * fix: create new story button prop * fix: story view * fix: remove package manager * fix: remove console * fix: unnecessary code * fix: elements --- .../components/StoryTab/StoryTabCommunity.tsx | 18 ++- .../CreateNewStoryButton.tsx | 4 +- .../social/elements/ImpressionButton/index.ts | 1 - .../StoryCommentButton.module.css | 18 +++ .../StoryCommentButton/StoryCommentButton.tsx | 77 ++++++++++++ .../elements/StoryCommentButton/index.ts | 1 + .../StoryImpressionButton.module.css} | 1 + .../StoryImpressionButton.tsx} | 17 ++- .../elements/StoryImpressionButton/index.ts | 1 + .../StoryReactionButton.module.css | 18 +++ .../StoryReactionButton.tsx | 104 +++++++++++++++ .../elements/StoryReactionButton/index.ts | 1 + src/v4/social/elements/index.ts | 12 +- .../StoryViewer/Renderers/Image.tsx | 37 +++--- .../Renderers/Renderers.module.css | 16 ++- .../StoryViewer/Renderers/Video.tsx | 13 +- .../Renderers/Wrappers/Footer/index.tsx | 28 +++-- .../Renderers/Wrappers/Header/index.tsx | 2 - .../StoryViewer/Renderers/types.ts | 3 + .../pages/DraftsPage/DraftsPage.module.css | 31 ++++- src/v4/social/pages/DraftsPage/DraftsPage.tsx | 118 +++++++++--------- .../pages/StoryPage/CommunityFeedStory.tsx | 58 +++------ .../pages/StoryPage/GlobalFeedStory.tsx | 101 ++++++--------- .../pages/StoryPage/StoryPage.module.css | 1 - 24 files changed, 442 insertions(+), 239 deletions(-) delete mode 100644 src/v4/social/elements/ImpressionButton/index.ts create mode 100644 src/v4/social/elements/StoryCommentButton/StoryCommentButton.module.css create mode 100644 src/v4/social/elements/StoryCommentButton/StoryCommentButton.tsx create mode 100644 src/v4/social/elements/StoryCommentButton/index.ts rename src/v4/social/elements/{ImpressionButton/ImpressionButton.module.css => StoryImpressionButton/StoryImpressionButton.module.css} (85%) rename src/v4/social/elements/{ImpressionButton/ImpressionButton.tsx => StoryImpressionButton/StoryImpressionButton.tsx} (82%) create mode 100644 src/v4/social/elements/StoryImpressionButton/index.ts create mode 100644 src/v4/social/elements/StoryReactionButton/StoryReactionButton.module.css create mode 100644 src/v4/social/elements/StoryReactionButton/StoryReactionButton.tsx create mode 100644 src/v4/social/elements/StoryReactionButton/index.ts diff --git a/src/v4/social/components/StoryTab/StoryTabCommunity.tsx b/src/v4/social/components/StoryTab/StoryTabCommunity.tsx index 02f2326cf..ef39ba2e3 100644 --- a/src/v4/social/components/StoryTab/StoryTabCommunity.tsx +++ b/src/v4/social/components/StoryTab/StoryTabCommunity.tsx @@ -92,16 +92,14 @@ export const StoryTabCommunityFeed: React.FC<StoryTabCommunityFeedProps> = ({ </button> {hasStoryPermission && ( - <> - <FileTrigger - onSelect={(e) => { - const files = Array.from(e as FileList); - onFileChange(files[0]); - }} - > - <CreateNewStoryButton pageId={pageId} componentId={componentId} /> - </FileTrigger> - </> + <FileTrigger + onSelect={(e) => { + const files = Array.from(e as FileList); + onFileChange(files[0]); + }} + > + <CreateNewStoryButton pageId={pageId} componentId={componentId} /> + </FileTrigger> )} {isErrored && <ErrorIcon className={clsx(styles.errorIcon)} />} </div> diff --git a/src/v4/social/elements/CreateNewStoryButton/CreateNewStoryButton.tsx b/src/v4/social/elements/CreateNewStoryButton/CreateNewStoryButton.tsx index 9c607c12f..4ffb62af4 100644 --- a/src/v4/social/elements/CreateNewStoryButton/CreateNewStoryButton.tsx +++ b/src/v4/social/elements/CreateNewStoryButton/CreateNewStoryButton.tsx @@ -22,7 +22,7 @@ const CreateNewStoryButtonSvg = (props: React.SVGProps<SVGSVGElement>) => ( </svg> ); -interface BackButtonProps { +interface CreateNewStoryProps { pageId?: string; componentId?: string; defaultClassName?: string; @@ -36,7 +36,7 @@ export const CreateNewStoryButton = ({ defaultClassName, imgClassName, onPress = () => {}, -}: BackButtonProps) => { +}: CreateNewStoryProps) => { const elementId = 'create_new_story_button'; const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = useAmityElement({ diff --git a/src/v4/social/elements/ImpressionButton/index.ts b/src/v4/social/elements/ImpressionButton/index.ts deleted file mode 100644 index 99f6ea2eb..000000000 --- a/src/v4/social/elements/ImpressionButton/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ImpressionButton } from './ImpressionButton'; diff --git a/src/v4/social/elements/StoryCommentButton/StoryCommentButton.module.css b/src/v4/social/elements/StoryCommentButton/StoryCommentButton.module.css new file mode 100644 index 000000000..968092c68 --- /dev/null +++ b/src/v4/social/elements/StoryCommentButton/StoryCommentButton.module.css @@ -0,0 +1,18 @@ +.storyCommentButton { + display: flex; + width: 3.5rem; + height: 2.5rem; + padding: var(--asc-spacing-s1) var(--asc-spacing-s2); + align-items: center; + gap: var(--asc-spacing-xxs3); + border-radius: var(--asc-border-radius-full); + background: var(--asc-color-secondary-default); + color: var(--asc-color-white); +} + +.storyCommentIcon { + display: flex; + align-items: center; + justify-content: center; + gap: var(--asc-spacing-xxs2); +} diff --git a/src/v4/social/elements/StoryCommentButton/StoryCommentButton.tsx b/src/v4/social/elements/StoryCommentButton/StoryCommentButton.tsx new file mode 100644 index 000000000..8448a095e --- /dev/null +++ b/src/v4/social/elements/StoryCommentButton/StoryCommentButton.tsx @@ -0,0 +1,77 @@ +import clsx from 'clsx'; +import millify from 'millify'; +import React from 'react'; +import { PressEvent } from 'react-aria'; +import { Typography } from '~/v4/core/components/index'; +import { useAmityElement } from '~/v4/core/hooks/uikit/index'; +import { IconComponent } from '~/v4/core/IconComponent'; + +import styles from './StoryCommentButton.module.css'; + +const StoryCommentSvg = (props: React.SVGProps<SVGSVGElement>) => { + return ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="20" + height="20" + fill="none" + viewBox="0 0 20 20" + {...props} + > + <path + fill="#A5A9B5" + d="M10 2.188c4.682 0 8.5 3.12 8.5 6.906C18.5 12.912 14.682 16 10 16c-1.295 0-2.49-.232-3.586-.63-.797.663-2.457 1.693-4.648 1.693-.133 0-.2-.034-.266-.133-.033-.1 0-.233.066-.3 0-.032 1.395-1.493 1.827-3.187-1.196-1.195-1.893-2.69-1.893-4.35 0-3.784 3.785-6.905 8.5-6.905z" + ></path> + </svg> + ); +}; + +interface StoryCommentButtonProps { + commentsCount: number; + onPress: (e: PressEvent) => void; + pageId?: string; + componentId?: string; + imgClassName?: string; +} + +export const StoryCommentButton = ({ + pageId = '*', + componentId = '*', + onPress, + commentsCount, + imgClassName, +}: StoryCommentButtonProps) => { + const elementId = 'story_comment_button'; + const { isExcluded, accessibilityId, defaultConfig, config, uiReference, themeStyles } = + useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + return ( + <IconComponent + data-qa-anchor={accessibilityId} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} + className={clsx(styles.storyCommentButton)} + imgIcon={() => ( + <img + src={config.icon} + alt={uiReference} + className={clsx(imgClassName)} + style={themeStyles} + /> + )} + defaultIcon={() => ( + <div className={clsx(styles.storyCommentIcon)}> + <StoryCommentSvg /> + <Typography.BodyBold>{millify(commentsCount)}</Typography.BodyBold> + </div> + )} + onPress={onPress} + /> + ); +}; diff --git a/src/v4/social/elements/StoryCommentButton/index.ts b/src/v4/social/elements/StoryCommentButton/index.ts new file mode 100644 index 000000000..5ac4972a8 --- /dev/null +++ b/src/v4/social/elements/StoryCommentButton/index.ts @@ -0,0 +1 @@ +export { StoryCommentButton } from './StoryCommentButton'; diff --git a/src/v4/social/elements/ImpressionButton/ImpressionButton.module.css b/src/v4/social/elements/StoryImpressionButton/StoryImpressionButton.module.css similarity index 85% rename from src/v4/social/elements/ImpressionButton/ImpressionButton.module.css rename to src/v4/social/elements/StoryImpressionButton/StoryImpressionButton.module.css index 1b198ad2c..f8cddca08 100644 --- a/src/v4/social/elements/ImpressionButton/ImpressionButton.module.css +++ b/src/v4/social/elements/StoryImpressionButton/StoryImpressionButton.module.css @@ -2,4 +2,5 @@ display: flex; align-items: center; gap: var(--asc-spacing-xxs2); + cursor: auto; } diff --git a/src/v4/social/elements/ImpressionButton/ImpressionButton.tsx b/src/v4/social/elements/StoryImpressionButton/StoryImpressionButton.tsx similarity index 82% rename from src/v4/social/elements/ImpressionButton/ImpressionButton.tsx rename to src/v4/social/elements/StoryImpressionButton/StoryImpressionButton.tsx index 66362e22d..272a13993 100644 --- a/src/v4/social/elements/ImpressionButton/ImpressionButton.tsx +++ b/src/v4/social/elements/StoryImpressionButton/StoryImpressionButton.tsx @@ -4,9 +4,10 @@ import { Typography } from '~/v4/core/components'; import { IconComponent } from '~/v4/core/IconComponent'; import { useAmityElement } from '~/v4/core/hooks/uikit'; -import styles from './ImpressionButton.module.css'; +import styles from './StoryImpressionButton.module.css'; +import millify from 'millify'; -const ImpressionSvg = (props: React.SVGProps<SVGSVGElement>) => ( +const StoryImpressionSvg = (props: React.SVGProps<SVGSVGElement>) => ( <svg xmlns="http://www.w3.org/2000/svg" width="20" @@ -22,7 +23,7 @@ const ImpressionSvg = (props: React.SVGProps<SVGSVGElement>) => ( </svg> ); -interface CommentButtonProps { +interface StoryImpressionButtonButtonProps { pageId?: string; componentId?: string; reach?: number | null; @@ -30,13 +31,13 @@ interface CommentButtonProps { imgIconClassName?: string; } -export function ImpressionButton({ +export function StoryImpressionButton({ pageId = '*', componentId = '*', reach = 0, defaultIconClassName, imgIconClassName, -}: CommentButtonProps) { +}: StoryImpressionButtonButtonProps) { const elementId = 'story_impression_button'; const { accessibilityId, config, defaultConfig, isExcluded, uiReference } = useAmityElement({ pageId, @@ -53,10 +54,8 @@ export function ImpressionButton({ className={clsx(styles.impressionButton, defaultIconClassName)} data-qa-anchor={accessibilityId} > - <ImpressionSvg /> - <Typography.BodyBold> - {typeof reach === 'number' ? reach : config.text} - </Typography.BodyBold> + <StoryImpressionSvg /> + <Typography.BodyBold>{reach}</Typography.BodyBold> </div> )} imgIcon={() => <img src={config.icon} alt={uiReference} className={imgIconClassName} />} diff --git a/src/v4/social/elements/StoryImpressionButton/index.ts b/src/v4/social/elements/StoryImpressionButton/index.ts new file mode 100644 index 000000000..a0950d25a --- /dev/null +++ b/src/v4/social/elements/StoryImpressionButton/index.ts @@ -0,0 +1 @@ +export { StoryImpressionButton } from './StoryImpressionButton'; diff --git a/src/v4/social/elements/StoryReactionButton/StoryReactionButton.module.css b/src/v4/social/elements/StoryReactionButton/StoryReactionButton.module.css new file mode 100644 index 000000000..23b49733b --- /dev/null +++ b/src/v4/social/elements/StoryReactionButton/StoryReactionButton.module.css @@ -0,0 +1,18 @@ +.storyReactionButton { + display: flex; + width: 3.5rem; + height: 2.5rem; + padding: var(--asc-spacing-s1) var(--asc-spacing-s2); + align-items: center; + gap: var(--asc-spacing-xxs3); + border-radius: var(--asc-border-radius-full); + background: var(--asc-color-secondary-default); + color: var(--asc-color-white); +} + +.storyReactionIcon { + display: flex; + align-items: center; + justify-content: center; + gap: var(--asc-spacing-xxs2); +} diff --git a/src/v4/social/elements/StoryReactionButton/StoryReactionButton.tsx b/src/v4/social/elements/StoryReactionButton/StoryReactionButton.tsx new file mode 100644 index 000000000..cec901262 --- /dev/null +++ b/src/v4/social/elements/StoryReactionButton/StoryReactionButton.tsx @@ -0,0 +1,104 @@ +import React from 'react'; +import clsx from 'clsx'; +import { IconComponent } from '~/v4/core/IconComponent'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; + +import styles from './StoryReactionButton.module.css'; +import { Typography } from '~/v4/core/components/index'; +import millify from 'millify'; + +const StoryReactionSvg = (props: React.SVGProps<SVGSVGElement>) => { + return ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="20" + height="20" + fill="none" + viewBox="0 0 20 20" + {...props} + > + <path + fill="#A5A9B5" + d="M17.793 11.025c.148.854.037 1.67-.334 2.338a3.437 3.437 0 01-.668 2.487c-.037 2.078-1.299 3.525-4.156 3.525h-.854c-3.785 0-4.935-1.41-6.605-1.447-.112.482-.594.853-1.114.853H1.688c-.667 0-1.187-.52-1.187-1.187V8.687c0-.63.52-1.187 1.188-1.187h3.636c.705-.594 1.707-2.227 2.56-3.08.52-.52.372-4.045 2.673-4.045 2.115 0 3.525 1.188 3.525 3.896 0 .706-.148 1.262-.334 1.745h1.373c1.781 0 3.191 1.521 3.191 3.154 0 .705-.185 1.299-.519 1.855zm-2.3 2.004c.816-.742.704-1.892.185-2.449.37 0 .853-.705.853-1.373-.037-.705-.63-1.41-1.41-1.41h-3.86c0-1.41 1.04-2.078 1.04-3.526 0-.89 0-2.115-1.744-2.115-.705.705-.372 2.487-1.41 3.526-1.002 1.002-2.45 3.6-3.526 3.6H5.25v6.939c1.967 0 3.71 1.373 6.346 1.373h1.41c1.299 0 2.264-.631 1.967-2.412.556-.334 1.002-1.373.52-2.153zM3.765 16.406a.903.903 0 00-.891-.89.88.88 0 00-.89.89c0 .52.37.89.89.89a.88.88 0 00.89-.89z" + ></path> + </svg> + ); +}; + +const StoryMyReactionSvg = (props: React.SVGProps<SVGSVGElement>) => { + return ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="24" + height="24" + fill="none" + viewBox="0 0 24 24" + {...props} + > + <circle cx="12" cy="12" r="12" fill="url(#paint0_linear_7702_34939)"></circle> + <path + fill="#fff" + d="M7.486 10.575H5.42a.611.611 0 00-.62.619v6.187c0 .361.258.619.62.619h2.066c.336 0 .62-.258.62-.619v-6.187a.628.628 0 00-.62-.62zm-1.033 6.394a.596.596 0 01-.62-.62c0-.334.258-.618.62-.618.336 0 .62.284.62.619 0 .36-.284.619-.62.619zm8.264-10.055c0-1.908-1.24-2.114-1.859-2.114-.542 0-.775 1.031-.878 1.495-.155.567-.284 1.134-.672 1.521-.826.851-1.265 1.908-2.298 2.913a.415.415 0 00-.078.232v5.518c0 .154.13.283.284.31.414 0 .956.231 1.37.412.826.36 1.833.799 3.073.799h.077c1.11 0 2.428 0 2.944-.748.233-.31.285-.696.155-1.16.44-.438.646-1.263.44-1.934.439-.593.49-1.443.232-2.036.31-.31.516-.8.49-1.264 0-.799-.67-1.52-1.523-1.52h-2.635c.207-.723.878-1.341.878-2.424z" + ></path> + <defs> + <linearGradient + id="paint0_linear_7702_34939" + x1="9" + x2="19.8" + y1="2.4" + y2="29.4" + gradientUnits="userSpaceOnUse" + > + <stop stopColor="#5B9DFF"></stop> + <stop offset="1" stopColor="#0041BE"></stop> + </linearGradient> + </defs> + </svg> + ); +}; + +interface StoryReactionButtonProps { + myReactions?: string[]; + reactionsCount: number; + pageId?: string; + componentId?: string; + defaultIconClassName?: string; + imgIconClassName?: string; + onPress: () => void; +} + +export const StoryReactionButton = ({ + myReactions = [], + reactionsCount, + pageId = '*', + componentId = '*', + defaultIconClassName, + imgIconClassName, + onPress, +}: StoryReactionButtonProps) => { + const elementId = 'story_reaction_button'; + const { isExcluded, accessibilityId, config, uiReference } = useAmityElement({ + pageId, + componentId, + elementId, + }); + + const hasMyReactions = myReactions.length > 0; + + if (isExcluded) return null; + + return ( + <IconComponent + data-qa-anchor={accessibilityId} + className={clsx(styles.storyReactionButton)} + defaultIcon={() => ( + <div className={clsx(styles.storyReactionIcon, defaultIconClassName)}> + {hasMyReactions ? <StoryMyReactionSvg /> : <StoryReactionSvg />} + <Typography.BodyBold>{millify(reactionsCount)}</Typography.BodyBold> + </div> + )} + imgIcon={() => <img src={config.icon} alt={uiReference} className={clsx(imgIconClassName)} />} + onPress={onPress} + /> + ); +}; diff --git a/src/v4/social/elements/StoryReactionButton/index.ts b/src/v4/social/elements/StoryReactionButton/index.ts new file mode 100644 index 000000000..cf80af8db --- /dev/null +++ b/src/v4/social/elements/StoryReactionButton/index.ts @@ -0,0 +1 @@ +export { StoryReactionButton } from './StoryReactionButton'; diff --git a/src/v4/social/elements/index.ts b/src/v4/social/elements/index.ts index 7acf1d2b7..f1cc29ff7 100644 --- a/src/v4/social/elements/index.ts +++ b/src/v4/social/elements/index.ts @@ -1,14 +1,14 @@ export { AspectRatioButton } from './AspectRatioButton'; -export { BackButton } from './BackButton/BackButton'; +export { BackButton } from './BackButton'; export { CloseButton } from './CloseButton'; export { CancelButton } from './CancelButton'; -export { CreateStoryButton } from './CreateStoryButton/CreateStoryButton'; -export { HyperLinkButton } from './HyperLinkButton/HyperLinkButton'; -export { ImpressionButton } from './ImpressionButton'; -export { OverflowMenuButton } from './OverflowMenuButton/OverflowMenuButton'; +export { CreateStoryButton } from './CreateStoryButton'; +export { HyperLinkButton } from './HyperLinkButton'; +export { StoryImpressionButton } from './StoryImpressionButton'; +export { OverflowMenuButton } from './OverflowMenuButton'; export { ReactionButton } from './ReactionButton'; export { SaveButton } from './SaveButton'; -export { ShareStoryButton } from './ShareStoryButton/ShareStoryButton'; +export { ShareStoryButton } from './ShareStoryButton'; export { SpeakerButton } from './SpeakerButton'; export { HyperLink } from './HyperLink'; export { CreateNewStoryButton } from './CreateNewStoryButton'; diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index e8f4898dc..aab1276d8 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -21,6 +21,7 @@ import { LIKE_REACTION_KEY } from '~/v4/social/constants/reactions'; import { checkStoryPermission, formatTimeAgo, isAdmin, isModerator } from '~/v4/social/utils'; import styles from './Renderers.module.css'; +import clsx from 'clsx'; export const renderer: CustomRenderer = ({ story, @@ -35,12 +36,11 @@ export const renderer: CustomRenderer = ({ const [isOpenBottomSheet, setIsOpenBottomSheet] = useState(false); const [isOpenCommentSheet, setIsOpenCommentSheet] = useState(false); const [isPaused, setIsPaused] = useState(false); - const { loader, storyStyles } = config; + const { loader } = config; const { client } = useSDK(); const isLiked = !!(story && story.myReactions && story.myReactions.includes(LIKE_REACTION_KEY)); const reactionsCount = story.reactionsCount || 0; - const myReactions = story.myReactions || []; const { storyId, @@ -51,9 +51,10 @@ export const renderer: CustomRenderer = ({ creator, community, actions, - handleAddIconClick, addStoryButton, fileInputRef, + storyStyles, + myReactions, } = story; const { members } = useCommunityMembersCollection(community?.communityId as string); @@ -70,11 +71,6 @@ export const renderer: CustomRenderer = ({ const haveStoryPermission = isGlobalAdmin || isCommunityModerator || checkStoryPermission(client, community?.communityId); - const computedStyles = { - ...rendererStyles.storyContent, - ...(storyStyles || {}), - }; - const heading = <div data-qa-anchor="community_display_name">{community?.displayName}</div>; const subheading = createdAt && creator?.displayName ? ( @@ -191,20 +187,27 @@ export const renderer: CustomRenderer = ({ onPlay={play} onPause={pause} onAction={openBottomSheet} - onAddStory={handleAddIconClick} onClickCommunity={() => onClickCommunity?.()} onClose={handleOnClose} addStoryButton={addStoryButton} /> - <img - className={styles.storyImage} - data-qa-anchor="image_view" - style={computedStyles} - src={story.url} - onLoad={imageLoaded} - alt="Story Image" - /> + <div + className={clsx(styles.storyImageContainer)} + style={ + { + '--asc-story-image-background': storyStyles?.background, + } as React.CSSProperties + } + > + <img + className={styles.storyImage} + data-qa-anchor="image_view" + src={story.url} + onLoad={imageLoaded} + alt="Story Image" + /> + </div> {!loaded && <div className={styles.loadingOverlay}>{loader || <div>loading...</div>}</div>} diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css b/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css index 5a67106f9..3d4aed3e2 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css @@ -62,11 +62,19 @@ align-items: center; } +.storyImageContainer { + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + background: var(--asc-story-image-background); +} + .storyImage { - width: auto; - max-width: 100%; - max-height: 100%; - margin: auto; + width: 100%; + height: 100%; + object-fit: contain; } .playStoryButton { diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx index 5bde45379..8c28d3552 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx @@ -61,20 +61,15 @@ export const renderer: CustomRenderer = ({ creator, community, actions, - handleAddIconClick, addStoryButton, fileInputRef, + myReactions, } = story; const { members } = useCommunityMembersCollection(community?.communityId as string); const member = members?.find((member) => member.userId === client?.userId); const isMember = member != null; - const avatarUrl = useImage({ - fileId: community?.avatarFileId || '', - imageSize: 'small', - }); - const heading = <div data-qa-anchor="community_display_name">{community?.displayName}</div>; const subheading = createdAt && creator?.displayName ? ( @@ -222,7 +217,7 @@ export const renderer: CustomRenderer = ({ pageId="story_page" componentId="*" isMuted={muted} - onClick={muted ? unmute : mute} + onPress={muted ? unmute : mute} /> <Header community={community} @@ -237,7 +232,6 @@ export const renderer: CustomRenderer = ({ onMute={mute} onUnmute={unmute} onAction={openBottomSheet} - onAddStory={handleAddIconClick} onClickCommunity={() => onClickCommunity?.()} onClose={handleOnClose} addStoryButton={addStoryButton} @@ -322,9 +316,10 @@ export const renderer: CustomRenderer = ({ syncState={syncState} reach={reach} commentsCount={commentsCount} - totalLikes={totalLikes} + reactionsCount={totalLikes} isLiked={isLiked} onClickComment={openCommentSheet} + myReactions={myReactions} showImpression={isCreator || haveStoryPermission} isMember={isMember} /> diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx index 02e771444..e489249ae 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx @@ -1,16 +1,16 @@ import React from 'react'; import { DotsIcon, ErrorIcon } from '~/icons'; -import { useIntl } from 'react-intl'; import { ReactionRepository } from '@amityco/ts-sdk'; import { LIKE_REACTION_KEY } from '~/constants'; import Spinner from '~/social/components/Spinner'; -import { ImpressionButton, ReactionButton } from '~/v4/social/elements'; import { useNotifications } from '~/v4/core/providers/NotificationProvider'; -import { CommentButton } from '~/v4/social/elements/CommentButton/CommentButton'; import styles from './Footer.module.css'; import clsx from 'clsx'; +import { StoryCommentButton } from '~/v4/social/elements/StoryCommentButton/StoryCommentButton'; +import { StoryReactionButton } from '~/v4/social/elements/StoryReactionButton/StoryReactionButton'; +import { StoryImpressionButton } from '~/v4/social/elements/StoryImpressionButton/StoryImpressionButton'; const Footer: React.FC< React.PropsWithChildren<{ @@ -23,7 +23,7 @@ const Footer: React.FC< onClickComment: () => void; syncState?: Amity.SyncState; isMember?: boolean; - myReactions: string[]; + myReactions?: string[]; }> > = ({ syncState, @@ -38,13 +38,12 @@ const Footer: React.FC< myReactions, }) => { const notification = useNotifications(); - const { formatMessage } = useIntl(); const handleClickReaction = async () => { try { if (!isMember) { notification.show({ - content: formatMessage({ id: 'storyViewer.toast.disable.react' }), + content: 'You need to be a member to like this story', }); return; } @@ -63,7 +62,7 @@ const Footer: React.FC< <div className={styles.viewStoryCompostBarContainer}> <div className={styles.viewStoryUploadingWrapper}> <Spinner width={20} height={20} /> - {formatMessage({ id: 'storyViewer.footer.uploading' })} + Uploading... </div> </div> ); @@ -74,7 +73,7 @@ const Footer: React.FC< <div className={styles.viewStoryFailedCompostBarContainer}> <div className={styles.viewStoryFailedCompostBarWrapper}> <ErrorIcon /> - {formatMessage({ id: 'storyViewer.footer.failed' })} + <span>Failed to upload</span> </div> <DotsIcon /> </div> @@ -86,12 +85,19 @@ const Footer: React.FC< <div> {showImpression && ( <div className={styles.viewStoryCompostBarViewIconContainer}> - <ImpressionButton pageId="story_page" reach={reach} /> + <StoryImpressionButton /> + {/* <ImpressionButton pageId="story_page" reach={reach} /> */} </div> )} </div> <div className={styles.viewStoryCompostBarEngagementContainer}> - <CommentButton + <StoryCommentButton commentsCount={commentsCount} onPress={onClickComment} /> + <StoryReactionButton + myReactions={myReactions} + reactionsCount={reactionsCount} + onPress={handleClickReaction} + /> + {/* <CommentButton className={clsx(styles.viewStoryCommentButton)} defaultIconClassName={clsx(styles.viewStoryCommentIcon)} pageId="story_page" @@ -103,7 +109,7 @@ const Footer: React.FC< pageId="story_page" myReactions={myReactions} reactionsCount={reactionsCount} - /> + /> */} </div> </div> ); diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/index.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/index.tsx index febef076b..1c6cf6ea8 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/index.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/index.tsx @@ -14,7 +14,6 @@ const Header: React.FC< onPause: () => void; onAction: () => void; onClose: () => void; - onAddStory: (e: React.MouseEvent<Element, MouseEvent>) => void; onClickCommunity: () => void; community?: Amity.Community | null; heading?: React.ReactNode; @@ -47,7 +46,6 @@ const Header: React.FC< <div className={styles.viewStoryHeadingInfoContainer}> <div className={styles.avatarContainer}> <CommunityAvatar pageId="story_page" community={community} /> - {haveStoryPermission && addStoryButton} </div> <div className={styles.viewStoryInfoContainer}> diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/types.ts b/src/v4/social/internal-components/StoryViewer/Renderers/types.ts index 0a14924a8..299125d54 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/types.ts +++ b/src/v4/social/internal-components/StoryViewer/Renderers/types.ts @@ -24,6 +24,9 @@ export type CustomStory = Story & handleAddIconClick: (e: React.MouseEvent<Element, MouseEvent>) => void; addStoryButton: JSX.Element; fileInputRef: React.RefObject<HTMLInputElement>; + storyStyles: { + background: string; + }; }; export type CustomRendererProps = RendererProps & { diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.module.css b/src/v4/social/pages/DraftsPage/DraftsPage.module.css index 89b0f0b94..e0f3fb9e5 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.module.css +++ b/src/v4/social/pages/DraftsPage/DraftsPage.module.css @@ -1,11 +1,22 @@ .draftPageContainer { - display: flex; - flex-direction: column; + aspect-ratio: 9 / 16; + max-width: 23.4375rem; /* 375px */ + max-height: 100%; width: 100%; - height: 100%; + height: auto; position: relative; - font-family: var(--asc-text-global-font-family); - font-style: var(--asc-text-global-font-style); + display: flex; + flex-direction: column; +} + +@media (width <= 768px) { + .draftPageContainer { + max-width: 100%; + max-height: 100vh; + width: 100%; + height: 100%; + aspect-ratio: auto; + } } .headerContainer { @@ -16,6 +27,16 @@ z-index: 1; } +.storyWrapper { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + gap: 1rem; + overflow: hidden; +} + .header { width: 100%; justify-content: space-between; diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index c35c329aa..63a2f5b8b 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -219,76 +219,78 @@ export const PlainDraftStoryPage = ({ }, [file, imageMode, mediaType]); return ( - <div id="asc-uikit-create-story" className={styles.draftPageContainer}> - <div className={styles.headerContainer}> - <div className={styles.header}> - <BackButton pageId={pageId} onPress={discardCreateStory} /> - <div className={styles.topRightButtons}> - {mediaType?.type === 'image' && ( - <AspectRatioButton pageId={pageId} onPress={onClickImageMode} /> - )} - <HyperLinkButton pageId={pageId} onPress={handleOnClickHyperLinkActionButton} /> + <div className={styles.storyWrapper}> + <div id="asc-uikit-create-story" className={styles.draftPageContainer}> + <div className={styles.headerContainer}> + <div className={styles.header}> + <BackButton pageId={pageId} onPress={discardCreateStory} /> + <div className={styles.topRightButtons}> + {mediaType?.type === 'image' && ( + <AspectRatioButton pageId={pageId} onPress={onClickImageMode} /> + )} + <HyperLinkButton pageId={pageId} onPress={handleOnClickHyperLinkActionButton} /> + </div> </div> </div> - </div> - {mediaType?.type === 'image' ? ( - <div - className={styles.mainContainer} - style={{ - background: `linear-gradient( + {mediaType?.type === 'image' ? ( + <div + className={styles.mainContainer} + style={{ + background: `linear-gradient( 180deg, ${colors?.length > 0 ? colors[0].hex : 'var(--asc-color-black)'} 0%, ${colors?.length > 0 ? colors[colors?.length - 1].hex : 'var(--asc-color-black)'} 100% )`, - }} - > - <img - className={styles.previewImage} - src={file ? URL.createObjectURL(file) : mediaType.url} - style={{ - width: '100%', - height: '100%', - objectFit: imageMode === 'fit' ? 'contain' : 'cover', }} - alt="Draft" + > + <img + className={styles.previewImage} + src={file ? URL.createObjectURL(file) : mediaType.url} + style={{ + width: '100%', + height: '100%', + objectFit: imageMode === 'fit' ? 'contain' : 'cover', + }} + alt="Draft" + /> + </div> + ) : mediaType?.type === 'video' ? ( + <VideoPreview + mediaFit="contain" + className={styles.videoPreview} + src={file ? URL.createObjectURL(file) : mediaType.url} + autoPlay + loop + controls={false} /> - </div> - ) : mediaType?.type === 'video' ? ( - <VideoPreview - mediaFit="contain" - className={styles.videoPreview} - src={file ? URL.createObjectURL(file) : mediaType.url} - autoPlay - loop - controls={false} - /> - ) : null} - {hyperLink[0]?.data?.url && ( - <div className={styles.hyperLinkContainer}> - <HyperLink onClick={() => setIsHyperLinkBottomSheetOpen(true)}> - {hyperLink[0]?.data?.customText || hyperLink[0].data.url.replace(/^https?:\/\//, '')} - </HyperLink> - </div> - )} - - <HyperLinkConfig - pageId={pageId} - isOpen={isHyperLinkBottomSheetOpen} - onClose={handleHyperLinkBottomSheetClose} - onSubmit={onSubmitHyperLink} - onRemove={onRemoveHyperLink} - isHaveHyperLink={hyperLink?.[0]?.data?.url !== ''} - /> + ) : null} + {hyperLink[0]?.data?.url && ( + <div className={styles.hyperLinkContainer}> + <HyperLink onClick={() => setIsHyperLinkBottomSheetOpen(true)}> + {hyperLink[0]?.data?.customText || hyperLink[0].data.url.replace(/^https?:\/\//, '')} + </HyperLink> + </div> + )} - <div className={styles.footer}> - <ShareStoryButton - community={community} + <HyperLinkConfig pageId={pageId} - onClick={() => - onCreateStory(file, imageMode, {}, hyperLink[0]?.data?.url ? hyperLink : []) - } + isOpen={isHyperLinkBottomSheetOpen} + onClose={handleHyperLinkBottomSheetClose} + onSubmit={onSubmitHyperLink} + onRemove={onRemoveHyperLink} + isHaveHyperLink={hyperLink?.[0]?.data?.url !== ''} /> + + <div className={styles.footer}> + <ShareStoryButton + community={community} + pageId={pageId} + onClick={() => + onCreateStory(file, imageMode, {}, hyperLink[0]?.data?.url ? hyperLink : []) + } + /> + </div> </div> </div> ); diff --git a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx index 1bce079b3..234e04487 100644 --- a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx @@ -91,22 +91,6 @@ export const CommunityFeedStory = ({ const fileInputRef = useRef<HTMLInputElement>(null); - const handleAddIconClick = (e: React.MouseEvent) => { - e.stopPropagation(); - e.preventDefault(); - - if (fileInputRef.current) { - fileInputRef.current.click(); - } - }; - - const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => { - const selectedFile = event.target.files?.[0]; - if (selectedFile) { - onChange(selectedFile as File); - } - }; - const { client, currentUserId } = useSDK(); const { formatMessage } = useIntl(); @@ -134,10 +118,6 @@ export const CommunityFeedStory = ({ }); }; - const onChange = (file: File) => { - setFile(file); - }; - const deleteStory = async (storyId: string) => { confirmDeleteStory(storyId); }; @@ -191,7 +171,7 @@ export const CommunityFeedStory = ({ setFile(null); }; - const addStoryButton = <CreateNewStoryButton pageId={pageId} onClick={handleAddIconClick} />; + const addStoryButton = <CreateNewStoryButton pageId={pageId} />; const formattedStories = stories?.map((story) => { const isImage = story?.dataType === 'image'; @@ -210,7 +190,6 @@ export const CommunityFeedStory = ({ } : null, ].filter(isNonNullable), - handleAddIconClick, onCreateStory, discardStory, addStoryButton, @@ -252,17 +231,6 @@ export const CommunityFeedStory = ({ setCurrentIndex(currentIndex + 1); }; - if (file) { - goToDraftStoryPage({ - targetId: communityId, - targetType: 'community', - mediaType: file.type.includes('image') - ? { type: 'image', url: URL.createObjectURL(file) } - : { type: 'video', url: URL.createObjectURL(file) }, - storyType: 'communityFeed', - }); - } - useEffect(() => { if (stories[stories.length - 1]?.syncState === 'syncing') { setCurrentIndex(stories.length - 1); @@ -282,7 +250,7 @@ export const CommunityFeedStory = ({ }, [stories]); useEffect(() => { - if (!stories || !file) return; + if (!stories) return; const extractColorsFromImage = async (url: string) => { const colorsFromImage = await extractColors(url, { crossOrigin: 'anonymous', @@ -296,19 +264,23 @@ export const CommunityFeedStory = ({ } else { setColors([]); } - }, [stories, file, currentIndex]); + }, [stories, currentIndex]); + + if (file) { + goToDraftStoryPage({ + targetId: communityId, + targetType: 'community', + mediaType: file.type.includes('image') + ? { type: 'image', url: URL.createObjectURL(file) } + : { type: 'video', url: URL.createObjectURL(file) }, + storyType: 'communityFeed', + }); + } return ( <div className={clsx(styles.storyWrapper)} data-qa-anchor={accessibilityId}> <ArrowLeftButton onClick={previousStory} /> <div id={targetRootId} className={clsx(styles.viewStoryContainer)}> - <input - className={clsx(styles.hiddenInput)} - ref={fileInputRef} - type="file" - accept="image/*,video/*" - onChange={handleFileChange} - /> <div className={clsx(styles.viewStoryContent)}> <div className={clsx(styles.overlayLeft)} onClick={previousStory} /> <div className={clsx(styles.overlayRight)} onClick={nextStory} /> @@ -316,8 +288,6 @@ export const CommunityFeedStory = ({ {formattedStories?.length > 0 ? ( // NOTE: Do not use isPaused prop, it will cause the first video story skipped <Stories - width="100%" - height="100%" storyStyles={storyStyles} preventDefault currentIndex={currentIndex} diff --git a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx index 2a5b01678..263ce460c 100644 --- a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx @@ -24,6 +24,7 @@ import { CreateNewStoryButton } from '~/v4/social/elements/CreateNewStoryButton' import styles from './StoryPage.module.css'; import { useAmityPage } from '~/v4/core/hooks/uikit/index'; +import { FileTrigger } from 'react-aria-components'; const DURATION = 5000; @@ -89,22 +90,6 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ const fileInputRef = useRef<HTMLInputElement>(null); - const handleAddIconClick = (e: React.MouseEvent) => { - e.stopPropagation(); - e.preventDefault(); - - if (fileInputRef.current) { - fileInputRef.current.click(); - } - }; - - const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => { - const selectedFile = event.target.files?.[0]; - if (selectedFile) { - onChange(selectedFile as File); - } - }; - const { client, currentUserId } = useSDK(); const { formatMessage } = useIntl(); @@ -139,10 +124,6 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ }); }; - const onChange = (file: File) => { - setFile(file); - }; - const deleteStory = async (storyId: string) => { confirmDeleteStory(storyId); }; @@ -198,7 +179,32 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ setFile(null); }; - const addStoryButton = <CreateNewStoryButton pageId={pageId} onPress={handleAddIconClick} />; + const addStoryButton = ( + <FileTrigger + ref={fileInputRef} + onSelect={(e) => { + const files = Array.from(e as FileList); + setFile(files[0]); + }} + > + <CreateNewStoryButton pageId={pageId} /> + </FileTrigger> + ); + + const storyStyles = { + width: '100%', + height: '100%', + objectFit: + stories[currentIndex]?.dataType === 'image' && + stories[currentIndex]?.data?.imageDisplayMode === 'fill' + ? 'cover' + : 'contain', + background: `linear-gradient( + 180deg, + ${colors?.length > 0 ? colors[0].hex : '#000'} 0%, + ${colors?.length > 0 ? colors[colors?.length - 1].hex : '#000'} 100% + )`, + }; const formattedStories = stories?.map((story) => { const isImage = story?.dataType === 'image'; @@ -223,11 +229,11 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ } : null, ].filter(isNonNullable), - handleAddIconClick, onCreateStory, discardStory, addStoryButton, fileInputRef, + storyStyles, }; }); @@ -267,36 +273,10 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ const targetRootId = 'asc-uikit-stories-viewer'; - const storyStyles = { - width: '100%', - height: '100%', - objectFit: - stories[currentIndex]?.dataType === 'image' && - stories[currentIndex]?.data?.imageDisplayMode === 'fill' - ? 'cover' - : 'contain', - background: `linear-gradient( - 180deg, - ${colors?.length > 0 ? colors[0].hex : '#000'} 0%, - ${colors?.length > 0 ? colors[colors?.length - 1].hex : '#000'} 100% - )`, - }; - const increaseIndex = () => { setCurrentIndex(currentIndex + 1); }; - if (file) { - goToDraftStoryPage({ - targetId, - targetType: 'community', - mediaType: file.type.includes('image') - ? { type: 'image', url: URL.createObjectURL(file) } - : { type: 'video', url: URL.createObjectURL(file) }, - storyType: 'globalFeed', - }); - } - useEffect(() => { if (stories[stories.length - 1]?.syncState === 'syncing') { setCurrentIndex(stories.length - 1); @@ -316,7 +296,7 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ }, [stories]); useEffect(() => { - if (!file || !stories) return; + if (!stories) return; const extractColorsFromImage = async (url: string) => { const colorsFromImage = await extractColors(url, { crossOrigin: 'anonymous', @@ -330,19 +310,23 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ } else { setColors([]); } - }, [stories, file, currentIndex]); + }, [stories, currentIndex]); + + if (file) { + goToDraftStoryPage({ + targetId, + targetType: 'community', + mediaType: file.type.includes('image') + ? { type: 'image', url: URL.createObjectURL(file) } + : { type: 'video', url: URL.createObjectURL(file) }, + storyType: 'globalFeed', + }); + } return ( <div className={clsx(styles.storyWrapper)} data-qa-anchor={accessibilityId}> <ArrowLeftButton onClick={previousStory} /> <div id={targetRootId} className={clsx(styles.viewStoryContainer)}> - <input - className={clsx(styles.hiddenInput)} - ref={fileInputRef} - type="file" - accept="image/*,video/*" - onChange={handleFileChange} - /> <div className={clsx(styles.viewStoryContent)}> <div className={clsx(styles.overlayLeft)} onClick={previousStory} /> <div className={clsx(styles.overlayRight)} onClick={nextStory} /> @@ -350,9 +334,6 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ {formattedStories?.length > 0 ? ( // NOTE: Do not use isPaused prop, it will cause the first video story skipped <Stories - width="100%" - height="100%" - storyStyles={storyStyles} preventDefault currentIndex={currentIndex} stories={formattedStories} diff --git a/src/v4/social/pages/StoryPage/StoryPage.module.css b/src/v4/social/pages/StoryPage/StoryPage.module.css index 7036c8178..f412db33c 100644 --- a/src/v4/social/pages/StoryPage/StoryPage.module.css +++ b/src/v4/social/pages/StoryPage/StoryPage.module.css @@ -44,7 +44,6 @@ align-items: center; justify-content: center; max-width: 23.4375rem; - max-height: 43.5625rem; flex-shrink: 0; } From 881e565f0e7dc98f2926c96013ca6900c8252687 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Mon, 24 Jun 2024 17:05:57 +0700 Subject: [PATCH 156/300] fix: ASC-23385 - view story comment tray close after comment (#438) * fix: create new story button prop * fix: story view * fix: remove package manager * fix: remove console * fix: unnecessary code * fix: elements * fix: story comment button width * fix: useGetActiveStoriesByTarget hook * fix: add story progress bar * fix: view story comment tray close after comment * fix: story progress bar * fix: community story * fix: remove unused * fix: community * fix: type * fix: speaker button condition ui * fix: story sub * fix: remove unused * fix: button css --- src/social/pages/ViewStoryPage.tsx | 3 + .../elements/SpeakerButton/SpeakerButton.tsx | 2 +- .../StoryCommentButton.module.css | 3 +- .../StoryProgressBar.module.css | 24 ++ .../StoryProgressBar/StoryProgressBar.tsx | 102 ++++++ .../social/elements/StoryProgressBar/index.ts | 1 + .../StoryReactionButton.module.css | 3 +- src/v4/social/hooks/useGetActiveStories.ts | 82 +---- .../StoryViewer/Renderers/Image.tsx | 102 +++--- .../StoryViewer/Renderers/Video.tsx | 36 ++- .../Renderers/Wrappers/Footer/index.tsx | 16 +- .../StoryViewer/Renderers/types.ts | 4 + .../pages/StoryPage/CommunityFeedStory.tsx | 250 +++++++++------ .../pages/StoryPage/GlobalFeedStory.tsx | 295 ++++++++++-------- 14 files changed, 531 insertions(+), 392 deletions(-) create mode 100644 src/v4/social/elements/StoryProgressBar/StoryProgressBar.module.css create mode 100644 src/v4/social/elements/StoryProgressBar/StoryProgressBar.tsx create mode 100644 src/v4/social/elements/StoryProgressBar/index.ts diff --git a/src/social/pages/ViewStoryPage.tsx b/src/social/pages/ViewStoryPage.tsx index 70d26edc6..808f373ea 100644 --- a/src/social/pages/ViewStoryPage.tsx +++ b/src/social/pages/ViewStoryPage.tsx @@ -12,6 +12,7 @@ interface AmityViewStoryPageProps { } const ViewStoryPage: React.FC<AmityViewStoryPageProps> = ({ type, targetId }) => { + const pageId = 'view_story_page'; const { onBack, goToDraftStoryPage, onClickCommunity, onChangePage, onClickStory } = useNavigation(); @@ -26,6 +27,7 @@ const ViewStoryPage: React.FC<AmityViewStoryPageProps> = ({ type, targetId }) => if (type === 'communityFeed') return ( <CommunityFeedStory + pageId={pageId} communityId={targetId} onBack={onBack} onClose={goToCommunity} @@ -40,6 +42,7 @@ const ViewStoryPage: React.FC<AmityViewStoryPageProps> = ({ type, targetId }) => if (type === 'globalFeed') return ( <ViewGlobalFeedStoryPage + pageId={pageId} targetId={targetId} onChangePage={goToNewsFeed} onClose={goToNewsFeed} diff --git a/src/v4/social/elements/SpeakerButton/SpeakerButton.tsx b/src/v4/social/elements/SpeakerButton/SpeakerButton.tsx index e09771408..28bbf5296 100644 --- a/src/v4/social/elements/SpeakerButton/SpeakerButton.tsx +++ b/src/v4/social/elements/SpeakerButton/SpeakerButton.tsx @@ -72,7 +72,7 @@ export function SpeakerButton({ data-qa-anchor={accessibilityId} className={clsx(styles.speakerButton)} onPress={onPress} - defaultIcon={() => (isMuted ? <SpeakerMuteSvg /> : <SpeakerUnmuteSvg />)} + defaultIcon={() => (isMuted ? <SpeakerUnmuteSvg /> : <SpeakerMuteSvg />)} imgIcon={() => <img src={config.icon} alt={uiReference} className={clsx(imgIconClassName)} />} defaultIconName={defaultConfig.icon} configIconName={config.icon} diff --git a/src/v4/social/elements/StoryCommentButton/StoryCommentButton.module.css b/src/v4/social/elements/StoryCommentButton/StoryCommentButton.module.css index 968092c68..9e10a7467 100644 --- a/src/v4/social/elements/StoryCommentButton/StoryCommentButton.module.css +++ b/src/v4/social/elements/StoryCommentButton/StoryCommentButton.module.css @@ -1,13 +1,12 @@ .storyCommentButton { display: flex; - width: 3.5rem; - height: 2.5rem; padding: var(--asc-spacing-s1) var(--asc-spacing-s2); align-items: center; gap: var(--asc-spacing-xxs3); border-radius: var(--asc-border-radius-full); background: var(--asc-color-secondary-default); color: var(--asc-color-white); + cursor: pointer; } .storyCommentIcon { diff --git a/src/v4/social/elements/StoryProgressBar/StoryProgressBar.module.css b/src/v4/social/elements/StoryProgressBar/StoryProgressBar.module.css new file mode 100644 index 000000000..a9b5ffd70 --- /dev/null +++ b/src/v4/social/elements/StoryProgressBar/StoryProgressBar.module.css @@ -0,0 +1,24 @@ +.progressBarContainer { + position: absolute; + top: 0.5rem; + left: 0.5rem; + right: 0.5rem; + height: 0.125rem; + display: flex; + justify-content: space-between; + align-items: center; + z-index: 10; +} + +.progressSegment { + height: 100%; + background-color: rgb(255 255 255 / 50%); + border-radius: 0.0625rem; + overflow: hidden; +} + +.progressBar { + height: 100%; + background-color: var(--asc-color-white); + transition: width 0.1s linear; +} diff --git a/src/v4/social/elements/StoryProgressBar/StoryProgressBar.tsx b/src/v4/social/elements/StoryProgressBar/StoryProgressBar.tsx new file mode 100644 index 000000000..88b89f25b --- /dev/null +++ b/src/v4/social/elements/StoryProgressBar/StoryProgressBar.tsx @@ -0,0 +1,102 @@ +import React, { useEffect, useState } from 'react'; +import { useAmityElement } from '~/v4/core/hooks/uikit/index'; +import clsx from 'clsx'; +import styles from './StoryProgressBar.module.css'; + +interface ProgressBarProps { + duration: number; + currentIndex: number; + storiesCount: number; + isPaused: boolean; + onComplete: () => void; + pageId?: string; + componentId?: string; +} + +export const StoryProgressBar: React.FC<ProgressBarProps> = React.memo( + ({ + pageId = '*', + componentId = '*', + duration, + currentIndex, + storiesCount, + isPaused, + onComplete, + }) => { + const elementId = 'progress_bar'; + const { config, isExcluded } = useAmityElement({ + pageId, + componentId, + elementId, + }); + const [segments, setSegments] = useState<number[]>(new Array(storiesCount).fill(0)); + const [lastIndex, setLastIndex] = useState(currentIndex); + + const backgroundColor = config?.backgroundColor; + const progressColor = config?.progressColor; + + useEffect(() => { + if (currentIndex < lastIndex) { + setSegments((prevSegments) => { + const newSegments = [...prevSegments]; + newSegments[currentIndex] = 0; + return newSegments; + }); + } + setLastIndex(currentIndex); + }, [currentIndex, lastIndex]); + + useEffect(() => { + let interval: NodeJS.Timeout; + + if (!isPaused) { + interval = setInterval(() => { + setSegments((prevSegments) => { + const newSegments = [...prevSegments]; + if (newSegments[currentIndex] < 100) { + newSegments[currentIndex] += (100 / duration) * 100; + if (newSegments[currentIndex] >= 100) { + newSegments[currentIndex] = 100; + onComplete(); + } + } + return newSegments; + }); + }, 100); + } + + return () => { + if (interval) clearInterval(interval); + }; + }, [duration, currentIndex, isPaused, onComplete]); + + if (isExcluded) return null; + + return ( + <div + className={clsx(styles.progressBarContainer)} + style={{ backgroundColor } as React.CSSProperties} + > + {segments.map((progress, index) => ( + <div + key={index} + className={clsx(styles.progressSegment)} + style={{ + width: `calc(${100 / storiesCount}% - 4px)`, + }} + > + <div + className={clsx(styles.progressBar)} + style={ + { + width: `${index < currentIndex ? 100 : index === currentIndex ? progress : 0}%`, + backgroundColor: progressColor, + } as React.CSSProperties + } + /> + </div> + ))} + </div> + ); + }, +); diff --git a/src/v4/social/elements/StoryProgressBar/index.ts b/src/v4/social/elements/StoryProgressBar/index.ts new file mode 100644 index 000000000..3d82aa21f --- /dev/null +++ b/src/v4/social/elements/StoryProgressBar/index.ts @@ -0,0 +1 @@ +export { StoryProgressBar } from './StoryProgressBar'; diff --git a/src/v4/social/elements/StoryReactionButton/StoryReactionButton.module.css b/src/v4/social/elements/StoryReactionButton/StoryReactionButton.module.css index 23b49733b..1032c4c10 100644 --- a/src/v4/social/elements/StoryReactionButton/StoryReactionButton.module.css +++ b/src/v4/social/elements/StoryReactionButton/StoryReactionButton.module.css @@ -1,13 +1,12 @@ .storyReactionButton { display: flex; - width: 3.5rem; - height: 2.5rem; padding: var(--asc-spacing-s1) var(--asc-spacing-s2); align-items: center; gap: var(--asc-spacing-xxs3); border-radius: var(--asc-border-radius-full); background: var(--asc-color-secondary-default); color: var(--asc-color-white); + cursor: pointer; } .storyReactionIcon { diff --git a/src/v4/social/hooks/useGetActiveStories.ts b/src/v4/social/hooks/useGetActiveStories.ts index 2f7841ca6..11ee4037e 100644 --- a/src/v4/social/hooks/useGetActiveStories.ts +++ b/src/v4/social/hooks/useGetActiveStories.ts @@ -1,79 +1,15 @@ import { StoryRepository } from '@amityco/ts-sdk'; -import { useCallback, useEffect, useRef, useState } from 'react'; -import { isNonNullable } from '~/v4/helpers/utils'; +import useLiveCollection from '~/v4/core/hooks/useLiveCollection'; -type UseStories = { - stories: (Amity.Story | undefined)[]; - hasMore: boolean; - loadMore: () => void; -}; - -export const useGetActiveStoriesByTarget = (params: Amity.GetStoriesByTargetParam): UseStories => { - const disposeFnRef = useRef<(() => void) | null>(null); - const [stories, setStories] = useState<Amity.Story[]>([]); - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState<Error | null>(null); - const [hasMore, setHasMore] = useState<boolean>(false); - const loadMoreFnRef = useRef<(() => void) | undefined | null>(null); - const [loadMoreHasBeenCalled, setLoadMoreHasBeenCalled] = useState(false); - - const loadMore = useCallback(() => { - if (loadMoreFnRef.current) { - setLoadMoreHasBeenCalled(true); - loadMoreFnRef.current?.(); - } - }, [loadMoreFnRef, loadMoreHasBeenCalled, isLoading, setIsLoading]); - - useEffect(() => { - async function run() { - if (disposeFnRef.current) { - disposeFnRef.current(); - } - - disposeFnRef.current = StoryRepository.getActiveStoriesByTarget( - { - targetId: params.targetId, - targetType: params.targetType, - options: params.options, - }, - async ({ data, hasNextPage, onNextPage }) => { - if (data) { - if (params.options?.orderBy === 'asc' && params.options?.sortBy === 'createdAt') { - const sortedData = data.filter(isNonNullable).sort((a, b) => { - // Place stories with syncing state at the end - if (a.syncState === 'syncing' && b.syncState !== 'syncing') { - return 1; - } else if (a.syncState !== 'syncing' && b.syncState === 'syncing') { - return -1; - } else { - // For other cases, maintain the original order - return 0; - } - }); - setStories(sortedData); - } else { - setStories(data.filter(isNonNullable)); - } - } - - hasNextPage && setHasMore(hasNextPage); - loadMoreFnRef.current = onNextPage; - }, - ); - } - - run(); - - return () => { - if (disposeFnRef.current) { - disposeFnRef.current(); - } - }; - }, [params.targetId]); +export const useGetActiveStoriesByTarget = (params: Amity.GetStoriesByTargetParam) => { + const { items, ...rest } = useLiveCollection({ + fetcher: StoryRepository.getActiveStoriesByTarget, + params, + shouldCall: () => true, + }); return { - stories, - hasMore, - loadMore, + stories: items, + ...rest, }; }; diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index aab1276d8..8ff838b96 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { useIntl } from 'react-intl'; import Truncate from 'react-truncate-markup'; import { @@ -16,13 +16,15 @@ import { Button } from '~/v4/core/components/Button'; import useCommunityMembersCollection from '~/v4/social/hooks/collections/useCommunityMembersCollection'; import useSDK from '~/v4/core/hooks/useSDK'; import useUser from '~/v4/core/hooks/objects/useUser'; -import useCommunityStoriesSubscription from '~/v4/social/hooks/useCommunityStoriesSubscription'; import { LIKE_REACTION_KEY } from '~/v4/social/constants/reactions'; import { checkStoryPermission, formatTimeAgo, isAdmin, isModerator } from '~/v4/social/utils'; import styles from './Renderers.module.css'; import clsx from 'clsx'; +import { StoryProgressBar } from '~/v4/social/elements/StoryProgressBar/StoryProgressBar'; +import useCommunityStoriesSubscription from '~/v4/social/hooks/useCommunityStoriesSubscription'; + export const renderer: CustomRenderer = ({ story, action, @@ -55,6 +57,10 @@ export const renderer: CustomRenderer = ({ fileInputRef, storyStyles, myReactions, + currentIndex, + storiesCount, + increaseIndex, + pageId, } = story; const { members } = useCommunityMembersCollection(community?.communityId as string); @@ -71,16 +77,22 @@ export const renderer: CustomRenderer = ({ const haveStoryPermission = isGlobalAdmin || isCommunityModerator || checkStoryPermission(client, community?.communityId); - const heading = <div data-qa-anchor="community_display_name">{community?.displayName}</div>; - const subheading = - createdAt && creator?.displayName ? ( - <span> - <span data-qa-anchor="created_at">{formatTimeAgo(createdAt as string)}</span> • By{' '} - <span data-qa-anchor="creator_display_name">{creator?.displayName}</span> - </span> - ) : ( - '' - ); + const heading = useMemo( + () => <div data-qa-anchor="community_display_name">{community?.displayName}</div>, + [community?.displayName], + ); + const subheading = useMemo( + () => + createdAt && creator?.displayName ? ( + <span> + <span data-qa-anchor="created_at">{formatTimeAgo(createdAt as string)}</span> • By{' '} + <span data-qa-anchor="creator_display_name">{creator?.displayName}</span> + </span> + ) : ( + '' + ), + [createdAt, creator?.displayName], + ); const targetRootId = 'asc-uikit-stories-viewer'; const imageLoaded = () => { @@ -88,7 +100,7 @@ export const renderer: CustomRenderer = ({ if (isPaused) { setIsPaused(false); } - action('play'); + action('play', true); }; const play = () => setIsPaused(false); @@ -128,42 +140,35 @@ export const renderer: CustomRenderer = ({ onClose(); }; + const handleProgressComplete = () => { + increaseIndex(); + }; + useEffect(() => { if (isPaused || isOpenBottomSheet || isOpenCommentSheet) { action('pause', true); } else { action('play', true); } - }, [isPaused, isOpenBottomSheet, isOpenCommentSheet]); + }, [isPaused, isOpenBottomSheet, isOpenCommentSheet, action]); useEffect(() => { action('pause', true); if (fileInputRef.current) { - fileInputRef.current.addEventListener('click', () => { - action('pause', true); - }); - fileInputRef.current.addEventListener('cancel', () => { - action('play', true); - }); - } + const handleClick = () => action('pause', true); + const handleCancel = () => action('play', true); - return () => { - if (fileInputRef.current) { - fileInputRef.current.removeEventListener('cancel', () => { - action('play', true); - }); - fileInputRef.current.removeEventListener('click', () => { - action('pause', true); - }); - } - }; - }, []); + fileInputRef.current.addEventListener('click', handleClick); + fileInputRef.current.addEventListener('cancel', handleCancel); - useCommunityStoriesSubscription({ - targetId: community?.communityId as string, - targetType: 'community', - shouldSubscribe: () => !!community?.communityId, - }); + return () => { + if (fileInputRef.current) { + fileInputRef.current.removeEventListener('cancel', handleCancel); + fileInputRef.current.removeEventListener('click', handleClick); + } + }; + } + }, [action, fileInputRef]); return ( <motion.div @@ -176,6 +181,14 @@ export const renderer: CustomRenderer = ({ onDragEnd={handleDragEnd} whileDrag={{ scale: 0.95, borderRadius: '8px', cursor: 'grabbing' }} > + <StoryProgressBar + pageId={pageId} + duration={5000} + currentIndex={currentIndex} + storiesCount={storiesCount} + isPaused={isPaused || isOpenBottomSheet || isOpenCommentSheet} + onComplete={handleProgressComplete} + /> <Header community={community} heading={heading} @@ -220,6 +233,7 @@ export const renderer: CustomRenderer = ({ > {actions?.map((bottomSheetAction) => ( <Button + key={bottomSheetAction.name} className={styles.actionButton} onClick={() => { bottomSheetAction.action(); @@ -289,20 +303,6 @@ export const renderer: CustomRenderer = ({ ); }; -const rendererStyles = { - story: { - display: 'flex', - position: 'relative', - overflow: 'hidden', - }, - storyContent: { - width: 'auto', - maxWidth: '100%', - maxHeight: '100%', - margin: 'auto', - }, -}; - export const tester: Tester = (story) => { return { condition: !story.content && (!story.type || story.type === 'image'), diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx index 8c28d3552..4b5e8fbf4 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; import { useIntl } from 'react-intl'; @@ -20,7 +20,6 @@ import { motion, PanInfo, useAnimationControls } from 'framer-motion'; import useCommunityMembersCollection from '~/v4/social/hooks/collections/useCommunityMembersCollection'; import useSDK from '~/v4/core/hooks/useSDK'; -import useImage from '~/v4/core/hooks/useImage'; import useUser from '~/v4/core/hooks/objects/useUser'; import clsx from 'clsx'; @@ -28,7 +27,7 @@ import { LIKE_REACTION_KEY } from '~/v4/social/constants/reactions'; import { checkStoryPermission, formatTimeAgo, isAdmin, isModerator } from '~/v4/social/utils'; import rendererStyles from './Renderers.module.css'; -import useCommunityStoriesSubscription from '~/v4/social/hooks/useCommunityStoriesSubscription'; +import { StoryProgressBar } from '~/v4/social/elements/StoryProgressBar/StoryProgressBar'; export const renderer: CustomRenderer = ({ story, @@ -64,6 +63,10 @@ export const renderer: CustomRenderer = ({ addStoryButton, fileInputRef, myReactions, + currentIndex, + storiesCount, + increaseIndex, + pageId, } = story; const { members } = useCommunityMembersCollection(community?.communityId as string); @@ -91,13 +94,8 @@ export const renderer: CustomRenderer = ({ const vid = useRef<HTMLVideoElement>(null); const controls = useAnimationControls(); - const onWaiting = () => { - action('pause', true); - }; - - const onPlaying = () => { - action('play', true); - }; + const onWaiting = () => action('pause', true); + const onPlaying = () => action('play', true); const videoLoaded = () => { messageHandler('UPDATE_VIDEO_DURATION', { duration: vid?.current?.duration }); @@ -163,6 +161,10 @@ export const renderer: CustomRenderer = ({ onClose(); }; + const handleProgressComplete = useCallback(() => { + increaseIndex(); + }, [increaseIndex]); + useEffect(() => { if (vid.current) { if (isPaused || isOpenBottomSheet || isOpenCommentSheet) { @@ -196,12 +198,6 @@ export const renderer: CustomRenderer = ({ }; }, []); - useCommunityStoriesSubscription({ - targetId: community?.communityId as string, - targetType: 'community', - shouldSubscribe: () => !!community?.communityId, - }); - return ( <motion.div className={clsx(rendererStyles.rendererContainer)} @@ -213,6 +209,14 @@ export const renderer: CustomRenderer = ({ onDragEnd={handleDragEnd} whileDrag={{ scale: 0.95, borderRadius: '8px', cursor: 'grabbing' }} > + <StoryProgressBar + pageId={pageId} + duration={5000} + currentIndex={currentIndex} + storiesCount={storiesCount} + isPaused={isPaused || isOpenBottomSheet || isOpenCommentSheet} + onComplete={handleProgressComplete} + /> <SpeakerButton pageId="story_page" componentId="*" diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx index e489249ae..c7ceb79eb 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx @@ -85,8 +85,7 @@ const Footer: React.FC< <div> {showImpression && ( <div className={styles.viewStoryCompostBarViewIconContainer}> - <StoryImpressionButton /> - {/* <ImpressionButton pageId="story_page" reach={reach} /> */} + <StoryImpressionButton reach={reach} /> </div> )} </div> @@ -97,19 +96,6 @@ const Footer: React.FC< reactionsCount={reactionsCount} onPress={handleClickReaction} /> - {/* <CommentButton - className={clsx(styles.viewStoryCommentButton)} - defaultIconClassName={clsx(styles.viewStoryCommentIcon)} - pageId="story_page" - commentsCount={commentsCount} - onPress={onClickComment} - /> - <ReactionButton - onReactionClick={handleClickReaction} - pageId="story_page" - myReactions={myReactions} - reactionsCount={reactionsCount} - /> */} </div> </div> ); diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/types.ts b/src/v4/social/internal-components/StoryViewer/Renderers/types.ts index 299125d54..056796ac5 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/types.ts +++ b/src/v4/social/internal-components/StoryViewer/Renderers/types.ts @@ -27,6 +27,10 @@ export type CustomStory = Story & storyStyles: { background: string; }; + currentIndex: number; + storiesCount: number; + increaseIndex: () => void; + pageId?: string; }; export type CustomRendererProps = RendererProps & { diff --git a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx index 234e04487..b7cfe7a1b 100644 --- a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import useStories from '~/social/hooks/useStories'; import useSDK from '~/core/hooks/useSDK'; @@ -26,7 +26,8 @@ import { ArrowLeftButton } from '~/v4/social/elements/ArrowLeftButton'; import { ArrowRightButton } from '~/v4/social/elements/ArrowRightButton'; import styles from './StoryPage.module.css'; -import { useAmityPage } from '~/v4/core/hooks/uikit/index'; +import { useAmityPage } from '~/v4/core/hooks/uikit'; +import { FileTrigger } from 'react-aria-components'; interface CommunityFeedStoryProps { pageId?: string; @@ -74,20 +75,24 @@ export const CommunityFeedStory = ({ }, }); - const communityFeedRenderers = renderers.map(({ renderer, tester }) => { - const newRenderer = (props: CustomRendererProps) => - renderer({ - ...props, - onClose: () => onClose(communityId), - onSwipeDown: () => onSwipeDown(communityId), - onClickCommunity: () => onClickCommunity(communityId), - }); + const communityFeedRenderers = useMemo( + () => + renderers.map(({ renderer, tester }) => { + const newRenderer = (props: CustomRendererProps) => + renderer({ + ...props, + onClose: () => onClose(communityId), + onSwipeDown: () => onSwipeDown(communityId), + onClickCommunity: () => onClickCommunity(communityId), + }); - return { - renderer: newRenderer, - tester, - }; - }); + return { + renderer: newRenderer, + tester, + }; + }), + [renderers, onClose, onSwipeDown, onClickCommunity, communityId], + ); const fileInputRef = useRef<HTMLInputElement>(null); @@ -122,80 +127,144 @@ export const CommunityFeedStory = ({ confirmDeleteStory(storyId); }; - const onCreateStory = async ( - file: File, - imageMode: 'fit' | 'fill', - metadata?: Amity.Metadata, - items?: Amity.StoryItem[], - ) => { - try { - const formData = new FormData(); - formData.append('files', file); - setFile(null); - if (file?.type.includes('image')) { - const { data: imageData } = await StoryRepository.createImageStory( - 'community', - communityId, - formData, - metadata, - imageMode, - items, - ); - if (imageData) { - notification.success({ - content: formatMessage({ id: 'storyViewer.notification.success' }), - }); - } - } else { - const { data: videoData } = await StoryRepository.createVideoStory( - 'community', - communityId, - formData, - metadata, - items, - ); - if (videoData) { - notification.success({ - content: formatMessage({ id: 'storyViewer.notification.success' }), - }); + const onCreateStory = useCallback( + async ( + file: File, + imageMode: 'fit' | 'fill', + metadata?: Amity.Metadata, + items?: Amity.StoryItem[], + ) => { + try { + const formData = new FormData(); + formData.append('files', file); + setFile(null); + if (file?.type.includes('image') && currentUserId) { + const { data: imageData } = await StoryRepository.createImageStory( + 'user', + currentUserId, + formData, + metadata, + imageMode, + items, + ); + if (imageData) { + notification.success({ + content: formatMessage({ id: 'storyViewer.notification.success' }), + }); + } + } else { + if (currentUserId) { + const { data: videoData } = await StoryRepository.createVideoStory( + 'user', + currentUserId, + formData, + metadata, + items, + ); + if (videoData) { + notification.success({ + content: formatMessage({ id: 'storyViewer.notification.success' }), + }); + } + } } + } catch (error) { + notification.error({ + content: formatMessage({ id: 'storyViewer.notification.error' }), + }); } - } catch (error) { - notification.error({ - content: formatMessage({ id: 'storyViewer.notification.error' }), - }); - } - }; + }, + [currentUserId, formatMessage, notification, setFile], + ); const discardStory = () => { setFile(null); }; + const addStoryButton = useMemo( + () => ( + <FileTrigger + ref={fileInputRef} + onSelect={(e) => { + const files = Array.from(e as FileList); + setFile(files[0]); + }} + > + <CreateNewStoryButton pageId={pageId} /> + </FileTrigger> + ), + [pageId, setFile], + ); - const addStoryButton = <CreateNewStoryButton pageId={pageId} />; - - const formattedStories = stories?.map((story) => { - const isImage = story?.dataType === 'image'; - const url = isImage ? story?.imageData?.fileUrl : story?.videoData?.videoUrl?.['720p']; - - return { - ...story, - url, - type: isImage ? 'image' : 'video', - actions: [ - isStoryCreator || isModerator - ? { - name: 'delete', - action: () => deleteStory(story?.storyId as string), - icon: <Trash2Icon />, - } - : null, - ].filter(isNonNullable), + const storyStyles = useMemo( + () => ({ + width: '100%', + height: '100%', + objectFit: + stories[currentIndex]?.dataType === 'image' && + stories[currentIndex]?.data?.imageDisplayMode === 'fill' + ? 'cover' + : 'contain', + background: `linear-gradient( + 180deg, + ${colors?.length > 0 ? colors[0].hex : '#000'} 0%, + ${colors?.length > 0 ? colors[colors?.length - 1].hex : '#000'} 100% + )`, + }), + [stories, currentIndex, colors], + ); + + const increaseIndex = () => { + setCurrentIndex((prevIndex) => prevIndex + 1); + }; + + const formattedStories = useMemo( + () => + stories?.map((story) => { + const isImage = story?.dataType === 'image'; + const url = isImage ? story?.imageData?.fileUrl : story?.videoData?.videoUrl?.['720p']; + + return { + ...story, + url, + type: isImage ? 'image' : 'video', + actions: [ + isStoryCreator || isModerator + ? { + name: 'delete', + action: () => deleteStory(story?.storyId as string), + icon: ( + <Trash2Icon + fill={getComputedStyle(document.documentElement).getPropertyValue( + '--asc-color-base-default', + )} + /> + ), + } + : null, + ].filter(isNonNullable), + onCreateStory, + discardStory, + addStoryButton, + fileInputRef, + storyStyles, + currentIndex, + storiesCount: stories?.length, + increaseIndex, + pageId, + }; + }), + [ + stories, + deleteStory, onCreateStory, discardStory, addStoryButton, fileInputRef, - }; - }); + storyStyles, + currentIndex, + increaseIndex, + ], + ); const nextStory = () => { if (currentIndex === stories.length - 1) { @@ -212,25 +281,6 @@ export const CommunityFeedStory = ({ const targetRootId = 'asc-uikit-stories-viewer'; - const storyStyles = { - width: '100%', - height: '100%', - objectFit: - stories[currentIndex]?.dataType === 'image' && - stories[currentIndex]?.data?.imageDisplayMode === 'fill' - ? 'cover' - : 'contain', - background: `linear-gradient( - 180deg, - ${colors?.length > 0 ? colors[0].hex : '#000'} 0%, - ${colors?.length > 0 ? colors[colors?.length - 1].hex : '#000'} 100% - )`, - }; - - const increaseIndex = () => { - setCurrentIndex(currentIndex + 1); - }; - useEffect(() => { if (stories[stories.length - 1]?.syncState === 'syncing') { setCurrentIndex(stories.length - 1); @@ -288,7 +338,9 @@ export const CommunityFeedStory = ({ {formattedStories?.length > 0 ? ( // NOTE: Do not use isPaused prop, it will cause the first video story skipped <Stories - storyStyles={storyStyles} + progressWrapperStyles={{ + display: 'none', + }} preventDefault currentIndex={currentIndex} stories={formattedStories} @@ -298,7 +350,7 @@ export const CommunityFeedStory = ({ onStoryEnd={increaseIndex} onNext={nextStory} onPrevious={previousStory} - onAllStoriesEnd={onBack} + onAllStoriesEnd={nextStory} /> ) : null} </div> diff --git a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx index 263ce460c..5f55a4a11 100644 --- a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useIntl } from 'react-intl'; import { FinalColor } from 'extract-colors/lib/types/Color'; import { StoryRepository } from '@amityco/ts-sdk'; @@ -56,11 +56,17 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ onSwipeDown, onClickCommunity, }) => { - const { accessibilityId } = useAmityPage({ - pageId, - }); + const { accessibilityId } = useAmityPage({ pageId }); const { confirm } = useConfirmContext(); const notification = useNotifications(); + const { formatMessage } = useIntl(); + const { client, currentUserId } = useSDK(); + const { file, setFile } = useStoryContext(); + + const [currentIndex, setCurrentIndex] = useState(0); + const [colors, setColors] = useState<FinalColor[]>([]); + + const fileInputRef = useRef<HTMLInputElement>(null); const { stories } = useGetActiveStoriesByTarget({ targetType: 'community', @@ -71,36 +77,26 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ }, }); - const globalFeedRenderers = renderers.map(({ renderer, tester }) => { - const newRenderer = (props: CustomRendererProps) => - renderer({ - ...props, - onClose: () => { - onClose(targetId); - }, - onSwipeDown: () => onSwipeDown(targetId), - onClickCommunity: () => onClickCommunity(targetId), - }); - - return { - renderer: newRenderer, - tester, - }; - }); - - const fileInputRef = useRef<HTMLInputElement>(null); - - const { client, currentUserId } = useSDK(); - - const { formatMessage } = useIntl(); - - const [currentIndex, setCurrentIndex] = useState(0); - const { file, setFile } = useStoryContext(); - const [colors, setColors] = useState<FinalColor[]>([]); - const isStoryCreator = stories[currentIndex]?.creator?.userId === currentUserId; const isModerator = checkStoryPermission(client, stories[currentIndex]?.targetId); + const previousStory = () => { + if (currentIndex === 0) { + const currentTargetIndex = targetIds.indexOf(targetId); + const previousTargetIndex = currentTargetIndex - 1; + + if (previousTargetIndex >= 0) { + const previousTargetId = targetIds[previousTargetIndex]; + onClickStory(previousTargetId); + } else { + onChangePage?.(); + } + setCurrentIndex(0); + return; + } + setCurrentIndex((prevIndex) => prevIndex - 1); + }; + const confirmDeleteStory = (storyId: string) => { const isLastStory = currentIndex === 0; confirm({ @@ -116,7 +112,7 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ content: formatMessage({ id: 'storyViewer.notification.deleted' }), }); if (isLastStory && stories.length > 1) { - setCurrentIndex(currentIndex - 1); + setCurrentIndex((prevIndex) => prevIndex - 1); } else if (stories.length === 1) { onChangePage?.(); } @@ -124,118 +120,149 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ }); }; - const deleteStory = async (storyId: string) => { + const deleteStory = (storyId: string) => { confirmDeleteStory(storyId); }; - const onCreateStory = async ( - file: File, - imageMode: 'fit' | 'fill', - metadata?: Amity.Metadata, - items?: Amity.StoryItem[], - ) => { - try { - const formData = new FormData(); - formData.append('files', file); - setFile(null); - if (file?.type.includes('image') && currentUserId) { - const { data: imageData } = await StoryRepository.createImageStory( - 'user', - currentUserId, - formData, - metadata, - imageMode, - items, - ); - if (imageData) { - notification.success({ - content: formatMessage({ id: 'storyViewer.notification.success' }), - }); - } - } else { - if (currentUserId) { - const { data: videoData } = await StoryRepository.createVideoStory( + const onCreateStory = useCallback( + async ( + file: File, + imageMode: 'fit' | 'fill', + metadata?: Amity.Metadata, + items?: Amity.StoryItem[], + ) => { + try { + const formData = new FormData(); + formData.append('files', file); + setFile(null); + if (file?.type.includes('image') && currentUserId) { + const { data: imageData } = await StoryRepository.createImageStory( 'user', currentUserId, formData, metadata, + imageMode, items, ); - if (videoData) { + if (imageData) { notification.success({ content: formatMessage({ id: 'storyViewer.notification.success' }), }); } + } else { + if (currentUserId) { + const { data: videoData } = await StoryRepository.createVideoStory( + 'user', + currentUserId, + formData, + metadata, + items, + ); + if (videoData) { + notification.success({ + content: formatMessage({ id: 'storyViewer.notification.success' }), + }); + } + } } + } catch (error) { + notification.error({ + content: formatMessage({ id: 'storyViewer.notification.error' }), + }); } - } catch (error) { - notification.error({ - content: formatMessage({ id: 'storyViewer.notification.error' }), - }); - } - }; + }, + [currentUserId, formatMessage, notification, setFile], + ); const discardStory = () => { setFile(null); }; - const addStoryButton = ( - <FileTrigger - ref={fileInputRef} - onSelect={(e) => { - const files = Array.from(e as FileList); - setFile(files[0]); - }} - > - <CreateNewStoryButton pageId={pageId} /> - </FileTrigger> + const addStoryButton = useMemo( + () => ( + <FileTrigger + ref={fileInputRef} + onSelect={(e) => { + const files = Array.from(e as FileList); + setFile(files[0]); + }} + > + <CreateNewStoryButton pageId={pageId} /> + </FileTrigger> + ), + [pageId, setFile], ); - const storyStyles = { - width: '100%', - height: '100%', - objectFit: - stories[currentIndex]?.dataType === 'image' && - stories[currentIndex]?.data?.imageDisplayMode === 'fill' - ? 'cover' - : 'contain', - background: `linear-gradient( + const storyStyles = useMemo( + () => ({ + width: '100%', + height: '100%', + objectFit: + stories[currentIndex]?.dataType === 'image' && + stories[currentIndex]?.data?.imageDisplayMode === 'fill' + ? 'cover' + : 'contain', + background: `linear-gradient( 180deg, ${colors?.length > 0 ? colors[0].hex : '#000'} 0%, ${colors?.length > 0 ? colors[colors?.length - 1].hex : '#000'} 100% )`, + }), + [stories, currentIndex, colors], + ); + + const increaseIndex = () => { + setCurrentIndex((prevIndex) => prevIndex + 1); }; - const formattedStories = stories?.map((story) => { - const isImage = story?.dataType === 'image'; - const url = isImage ? story?.imageData?.fileUrl : story?.videoData?.videoUrl?.['720p']; - - return { - ...story, - url, - type: isImage ? 'image' : 'video', - actions: [ - isStoryCreator || isModerator - ? { - name: 'delete', - action: () => deleteStory(story?.storyId as string), - icon: ( - <TrashIcon - fill={getComputedStyle(document.documentElement).getPropertyValue( - '--asc-color-base-default', - )} - /> - ), - } - : null, - ].filter(isNonNullable), + const formattedStories = useMemo( + () => + stories?.map((story) => { + const isImage = story?.dataType === 'image'; + const url = isImage ? story?.imageData?.fileUrl : story?.videoData?.videoUrl?.['720p']; + + return { + ...story, + url, + type: isImage ? 'image' : 'video', + actions: [ + isStoryCreator || isModerator + ? { + name: 'delete', + action: () => deleteStory(story?.storyId as string), + icon: ( + <TrashIcon + fill={getComputedStyle(document.documentElement).getPropertyValue( + '--asc-color-base-default', + )} + /> + ), + } + : null, + ].filter(isNonNullable), + onCreateStory, + discardStory, + addStoryButton, + fileInputRef, + storyStyles, + currentIndex, + storiesCount: stories?.length, + increaseIndex, + pageId, + }; + }), + [ + stories, + deleteStory, onCreateStory, discardStory, addStoryButton, fileInputRef, storyStyles, - }; - }); + currentIndex, + increaseIndex, + ], + ); const nextStory = () => { if (currentIndex === formattedStories?.length - 1) { @@ -251,32 +278,30 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ setCurrentIndex(0); return; } - setCurrentIndex(currentIndex + 1); + setCurrentIndex((prevIndex) => prevIndex + 1); }; - const previousStory = () => { - if (currentIndex === 0) { - const currentTargetIndex = targetIds.indexOf(targetId); - const previousTargetIndex = currentTargetIndex - 1; + const globalFeedRenderers = useMemo( + () => + renderers.map(({ renderer, tester }) => { + const newRenderer = (props: CustomRendererProps) => + renderer({ + ...props, + onClose: () => onClose(targetId), + onSwipeDown: () => onSwipeDown(targetId), + onClickCommunity: () => onClickCommunity(targetId), + }); - if (previousTargetIndex >= 0) { - const previousTargetId = targetIds[previousTargetIndex]; - onClickStory(previousTargetId); - } else { - onChangePage?.(); - } - setCurrentIndex(0); - return; - } - setCurrentIndex(currentIndex - 1); - }; + return { + renderer: newRenderer, + tester, + }; + }), + [renderers, onClose, onSwipeDown, onClickCommunity, targetId], + ); const targetRootId = 'asc-uikit-stories-viewer'; - const increaseIndex = () => { - setCurrentIndex(currentIndex + 1); - }; - useEffect(() => { if (stories[stories.length - 1]?.syncState === 'syncing') { setCurrentIndex(stories.length - 1); @@ -305,8 +330,8 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ setColors(colorsFromImage); }; - if (file?.type.includes('image') || stories[currentIndex]?.dataType === 'image') { - extractColorsFromImage((stories[currentIndex]?.imageData?.fileUrl as string) ?? file); + if (stories[currentIndex]?.dataType === 'image') { + extractColorsFromImage(stories[currentIndex]?.imageData?.fileUrl as string); } else { setColors([]); } @@ -334,6 +359,10 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ {formattedStories?.length > 0 ? ( // NOTE: Do not use isPaused prop, it will cause the first video story skipped <Stories + // hide default progress bar + progressWrapperStyles={{ + display: 'none', + }} preventDefault currentIndex={currentIndex} stories={formattedStories} From f251eb467d6af1eedc423205e66004e65d92e030 Mon Sep 17 00:00:00 2001 From: Pitchaya T <33589608+ptchayap@users.noreply.github.com> Date: Mon, 24 Jun 2024 19:32:39 +0700 Subject: [PATCH 157/300] Release/v4.0.0 beta.7 (#439) * chore(release): 4.0.0-beta.5 * chore(release): 4.0.0-beta.6 * fix: build include css (#421) * chore(release): 4.0.0-beta.7 --------- Co-authored-by: bmo-amity-bot <developers@amity.co> --- CHANGELOG.md | 14 ++++++++++++++ package.json | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f656294b4..0bf1c46d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## 4.0.0-beta.7 (2024-06-18) + +### Bug Fixes + +- build include css ([#421](https://github.com/AmityCo/Amity-Social-Cloud-UIKit-Web/issues/421)) ([1dea7f5](https://github.com/AmityCo/Amity-Social-Cloud-UIKit-Web/commit/1dea7f50a9f70491e217f639be8159c0c20b6b65)) + +## 4.0.0-beta.6 (2024-06-07) + +## 4.0.0-beta.5 (2024-06-07) + +### Bug Fixes + +- ASC-21507 - reset form when confirm remove hyperlink ([#368](https://github.com/AmityCo/Amity-Social-Cloud-UIKit-Web/issues/368)) ([c80ef32](https://github.com/AmityCo/Amity-Social-Cloud-UIKit-Web/commit/c80ef32e3b88a0a5ccd407f7f75d8366d9b3e152)) + ## 4.0.0-beta.4 (2024-05-13) ### Bug Fixes diff --git a/package.json b/package.json index 578d25e00..787a8195f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@amityco/ui-kit", - "version": "4.0.0-beta.4", + "version": "4.0.0-beta.7", "engines": { "node": ">=20", "pnpm": "9" From 19c0669433f7364c8f7653b99de57cc8421f527c Mon Sep 17 00:00:00 2001 From: Pitchaya T <33589608+ptchayap@users.noreply.github.com> Date: Mon, 24 Jun 2024 19:51:37 +0700 Subject: [PATCH 158/300] Release/v4.0.0 beta.8 (#440) * build: upgrade version ts-sdk * build: upgrade dependencies * chore(release): 4.0.0-beta.8 --------- Co-authored-by: bmo-amity-bot <developers@amity.co> --- CHANGELOG.md | 2 ++ package.json | 6 +++--- pnpm-lock.yaml | 38 +++++++++++++++++++------------------- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bf1c46d0..0c67baaa1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## 4.0.0-beta.8 (2024-06-24) + ## 4.0.0-beta.7 (2024-06-18) ### Bug Fixes diff --git a/package.json b/package.json index 787a8195f..38195afcd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@amityco/ui-kit", - "version": "4.0.0-beta.7", + "version": "4.0.0-beta.8", "engines": { "node": ">=20", "pnpm": "9" @@ -39,12 +39,12 @@ "tsc": "tsc" }, "peerDependencies": { - "@amityco/ts-sdk": "^6.26.3", + "@amityco/ts-sdk": "^6.27.0", "react": ">=17.0.2", "react-dom": ">=17.0.2" }, "devDependencies": { - "@amityco/ts-sdk": "^6.26.3", + "@amityco/ts-sdk": "^6.27.0", "@storybook/addon-a11y": "^7.6.7", "@storybook/addon-actions": "^7.6.7", "@storybook/addon-backgrounds": "^7.6.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b410acbec..98d4851df 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -124,8 +124,8 @@ importers: version: 3.23.8 devDependencies: '@amityco/ts-sdk': - specifier: ^6.26.3 - version: 6.26.3 + specifier: ^6.27.0 + version: 6.27.0 '@storybook/addon-a11y': specifier: ^7.6.7 version: 7.6.19 @@ -269,10 +269,10 @@ importers: version: 8.0.1(stylelint@16.6.1(typescript@4.9.5)) svg-url-loader: specifier: ^7.1.1 - version: 7.1.1(webpack@5.92.0(esbuild@0.18.20)) + version: 7.1.1(webpack@5.92.0(esbuild@0.19.12)) ts-jest: specifier: ^29.1.1 - version: 29.1.5(@babel/core@7.24.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(esbuild@0.18.20)(jest@29.7.0(@types/node@20.14.4))(typescript@4.9.5) + version: 29.1.5(@babel/core@7.24.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(esbuild@0.19.12)(jest@29.7.0(@types/node@20.14.4))(typescript@4.9.5) tsup: specifier: ^7.3.0 version: 7.3.0(postcss@8.4.38)(typescript@4.9.5) @@ -294,8 +294,8 @@ packages: '@adobe/css-tools@4.3.3': resolution: {integrity: sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==} - '@amityco/ts-sdk@6.26.3': - resolution: {integrity: sha512-Fw9g0hLVs8GYUBHVdSQpV/n7JZRRFkFolHTKh42w4aVy7dwYRj0NhiRtQpSNfIxCoQWHG6fwL7ttC3L1ZWheOQ==} + '@amityco/ts-sdk@6.27.0': + resolution: {integrity: sha512-6I55XYqhet6MDlB/qw9YunFO1BD2TwZSr2BBZ3+dWsBjBVp8BTDr7rPy74gpoVmPWRVbD4XTRM1c8dPeWeLEPQ==} engines: {node: '>=12', npm: '>=6'} '@ampproject/remapping@2.3.0': @@ -7519,7 +7519,7 @@ snapshots: '@adobe/css-tools@4.3.3': {} - '@amityco/ts-sdk@6.26.3': + '@amityco/ts-sdk@6.27.0': dependencies: agentkeepalive: 4.5.0 axios: 1.7.2(debug@4.3.5) @@ -12920,11 +12920,11 @@ snapshots: dependencies: flat-cache: 5.0.0 - file-loader@6.2.0(webpack@5.92.0(esbuild@0.18.20)): + file-loader@6.2.0(webpack@5.92.0(esbuild@0.19.12)): dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.92.0(esbuild@0.18.20) + webpack: 5.92.0(esbuild@0.19.12) file-system-cache@2.3.0: dependencies: @@ -15791,11 +15791,11 @@ snapshots: svg-tags@1.0.0: {} - svg-url-loader@7.1.1(webpack@5.92.0(esbuild@0.18.20)): + svg-url-loader@7.1.1(webpack@5.92.0(esbuild@0.19.12)): dependencies: - file-loader: 6.2.0(webpack@5.92.0(esbuild@0.18.20)) + file-loader: 6.2.0(webpack@5.92.0(esbuild@0.19.12)) loader-utils: 2.0.4 - webpack: 5.92.0(esbuild@0.18.20) + webpack: 5.92.0(esbuild@0.19.12) synchronous-promise@2.0.17: {} @@ -15851,16 +15851,16 @@ snapshots: type-fest: 0.16.0 unique-string: 2.0.0 - terser-webpack-plugin@5.3.10(esbuild@0.18.20)(webpack@5.92.0(esbuild@0.18.20)): + terser-webpack-plugin@5.3.10(esbuild@0.19.12)(webpack@5.92.0(esbuild@0.19.12)): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 terser: 5.31.1 - webpack: 5.92.0(esbuild@0.18.20) + webpack: 5.92.0(esbuild@0.19.12) optionalDependencies: - esbuild: 0.18.20 + esbuild: 0.19.12 terser@5.31.1: dependencies: @@ -15945,7 +15945,7 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.1.5(@babel/core@7.24.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(esbuild@0.18.20)(jest@29.7.0(@types/node@20.14.4))(typescript@4.9.5): + ts-jest@29.1.5(@babel/core@7.24.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(esbuild@0.19.12)(jest@29.7.0(@types/node@20.14.4))(typescript@4.9.5): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 @@ -15962,7 +15962,7 @@ snapshots: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 babel-jest: 29.7.0(@babel/core@7.24.7) - esbuild: 0.18.20 + esbuild: 0.19.12 tsconfck@3.1.0(typescript@4.9.5): optionalDependencies: @@ -16291,7 +16291,7 @@ snapshots: webpack-virtual-modules@0.6.2: {} - webpack@5.92.0(esbuild@0.18.20): + webpack@5.92.0(esbuild@0.19.12): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.5 @@ -16314,7 +16314,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(esbuild@0.18.20)(webpack@5.92.0(esbuild@0.18.20)) + terser-webpack-plugin: 5.3.10(esbuild@0.19.12)(webpack@5.92.0(esbuild@0.19.12)) watchpack: 2.4.1 webpack-sources: 3.2.3 transitivePeerDependencies: From 97436375faf2c6a92347b220e12d8ab932a584ba Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Tue, 25 Jun 2024 14:03:50 +0700 Subject: [PATCH 159/300] feat: ASC-22898 - create post page (#437) * feat: show select post menu * feat: create PostComposerPage * feat: themeStyle * style: change create menu color * feat: implement ui CreatePostMenu * feat: add event when click createPostMenuButton * feat: implement CreateStoryButtons * feat: implement CreatePollButton * feat: implement CreateLivestreamButton * fix: changeable text * feat: implement SelectPostTargetPage * fix: pageId * fix: text static * style: pointer * feat: integrate API communites to SelectPostTargetPage * feat: add UserAvatar * fix: map key * style: change px to rem * fix: dark theme styles * reafactor: remove blank line * feat: create new post page * feat: DetailMediaAttachment * feat: implement media attachment * feat: show mention options * feat: mention * feat: disable post button * feat: add @ in mention text editor * feat: lexical * feat: add goToPostComposerPage * style: truncate * style: text size * feat: post to feed * style: editor * feat: limit max length 5000 cha * fix: follow comment pr * fix: error class * feat: onSubmit form * fix: query search mention from hooks * fix: remove try catch and remove comments * fix: mention post * fix: build failed --- package.json | 3 + pnpm-lock.yaml | 448 +++++++++++++++--- src/v4/core/components/Notification/index.tsx | 2 +- src/v4/core/providers/AmityUIKitProvider.tsx | 80 ++-- .../core/providers/CustomizationProvider.tsx | 6 +- src/v4/core/providers/NavigationProvider.tsx | 62 +++ .../core/providers/PageBehaviorProvider.tsx | 44 ++ .../CreatePostMenu/CreatePostMenu.tsx | 7 - .../DetailedMediaAttachment.module.css | 21 + .../DetailedMediaAttachment.tsx | 32 ++ .../DetailedMediaAttachment/index.tsx | 1 + .../MediaAttachment.module.css | 27 ++ .../MediaAttachment/MediaAttachment.tsx | 30 ++ .../components/MediaAttachment/index.tsx | 1 + .../TopNavigation/TopNavigation.tsx | 13 +- .../CameraButton/CameraButton.module.css | 18 + .../elements/CameraButton/CameraButton.tsx | 75 +++ src/v4/social/elements/CameraButton/index.tsx | 1 + .../CommunityDisplayName.module.css | 2 + .../CommunityDisplayName.tsx | 15 +- .../CreateNewPostButton.module.css | 12 + .../CreateNewPostButton.tsx | 37 ++ .../elements/CreateNewPostButton/index.tsx | 1 + .../CreateStoryButton.module.css | 15 + .../CreateStoryButtons/CreateStoryButton.tsx | 63 +++ .../elements/CreateStoryButtons/index.tsx | 1 + .../elements/FileButton/FileButton.module.css | 18 + .../social/elements/FileButton/FileButton.tsx | 69 +++ src/v4/social/elements/FileButton/index.tsx | 1 + .../ImageButton/ImageButton.module.css | 18 + .../elements/ImageButton/ImageButton.tsx | 76 +++ src/v4/social/elements/ImageButton/index.tsx | 1 + .../MyTimelineText/MyTimelineText.module.css | 2 +- .../PostCreationButton.module.css | 3 +- .../PostCreationButton/PostCreationButton.tsx | 7 +- .../PostTextField/PostTextField.module.css | 34 ++ .../elements/PostTextField/PostTextField.tsx | 98 ++++ .../social/elements/PostTextField/index.tsx | 1 + .../VideoButton/VideoButton.module.css | 18 + .../elements/VideoButton/VideoButton.tsx | 74 +++ src/v4/social/elements/VideoButton/index.tsx | 1 + .../CommunityMember.module.css | 27 ++ .../CommunityMember/CommunityMember.tsx | 46 ++ .../CommunityMember/index.tsx | 1 + .../MentionTextInput/MentionNodes.ts | 195 ++++++++ .../MentionTextInput.module.css | 6 + .../MentionTextInput/MentionTextInput.tsx | 228 +++++++++ .../MentionTextInput/index.tsx | 1 + src/v4/social/pages/Application/index.tsx | 4 +- .../PostComposerPage.module.css | 36 ++ .../PostComposerPage.stories.tsx | 12 + .../PostComposerPage/PostComposerPage.tsx | 156 ++++++ .../social/pages/PostComposerPage/index.tsx | 1 + .../SelectPostTargetPage.module.css | 3 +- .../SelectPostTargetPage.stories.tsx | 12 + .../SelectPostTargetPage.tsx | 25 +- .../pages/SocialHomePage/SocialHomePage.tsx | 56 ++- 57 files changed, 2062 insertions(+), 185 deletions(-) create mode 100644 src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.module.css create mode 100644 src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.tsx create mode 100644 src/v4/social/components/DetailedMediaAttachment/index.tsx create mode 100644 src/v4/social/components/MediaAttachment/MediaAttachment.module.css create mode 100644 src/v4/social/components/MediaAttachment/MediaAttachment.tsx create mode 100644 src/v4/social/components/MediaAttachment/index.tsx create mode 100644 src/v4/social/elements/CameraButton/CameraButton.module.css create mode 100644 src/v4/social/elements/CameraButton/CameraButton.tsx create mode 100644 src/v4/social/elements/CameraButton/index.tsx create mode 100644 src/v4/social/elements/CreateNewPostButton/CreateNewPostButton.module.css create mode 100644 src/v4/social/elements/CreateNewPostButton/CreateNewPostButton.tsx create mode 100644 src/v4/social/elements/CreateNewPostButton/index.tsx create mode 100644 src/v4/social/elements/CreateStoryButtons/CreateStoryButton.module.css create mode 100644 src/v4/social/elements/CreateStoryButtons/CreateStoryButton.tsx create mode 100644 src/v4/social/elements/CreateStoryButtons/index.tsx create mode 100644 src/v4/social/elements/FileButton/FileButton.module.css create mode 100644 src/v4/social/elements/FileButton/FileButton.tsx create mode 100644 src/v4/social/elements/FileButton/index.tsx create mode 100644 src/v4/social/elements/ImageButton/ImageButton.module.css create mode 100644 src/v4/social/elements/ImageButton/ImageButton.tsx create mode 100644 src/v4/social/elements/ImageButton/index.tsx create mode 100644 src/v4/social/elements/PostTextField/PostTextField.module.css create mode 100644 src/v4/social/elements/PostTextField/PostTextField.tsx create mode 100644 src/v4/social/elements/PostTextField/index.tsx create mode 100644 src/v4/social/elements/VideoButton/VideoButton.module.css create mode 100644 src/v4/social/elements/VideoButton/VideoButton.tsx create mode 100644 src/v4/social/elements/VideoButton/index.tsx create mode 100644 src/v4/social/internal-components/CommunityMember/CommunityMember.module.css create mode 100644 src/v4/social/internal-components/CommunityMember/CommunityMember.tsx create mode 100644 src/v4/social/internal-components/CommunityMember/index.tsx create mode 100644 src/v4/social/internal-components/MentionTextInput/MentionNodes.ts create mode 100644 src/v4/social/internal-components/MentionTextInput/MentionTextInput.module.css create mode 100644 src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx create mode 100644 src/v4/social/internal-components/MentionTextInput/index.tsx create mode 100644 src/v4/social/pages/PostComposerPage/PostComposerPage.module.css create mode 100644 src/v4/social/pages/PostComposerPage/PostComposerPage.stories.tsx create mode 100644 src/v4/social/pages/PostComposerPage/PostComposerPage.tsx create mode 100644 src/v4/social/pages/PostComposerPage/index.tsx create mode 100644 src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.stories.tsx diff --git a/package.json b/package.json index 38195afcd..fd8a96b3c 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "@types/lodash": "^4.14.202", "@types/prop-types": "^15.7.11", "@types/react": "^17.0.74", + "@types/react-dom": "^18.3.0", "@types/react-helmet": "^6.1.11", "@types/react-infinite-scroller": "^1.2.5", "@types/react-mentions": "^4.1.13", @@ -106,6 +107,7 @@ "@fortawesome/react-fontawesome": "^0.2.0", "@hookform/error-message": "^2.0.1", "@hookform/resolvers": "^3.3.4", + "@lexical/react": "^0.16.0", "@radix-ui/react-tabs": "^1.0.4", "@tanstack/react-query": "^5.28.14", "clsx": "^2.1.0", @@ -114,6 +116,7 @@ "filesize": "^9.0.11", "framer-motion": "^11.1.7", "hls.js": "^1.4.14", + "lexical": "^0.16.0", "linkify-react": "^4.1.3", "linkifyjs": "^4.1.3", "lodash": "^4.17.21", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 98d4851df..c57a17b7b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,9 +23,12 @@ importers: '@hookform/resolvers': specifier: ^3.3.4 version: 3.6.0(react-hook-form@7.52.0(react@18.3.1)) + '@lexical/react': + specifier: ^0.16.0 + version: 0.16.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(yjs@13.6.17) '@radix-ui/react-tabs': specifier: ^1.0.4 - version: 1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 1.0.4(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@tanstack/react-query': specifier: ^5.28.14 version: 5.45.1(react@18.3.1) @@ -47,6 +50,9 @@ importers: hls.js: specifier: ^1.4.14 version: 1.5.11 + lexical: + specifier: ^0.16.0 + version: 0.16.0 linkify-react: specifier: ^4.1.3 version: 4.1.3(linkifyjs@4.1.3)(react@18.3.1) @@ -118,7 +124,7 @@ importers: version: 8.3.2 vaul: specifier: ^0.9.1 - version: 0.9.1(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 0.9.1(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) zod: specifier: ^3.22.4 version: 3.23.8 @@ -137,10 +143,10 @@ importers: version: 7.6.19 '@storybook/addon-controls': specifier: ^7.6.7 - version: 7.6.19(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 7.6.19(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@storybook/addon-essentials': specifier: ^7.6.7 - version: 7.6.19(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 7.6.19(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@storybook/addon-toolbars': specifier: ^7.6.7 version: 7.6.19 @@ -174,6 +180,9 @@ importers: '@types/react': specifier: ^17.0.74 version: 17.0.80 + '@types/react-dom': + specifier: ^18.3.0 + version: 18.3.0 '@types/react-helmet': specifier: ^6.1.11 version: 6.1.11 @@ -1515,6 +1524,77 @@ packages: '@juggle/resize-observer@3.4.0': resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==} + '@lexical/clipboard@0.16.0': + resolution: {integrity: sha512-eYMJ6jCXpWBVC05Mu9HLMysrBbfi++xFfsm+Yo7A6kYGrqYUhpXqjJkYnw1xdZYL3bV73Oe4ByVJuq42GU+Mqw==} + + '@lexical/code@0.16.0': + resolution: {integrity: sha512-1EKCBSFV745UI2zn5v75sKcvVdmd+y2JtZhw8CItiQkRnBLv4l4d/RZYy+cKOuXJGsoBrKtxXn5sl7HebwQbPw==} + + '@lexical/devtools-core@0.16.0': + resolution: {integrity: sha512-Jt8p0J0UoMHf3UMh3VdyrXbLLwpEZuMqihTmbPRpwo+YQ6NGQU35QgwY2K0DpPAThpxL/Cm7uaFqGOy8Kjrhqw==} + peerDependencies: + react: '>=17.x' + react-dom: '>=17.x' + + '@lexical/dragon@0.16.0': + resolution: {integrity: sha512-Yr29SFZzOPs+S6UrEZaXnnso1fJGVfZOXVJQZbyzlspqJpSHXVH7InOXYHWN6JSWQ8Hs/vU3ksJXwqz+0TCp2g==} + + '@lexical/hashtag@0.16.0': + resolution: {integrity: sha512-2EdAvxYVYqb0nv6vgxCRgE8ip7yez5p0y0oeUyxmdbcfZdA+Jl90gYH3VdevmZ5Bk3wE0/fIqiLD+Bb5smqjCQ==} + + '@lexical/history@0.16.0': + resolution: {integrity: sha512-xwFxgDZGviyGEqHmgt6A6gPhsyU/yzlKRk9TBUVByba3khuTknlJ1a80H5jb+OYcrpiElml7iVuGYt+oC7atCA==} + + '@lexical/html@0.16.0': + resolution: {integrity: sha512-okxn3q/1qkUpCZNEFRI39XeJj4YRjb6prm3WqZgP4d39DI1W24feeTZJjYRCW+dc3NInwFaolU3pNA2MGkjRtg==} + + '@lexical/link@0.16.0': + resolution: {integrity: sha512-ppvJSh/XGqlzbeymOiwcXJcUcrqgQqTK2QXTBAZq7JThtb0WsJxYd2CSLSN+Ycu23prnwqOqILcU0+34+gAVFw==} + + '@lexical/list@0.16.0': + resolution: {integrity: sha512-nBx/DMM7nCgnOzo1JyNnVaIrk/Xi5wIPNi8jixrEV6w9Om2K6dHutn/79Xzp2dQlNGSLHEDjky6N2RyFgmXh0g==} + + '@lexical/mark@0.16.0': + resolution: {integrity: sha512-WMR4nqygSgIQ6Vdr5WAzohxBGjH+m44dBNTbWTGZGVlRvPzvBT6tieCoxFqpceIq/ko67HGTCNoFj2cMKVwgIA==} + + '@lexical/markdown@0.16.0': + resolution: {integrity: sha512-7HQLFrBbpY68mcq4A6C1qIGmjgA+fAByditi2WRe7tD2eoIKb/B5baQAnDKis0J+m5kTaCBmdlT6csSzyOPzeQ==} + + '@lexical/offset@0.16.0': + resolution: {integrity: sha512-4TqPEC2qA7sgO8Tm65nOWnhJ8dkl22oeuGv9sUB+nhaiRZnw3R45mDelg23r56CWE8itZnvueE7TKvV+F3OXtQ==} + + '@lexical/overflow@0.16.0': + resolution: {integrity: sha512-a7gtIRxleEuMN9dj2yO4CdezBBfIr9Mq+m7G5z62+xy7VL7cfMfF+xWjy3EmDYDXS4vOQgAXAUgO4oKz2AKGhQ==} + + '@lexical/plain-text@0.16.0': + resolution: {integrity: sha512-BK7/GSOZUHRJTbNPkpb9a/xN9z+FBCdunTsZhnOY8pQ7IKws3kuMO2Tk1zXfTd882ZNAxFdDKNdLYDSeufrKpw==} + + '@lexical/react@0.16.0': + resolution: {integrity: sha512-WKFQbI0/m1YkLjL5t90YLJwjGcl5QRe6mkfm3ljQuL7Ioj3F92ZN/J2gHFVJ9iC8/lJs6Zzw6oFjiP8hQxJf9Q==} + peerDependencies: + react: '>=17.x' + react-dom: '>=17.x' + + '@lexical/rich-text@0.16.0': + resolution: {integrity: sha512-AGTD6yJZ+kj2TNah1r7/6vyufs6fZANeSvv9x5eG+WjV4uyUJYkd1qR8C5gFZHdkyr+bhAcsAXvS039VzAxRrQ==} + + '@lexical/selection@0.16.0': + resolution: {integrity: sha512-trT9gQVJ2j6AwAe7tHJ30SRuxCpV6yR9LFtggxphHsXSvJYnoHC0CXh1TF2jHl8Gd5OsdWseexGLBE4Y0V3gwQ==} + + '@lexical/table@0.16.0': + resolution: {integrity: sha512-A66K779kxdr0yH2RwT2itsMnkzyFLFNPXyiWGLobCH8ON4QPuBouZvjbRHBe8Pe64yJ0c1bRDxSbTqUi9Wt3Gg==} + + '@lexical/text@0.16.0': + resolution: {integrity: sha512-9ilaOhuNIIGHKC8g8j3K/mEvJ09af9B6RKbm3GNoRcf/WNHD4dEFWNTEvgo/3zCzAS8EUBI6UINmfQQWlMjdIQ==} + + '@lexical/utils@0.16.0': + resolution: {integrity: sha512-GWmFEmd7o3GHqJBaEwzuZQbfTNI3Gg8ReGuHMHABgrkhZ8j2NggoRBlxsQLG0f7BewfTMVwbye22yBPq78775w==} + + '@lexical/yjs@0.16.0': + resolution: {integrity: sha512-YIJr87DfAXTwoVHDjR7cci//hr4r/a61Nn95eo2JNwbTqQo65Gp8rwJivqVxNfvKZmRdwHTKgvdEDoBmI/tGog==} + peerDependencies: + yjs: '>=13.5.22' + '@mdx-js/react@2.3.0': resolution: {integrity: sha512-zQH//gdOmuu7nt2oJR29vFhDv88oGPmVw6BggmrHeMI+xgEkp1B2dX9/bMBSYtK0dyLX/aOmesKS09g222K1/g==} peerDependencies: @@ -2885,6 +2965,9 @@ packages: '@types/range-parser@1.2.7': resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + '@types/react-dom@18.3.0': + resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} + '@types/react-helmet@6.1.11': resolution: {integrity: sha512-0QcdGLddTERotCXo3VFlUSWO3ztraw8nZ6e3zJSgG7apwV5xt+pJUS8ewPBqT4NYB1optGLprNQzFleIY84u/g==} @@ -5013,6 +5096,9 @@ packages: resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} engines: {node: '>=0.10.0'} + isomorphic.js@0.2.5: + resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==} + istanbul-lib-coverage@3.2.2: resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} engines: {node: '>=8'} @@ -5296,6 +5382,14 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + lexical@0.16.0: + resolution: {integrity: sha512-Skn45Qhriazq4fpAtwnAB11U//GKc4vjzx54xsV3TkDLDvWpbL4Z9TNRwRoN3g7w8AkWnqjeOSODKkrjgfRSrg==} + + lib0@0.2.94: + resolution: {integrity: sha512-hZ3p54jL4Wpu7IOg26uC7dnEWiMyNlUrb9KoG7+xYs45WkQwpVvKFndVq2+pqLYKe1u8Fp3+zAfZHVvTK34PvQ==} + engines: {node: '>=16'} + hasBin: true + lilconfig@2.1.0: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} @@ -6031,6 +6125,10 @@ packages: resolution: {integrity: sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==} engines: {node: '>= 0.8'} + prismjs@1.29.0: + resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==} + engines: {node: '>=6'} + process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -6154,6 +6252,12 @@ packages: react: ^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0 react-dom: ^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0 + react-error-boundary@3.1.4: + resolution: {integrity: sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==} + engines: {node: '>=10', npm: '>=6'} + peerDependencies: + react: '>=16.13.1' + react-hook-form@7.52.0: resolution: {integrity: sha512-mJX506Xc6mirzLsmXUJyqlAI3Kj9Ph2RhplYhUVffeOQSnubK2uVqBFOBJmvKikvbFV91pxVXmDiR+QMF19x6A==} engines: {node: '>=12.22.0'} @@ -7508,6 +7612,10 @@ packages: yeast@0.1.2: resolution: {integrity: sha512-8HFIh676uyGYP6wP13R/j6OJ/1HwJ46snpvzE7aHAN3Ryqh2yX6Xox2B4CUmTwwOIzlG3Bs7ocsP5dZH/R1Qbg==} + yjs@13.6.17: + resolution: {integrity: sha512-ERnKXYZrZqgGO81Yqt3D69detaRUwaqhsQTZRKi9CDMgteDMund+KcLChfwpjQbva9YwfRgh7S914Pa6qPVVCA==} + engines: {node: '>=16.0.0', npm: '>=8.0.0'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -8927,6 +9035,151 @@ snapshots: '@juggle/resize-observer@3.4.0': {} + '@lexical/clipboard@0.16.0': + dependencies: + '@lexical/html': 0.16.0 + '@lexical/list': 0.16.0 + '@lexical/selection': 0.16.0 + '@lexical/utils': 0.16.0 + lexical: 0.16.0 + + '@lexical/code@0.16.0': + dependencies: + '@lexical/utils': 0.16.0 + lexical: 0.16.0 + prismjs: 1.29.0 + + '@lexical/devtools-core@0.16.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@lexical/html': 0.16.0 + '@lexical/link': 0.16.0 + '@lexical/mark': 0.16.0 + '@lexical/table': 0.16.0 + '@lexical/utils': 0.16.0 + lexical: 0.16.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@lexical/dragon@0.16.0': + dependencies: + lexical: 0.16.0 + + '@lexical/hashtag@0.16.0': + dependencies: + '@lexical/utils': 0.16.0 + lexical: 0.16.0 + + '@lexical/history@0.16.0': + dependencies: + '@lexical/utils': 0.16.0 + lexical: 0.16.0 + + '@lexical/html@0.16.0': + dependencies: + '@lexical/selection': 0.16.0 + '@lexical/utils': 0.16.0 + lexical: 0.16.0 + + '@lexical/link@0.16.0': + dependencies: + '@lexical/utils': 0.16.0 + lexical: 0.16.0 + + '@lexical/list@0.16.0': + dependencies: + '@lexical/utils': 0.16.0 + lexical: 0.16.0 + + '@lexical/mark@0.16.0': + dependencies: + '@lexical/utils': 0.16.0 + lexical: 0.16.0 + + '@lexical/markdown@0.16.0': + dependencies: + '@lexical/code': 0.16.0 + '@lexical/link': 0.16.0 + '@lexical/list': 0.16.0 + '@lexical/rich-text': 0.16.0 + '@lexical/text': 0.16.0 + '@lexical/utils': 0.16.0 + lexical: 0.16.0 + + '@lexical/offset@0.16.0': + dependencies: + lexical: 0.16.0 + + '@lexical/overflow@0.16.0': + dependencies: + lexical: 0.16.0 + + '@lexical/plain-text@0.16.0': + dependencies: + '@lexical/clipboard': 0.16.0 + '@lexical/selection': 0.16.0 + '@lexical/utils': 0.16.0 + lexical: 0.16.0 + + '@lexical/react@0.16.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(yjs@13.6.17)': + dependencies: + '@lexical/clipboard': 0.16.0 + '@lexical/code': 0.16.0 + '@lexical/devtools-core': 0.16.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@lexical/dragon': 0.16.0 + '@lexical/hashtag': 0.16.0 + '@lexical/history': 0.16.0 + '@lexical/link': 0.16.0 + '@lexical/list': 0.16.0 + '@lexical/mark': 0.16.0 + '@lexical/markdown': 0.16.0 + '@lexical/overflow': 0.16.0 + '@lexical/plain-text': 0.16.0 + '@lexical/rich-text': 0.16.0 + '@lexical/selection': 0.16.0 + '@lexical/table': 0.16.0 + '@lexical/text': 0.16.0 + '@lexical/utils': 0.16.0 + '@lexical/yjs': 0.16.0(yjs@13.6.17) + lexical: 0.16.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-error-boundary: 3.1.4(react@18.3.1) + transitivePeerDependencies: + - yjs + + '@lexical/rich-text@0.16.0': + dependencies: + '@lexical/clipboard': 0.16.0 + '@lexical/selection': 0.16.0 + '@lexical/utils': 0.16.0 + lexical: 0.16.0 + + '@lexical/selection@0.16.0': + dependencies: + lexical: 0.16.0 + + '@lexical/table@0.16.0': + dependencies: + '@lexical/utils': 0.16.0 + lexical: 0.16.0 + + '@lexical/text@0.16.0': + dependencies: + lexical: 0.16.0 + + '@lexical/utils@0.16.0': + dependencies: + '@lexical/list': 0.16.0 + '@lexical/selection': 0.16.0 + '@lexical/table': 0.16.0 + lexical: 0.16.0 + + '@lexical/yjs@0.16.0(yjs@13.6.17)': + dependencies: + '@lexical/offset': 0.16.0 + lexical: 0.16.0 + yjs: 13.6.17 + '@mdx-js/react@2.3.0(react@18.3.1)': dependencies: '@types/mdx': 2.0.13 @@ -8962,26 +9215,28 @@ snapshots: dependencies: '@babel/runtime': 7.24.7 - '@radix-ui/react-arrow@1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-arrow@1.0.3(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.24.7 - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 17.0.80 + '@types/react-dom': 18.3.0 - '@radix-ui/react-collection@1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-collection@1.0.3(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.24.7 '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) '@radix-ui/react-context': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-slot': 1.0.2(@types/react@17.0.80)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 17.0.80 + '@types/react-dom': 18.3.0 '@radix-ui/react-compose-refs@1.0.1(@types/react@17.0.80)(react@18.3.1)': dependencies: @@ -8997,19 +9252,19 @@ snapshots: optionalDependencies: '@types/react': 17.0.80 - '@radix-ui/react-dialog@1.0.5(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-dialog@1.0.5(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.24.7 '@radix-ui/primitive': 1.0.1 '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) '@radix-ui/react-context': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.0.5(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-focus-guards': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-focus-scope': 1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-scope': 1.0.4(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-id': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-portal': 1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-presence': 1.0.1(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.0.4(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-slot': 1.0.2(@types/react@17.0.80)(react@18.3.1) '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@17.0.80)(react@18.3.1) aria-hidden: 1.2.4 @@ -9018,6 +9273,7 @@ snapshots: react-remove-scroll: 2.5.5(@types/react@17.0.80)(react@18.3.1) optionalDependencies: '@types/react': 17.0.80 + '@types/react-dom': 18.3.0 '@radix-ui/react-direction@1.0.1(@types/react@17.0.80)(react@18.3.1)': dependencies: @@ -9026,31 +9282,33 @@ snapshots: optionalDependencies: '@types/react': 17.0.80 - '@radix-ui/react-dismissable-layer@1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-dismissable-layer@1.0.4(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.24.7 '@radix-ui/primitive': 1.0.1 '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.80)(react@18.3.1) '@radix-ui/react-use-escape-keydown': 1.0.3(@types/react@17.0.80)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 17.0.80 + '@types/react-dom': 18.3.0 - '@radix-ui/react-dismissable-layer@1.0.5(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-dismissable-layer@1.0.5(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.24.7 '@radix-ui/primitive': 1.0.1 '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.80)(react@18.3.1) '@radix-ui/react-use-escape-keydown': 1.0.3(@types/react@17.0.80)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 17.0.80 + '@types/react-dom': 18.3.0 '@radix-ui/react-focus-guards@1.0.1(@types/react@17.0.80)(react@18.3.1)': dependencies: @@ -9059,27 +9317,29 @@ snapshots: optionalDependencies: '@types/react': 17.0.80 - '@radix-ui/react-focus-scope@1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-focus-scope@1.0.3(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.24.7 '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.80)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 17.0.80 + '@types/react-dom': 18.3.0 - '@radix-ui/react-focus-scope@1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-focus-scope@1.0.4(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.24.7 '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.80)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 17.0.80 + '@types/react-dom': 18.3.0 '@radix-ui/react-id@1.0.1(@types/react@17.0.80)(react@18.3.1)': dependencies: @@ -9089,14 +9349,14 @@ snapshots: optionalDependencies: '@types/react': 17.0.80 - '@radix-ui/react-popper@1.1.2(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-popper@1.1.2(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.24.7 '@floating-ui/react-dom': 2.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-arrow': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-arrow': 1.0.3(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) '@radix-ui/react-context': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.80)(react@18.3.1) '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@17.0.80)(react@18.3.1) '@radix-ui/react-use-rect': 1.0.1(@types/react@17.0.80)(react@18.3.1) @@ -9106,26 +9366,29 @@ snapshots: react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 17.0.80 + '@types/react-dom': 18.3.0 - '@radix-ui/react-portal@1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-portal@1.0.3(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.24.7 - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 17.0.80 + '@types/react-dom': 18.3.0 - '@radix-ui/react-portal@1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-portal@1.0.4(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.24.7 - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 17.0.80 + '@types/react-dom': 18.3.0 - '@radix-ui/react-presence@1.0.1(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-presence@1.0.1(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.24.7 '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) @@ -9134,8 +9397,9 @@ snapshots: react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 17.0.80 + '@types/react-dom': 18.3.0 - '@radix-ui/react-primitive@1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-primitive@1.0.3(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.24.7 '@radix-ui/react-slot': 1.0.2(@types/react@17.0.80)(react@18.3.1) @@ -9143,61 +9407,65 @@ snapshots: react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 17.0.80 + '@types/react-dom': 18.3.0 - '@radix-ui/react-roving-focus@1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-roving-focus@1.0.4(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.24.7 '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-collection': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-collection': 1.0.3(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) '@radix-ui/react-context': 1.0.1(@types/react@17.0.80)(react@18.3.1) '@radix-ui/react-direction': 1.0.1(@types/react@17.0.80)(react@18.3.1) '@radix-ui/react-id': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.80)(react@18.3.1) '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@17.0.80)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 17.0.80 + '@types/react-dom': 18.3.0 - '@radix-ui/react-select@1.2.2(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-select@1.2.2(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.24.7 '@radix-ui/number': 1.0.1 '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-collection': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-collection': 1.0.3(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.80)(react@18.3.1) '@radix-ui/react-context': 1.0.1(@types/react@17.0.80)(react@18.3.1) '@radix-ui/react-direction': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.0.4(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-focus-guards': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-focus-scope': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-scope': 1.0.3(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-id': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-popper': 1.1.2(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-portal': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-popper': 1.1.2(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.0.3(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-slot': 1.0.2(@types/react@17.0.80)(react@18.3.1) '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.80)(react@18.3.1) '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@17.0.80)(react@18.3.1) '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@17.0.80)(react@18.3.1) '@radix-ui/react-use-previous': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-visually-hidden': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.0.3(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) aria-hidden: 1.2.4 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) react-remove-scroll: 2.5.5(@types/react@17.0.80)(react@18.3.1) optionalDependencies: '@types/react': 17.0.80 + '@types/react-dom': 18.3.0 - '@radix-ui/react-separator@1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-separator@1.0.3(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.24.7 - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 17.0.80 + '@types/react-dom': 18.3.0 '@radix-ui/react-slot@1.0.2(@types/react@17.0.80)(react@18.3.1)': dependencies: @@ -9207,62 +9475,66 @@ snapshots: optionalDependencies: '@types/react': 17.0.80 - '@radix-ui/react-tabs@1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-tabs@1.0.4(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.24.7 '@radix-ui/primitive': 1.0.1 '@radix-ui/react-context': 1.0.1(@types/react@17.0.80)(react@18.3.1) '@radix-ui/react-direction': 1.0.1(@types/react@17.0.80)(react@18.3.1) '@radix-ui/react-id': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-presence': 1.0.1(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-roving-focus': 1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.0.4(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@17.0.80)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 17.0.80 + '@types/react-dom': 18.3.0 - '@radix-ui/react-toggle-group@1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-toggle-group@1.0.4(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.24.7 '@radix-ui/primitive': 1.0.1 '@radix-ui/react-context': 1.0.1(@types/react@17.0.80)(react@18.3.1) '@radix-ui/react-direction': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-roving-focus': 1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-toggle': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.0.4(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-toggle': 1.0.3(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@17.0.80)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 17.0.80 + '@types/react-dom': 18.3.0 - '@radix-ui/react-toggle@1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-toggle@1.0.3(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.24.7 '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@17.0.80)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 17.0.80 + '@types/react-dom': 18.3.0 - '@radix-ui/react-toolbar@1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-toolbar@1.0.4(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.24.7 '@radix-ui/primitive': 1.0.1 '@radix-ui/react-context': 1.0.1(@types/react@17.0.80)(react@18.3.1) '@radix-ui/react-direction': 1.0.1(@types/react@17.0.80)(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-roving-focus': 1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-separator': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-toggle-group': 1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.0.4(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-separator': 1.0.3(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-toggle-group': 1.0.4(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 17.0.80 + '@types/react-dom': 18.3.0 '@radix-ui/react-use-callback-ref@1.0.1(@types/react@17.0.80)(react@18.3.1)': dependencies: @@ -9317,14 +9589,15 @@ snapshots: optionalDependencies: '@types/react': 17.0.80 - '@radix-ui/react-visually-hidden@1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-visually-hidden@1.0.3(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.24.7 - '@radix-ui/react-primitive': 1.0.3(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 17.0.80 + '@types/react-dom': 18.3.0 '@radix-ui/rect@1.0.1': dependencies: @@ -10335,9 +10608,9 @@ snapshots: memoizerific: 1.11.3 ts-dedent: 2.2.0 - '@storybook/addon-controls@7.6.19(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@storybook/addon-controls@7.6.19(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@storybook/blocks': 7.6.19(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@storybook/blocks': 7.6.19(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) lodash: 4.17.21 ts-dedent: 2.2.0 transitivePeerDependencies: @@ -10348,13 +10621,13 @@ snapshots: - react-dom - supports-color - '@storybook/addon-docs@7.6.19(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@storybook/addon-docs@7.6.19(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@jest/transform': 29.7.0 '@mdx-js/react': 2.3.0(react@18.3.1) - '@storybook/blocks': 7.6.19(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@storybook/blocks': 7.6.19(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@storybook/client-logger': 7.6.19 - '@storybook/components': 7.6.19(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@storybook/components': 7.6.19(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@storybook/csf-plugin': 7.6.19 '@storybook/csf-tools': 7.6.19 '@storybook/global': 5.0.0 @@ -10377,12 +10650,12 @@ snapshots: - encoding - supports-color - '@storybook/addon-essentials@7.6.19(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@storybook/addon-essentials@7.6.19(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@storybook/addon-actions': 7.6.19 '@storybook/addon-backgrounds': 7.6.19 - '@storybook/addon-controls': 7.6.19(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@storybook/addon-docs': 7.6.19(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@storybook/addon-controls': 7.6.19(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@storybook/addon-docs': 7.6.19(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@storybook/addon-highlight': 7.6.19 '@storybook/addon-measure': 7.6.19 '@storybook/addon-outline': 7.6.19 @@ -10421,11 +10694,11 @@ snapshots: dependencies: memoizerific: 1.11.3 - '@storybook/blocks@7.6.19(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@storybook/blocks@7.6.19(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@storybook/channels': 7.6.19 '@storybook/client-logger': 7.6.19 - '@storybook/components': 7.6.19(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@storybook/components': 7.6.19(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@storybook/core-events': 7.6.19 '@storybook/csf': 0.1.8 '@storybook/docs-tools': 7.6.19 @@ -10599,10 +10872,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@storybook/components@7.6.19(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@storybook/components@7.6.19(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@radix-ui/react-select': 1.2.2(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-toolbar': 1.0.4(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-select': 1.2.2(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-toolbar': 1.0.4(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@storybook/client-logger': 7.6.19 '@storybook/csf': 0.1.8 '@storybook/global': 5.0.0 @@ -11091,6 +11364,10 @@ snapshots: '@types/range-parser@1.2.7': {} + '@types/react-dom@18.3.0': + dependencies: + '@types/react': 17.0.80 + '@types/react-helmet@6.1.11': dependencies: '@types/react': 17.0.80 @@ -13531,6 +13808,8 @@ snapshots: isobject@3.0.1: {} + isomorphic.js@0.2.5: {} + istanbul-lib-coverage@3.2.2: {} istanbul-lib-instrument@5.2.1: @@ -14021,6 +14300,12 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + lexical@0.16.0: {} + + lib0@0.2.94: + dependencies: + isomorphic.js: 0.2.5 + lilconfig@2.1.0: {} lilconfig@3.1.2: {} @@ -14725,6 +15010,8 @@ snapshots: pretty-hrtime@1.0.3: {} + prismjs@1.29.0: {} + process-nextick-args@2.0.1: {} process@0.11.10: {} @@ -14926,6 +15213,11 @@ snapshots: react-dom: 18.3.1(react@18.3.1) react-is: 18.1.0 + react-error-boundary@3.1.4(react@18.3.1): + dependencies: + '@babel/runtime': 7.24.7 + react: 18.3.1 + react-hook-form@7.52.0(react@18.3.1): dependencies: react: 18.3.1 @@ -16237,9 +16529,9 @@ snapshots: vary@1.1.2: {} - vaul@0.9.1(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + vaul@0.9.1(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@radix-ui/react-dialog': 1.0.5(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-dialog': 1.0.5(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) transitivePeerDependencies: @@ -16450,6 +16742,10 @@ snapshots: yeast@0.1.2: {} + yjs@13.6.17: + dependencies: + lib0: 0.2.94 + yocto-queue@0.1.0: {} zod@3.23.8: {} diff --git a/src/v4/core/components/Notification/index.tsx b/src/v4/core/components/Notification/index.tsx index b6913e9a9..4a23570c2 100644 --- a/src/v4/core/components/Notification/index.tsx +++ b/src/v4/core/components/Notification/index.tsx @@ -9,7 +9,7 @@ interface NotificationProps { icon?: ReactNode; } -const Notification = ({ className, content, icon }: NotificationProps) => ( +export const Notification = ({ className, content, icon }: NotificationProps) => ( <div className={clsx(styles.notificationContainer, className)}> <div className={clsx(styles.icon__container)}>{icon}</div> {content} </div> diff --git a/src/v4/core/providers/AmityUIKitProvider.tsx b/src/v4/core/providers/AmityUIKitProvider.tsx index 4dc85f11a..1d6f7f9c7 100644 --- a/src/v4/core/providers/AmityUIKitProvider.tsx +++ b/src/v4/core/providers/AmityUIKitProvider.tsx @@ -140,47 +140,45 @@ const AmityUIKitProvider: React.FC<AmityUIKitProviderProps> = ({ <CustomizationProvider initialConfig={configs || defaultConfig}> <StyledThemeProvider theme={buildGlobalTheme(theme)}> <ThemeProvider> - <UIStyles> - <CustomReactionProvider> - <SDKContextV3.Provider value={sdkContextValue}> - <SDKContext.Provider value={sdkContextValue}> - <SDKConnectorProviderV3> - <SDKConnectorProvider> - <NotificationProvider> - <DrawerProvider> - <LegacyNotificationProvider> - <ConfirmProvider> - <LegacyConfirmProvider> - <ConfigProvider - config={{ - socialCommunityCreationButtonVisible: - socialCommunityCreationButtonVisible || true, - }} - > - <PostRendererProvider config={postRendererConfig}> - <NavigationProvider> - <PageBehaviorProvider pageBehavior={pageBehavior}> - {children} - </PageBehaviorProvider> - </NavigationProvider> - </PostRendererProvider> - </ConfigProvider> - <NotificationsContainer /> - <LegacyNotificationsContainer /> - <ConfirmComponent /> - <DrawerContainer /> - <LegacyConfirmComponent /> - </LegacyConfirmProvider> - </ConfirmProvider> - </LegacyNotificationProvider> - </DrawerProvider> - </NotificationProvider> - </SDKConnectorProvider> - </SDKConnectorProviderV3> - </SDKContext.Provider> - </SDKContextV3.Provider> - </CustomReactionProvider> - </UIStyles> + <CustomReactionProvider> + <SDKContextV3.Provider value={sdkContextValue}> + <SDKContext.Provider value={sdkContextValue}> + <SDKConnectorProviderV3> + <SDKConnectorProvider> + <NotificationProvider> + <DrawerProvider> + <LegacyNotificationProvider> + <ConfirmProvider> + <LegacyConfirmProvider> + <ConfigProvider + config={{ + socialCommunityCreationButtonVisible: + socialCommunityCreationButtonVisible || true, + }} + > + <PostRendererProvider config={postRendererConfig}> + <NavigationProvider> + <PageBehaviorProvider pageBehavior={pageBehavior}> + {children} + </PageBehaviorProvider> + </NavigationProvider> + </PostRendererProvider> + </ConfigProvider> + <NotificationsContainer /> + <LegacyNotificationsContainer /> + <ConfirmComponent /> + <DrawerContainer /> + <LegacyConfirmComponent /> + </LegacyConfirmProvider> + </ConfirmProvider> + </LegacyNotificationProvider> + </DrawerProvider> + </NotificationProvider> + </SDKConnectorProvider> + </SDKConnectorProviderV3> + </SDKContext.Provider> + </SDKContextV3.Provider> + </CustomReactionProvider> </ThemeProvider> </StyledThemeProvider> </CustomizationProvider> diff --git a/src/v4/core/providers/CustomizationProvider.tsx b/src/v4/core/providers/CustomizationProvider.tsx index 86a031777..9ddd2bcbe 100644 --- a/src/v4/core/providers/CustomizationProvider.tsx +++ b/src/v4/core/providers/CustomizationProvider.tsx @@ -334,7 +334,7 @@ export const defaultConfig: DefaultConfig = { image: 'platformValue', }, 'post_composer_page/*/community_display_name': {}, - 'post_composer_page/*/create_button': { + 'post_composer_page/*/create_new_post_button': { text: 'Post', }, 'post_composer_page/*/edit_post_button': { @@ -372,8 +372,8 @@ export const defaultConfig: DefaultConfig = { text: 'Video', image: 'platformValue', }, - 'create_post_page/detailed_media_attachment/file_button': { - textpost_composer_page: 'Attachment', + 'post_composer_page/detailed_media_attachment/file_button': { + text: 'Attachment', image: 'platformValue', }, 'social_home_page/*/*': {}, diff --git a/src/v4/core/providers/NavigationProvider.tsx b/src/v4/core/providers/NavigationProvider.tsx index 883326abe..2c6b9ad99 100644 --- a/src/v4/core/providers/NavigationProvider.tsx +++ b/src/v4/core/providers/NavigationProvider.tsx @@ -1,5 +1,6 @@ import React, { createContext, useCallback, useContext, useState, useMemo, ReactNode } from 'react'; import { AmityStoryMediaType } from '~/v4/social/pages/DraftsPage/DraftsPage'; +import { Mode } from '~/v4/social/pages/PostComposerPage/PostComposerPage'; export enum PageTypes { Explore = 'explore', @@ -17,6 +18,7 @@ export enum PageTypes { SocialGlobalSearchPage = 'SocialGlobalSearchPage', SelectPostTargetPage = 'SelectPostTargetPage', DraftPage = 'DraftPage', + PostComposerPage = 'PostComposerPage', } type Page = @@ -78,6 +80,17 @@ type Page = mediaType: AmityStoryMediaType; targetId: string; targetType: Amity.StoryTargetType; + } + | { + type: PageTypes.PostComposerPage; + + context: { + targetId: string | null; + targetType: 'community' | 'user'; + mode: Mode; + community?: Amity.Community; + post?: Amity.Post; + }; }; type ContextValue = { @@ -117,6 +130,14 @@ type ContextValue = { | null | undefined, ) => void; + goToPostComposerPage: ( + mode: Mode, + targetId: string | null, + targetType: 'community' | 'user', + community?: Amity.Community, + post?: Amity.Post, + ) => void; + goToSocialHomePage: () => void; }; let defaultValue: ContextValue = { @@ -144,6 +165,14 @@ let defaultValue: ContextValue = { goToCommunityProfilePage: (communityId: string) => {}, goToSocialGlobalSearchPage: (tab?: string) => {}, goToSelectPostTargetPage: () => {}, + goToPostComposerPage: ( + mode: Mode, + targetId: string | null, + targetType: 'community' | 'user', + community?: Amity.Community, + post?: Amity.Post, + ) => {}, + goToSocialHomePage: () => {}, setNavigationBlocker: () => {}, onBack: () => {}, }; @@ -176,6 +205,11 @@ if (process.env.NODE_ENV !== 'production') { goToSelectPostTargetPage: () => console.log('NavigationContext goToTargetPage()'), goToDraftStoryPage: ({ targetId, targetType, mediaType }) => console.log(`NavigationContext goToDraftStoryPage(${targetId}, ${targetType}, ${mediaType})`), + goToPostComposerPage: (mode, targetId, targetType, community, post) => + console.log( + `NavigationContext goToPostComposerPage(${mode} ${targetId}) ${targetType} ${community} ${post}`, + ), + goToSocialHomePage: () => console.log('NavigationContext goToSocialHomePage()'), }; } @@ -468,6 +502,32 @@ export default function NavigationProvider({ [onChangePage, pushPage], ); + const goToSocialHomePage = useCallback(() => { + const next = { + type: PageTypes.SocialHomePage, + }; + + pushPage(next); + }, [onChangePage, pushPage]); + + const goToPostComposerPage = useCallback( + (mode, targetId, targetType, community, post) => { + const next = { + type: PageTypes.PostComposerPage, + context: { + mode, + targetId, + targetType, + community, + post, + }, + }; + + pushPage(next); + }, + [onChangePage, pushPage], + ); + return ( <NavigationContext.Provider value={{ @@ -488,6 +548,8 @@ export default function NavigationProvider({ goToViewStoryPage, goToSelectPostTargetPage, goToDraftStoryPage, + goToPostComposerPage, + goToSocialHomePage, setNavigationBlocker, }} > diff --git a/src/v4/core/providers/PageBehaviorProvider.tsx b/src/v4/core/providers/PageBehaviorProvider.tsx index 68e4e96bd..c1728b401 100644 --- a/src/v4/core/providers/PageBehaviorProvider.tsx +++ b/src/v4/core/providers/PageBehaviorProvider.tsx @@ -1,5 +1,6 @@ import React, { useContext } from 'react'; import { PageTypes, useNavigation } from '~/v4/core/providers/NavigationProvider'; +import { Mode } from '~/v4/social/pages/PostComposerPage/PostComposerPage'; export interface PageBehavior { AmityStoryViewPageBehavior: { @@ -32,6 +33,18 @@ export interface PageBehavior { AmityCreatePostMenuComponentBehavior: { goToSelectPostTargetPage(): void; }; + AmityPostTargetSelectionPage: { + goToPostComposerPage: (context: { + mode: Mode.CREATE | Mode.EDIT; + targetId: string | null; + targetType: 'community' | 'user'; + community?: Amity.Community; + post?: Amity.Post; + }) => void; + }; + AmityPostComposerPageBehavior: { + goToSocialHomePage(): void; + }; } const PageBehaviorContext = React.createContext<PageBehavior | undefined>(undefined); @@ -53,6 +66,8 @@ export const PageBehaviorProvider: React.FC<PageBehaviorProviderProps> = ({ goToViewStoryPage, onChangePage, goToSelectPostTargetPage, + goToPostComposerPage, + goToSocialHomePage, } = useNavigation(); const navigationBehavior: PageBehavior = { AmityStoryViewPageBehavior: { @@ -115,6 +130,7 @@ export const PageBehaviorProvider: React.FC<PageBehaviorProviderProps> = ({ goToUserProfilePage(context.userId); }, }, + AmitySocialGlobalSearchPageBehavior: {}, AmityCommunitySearchResultComponentBehavior: { goToCommunityProfilePage: (context: { communityId: string }) => { @@ -134,6 +150,34 @@ export const PageBehaviorProvider: React.FC<PageBehaviorProviderProps> = ({ goToSelectPostTargetPage(); }, }, + AmityPostTargetSelectionPage: { + goToPostComposerPage: (context: { + mode: Mode.CREATE | Mode.EDIT; + targetId: string | null; + targetType: 'community' | 'user'; + community?: Amity.Community; + post?: Amity.Post; + }) => { + if (pageBehavior?.AmityPostTargetSelectionPage?.goToPostComposerPage) { + return pageBehavior.AmityPostTargetSelectionPage.goToPostComposerPage(context); + } + goToPostComposerPage( + context.mode, + context.targetId, + context.targetType, + context.community, + context.post, + ); + }, + }, + AmityPostComposerPageBehavior: { + goToSocialHomePage() { + if (pageBehavior?.AmityPostComposerPageBehavior?.goToSocialHomePage) { + return pageBehavior.AmityPostComposerPageBehavior.goToSocialHomePage(); + } + goToSocialHomePage(); + }, + }, }; return ( diff --git a/src/v4/social/components/CreatePostMenu/CreatePostMenu.tsx b/src/v4/social/components/CreatePostMenu/CreatePostMenu.tsx index 19438a786..86801fa8b 100644 --- a/src/v4/social/components/CreatePostMenu/CreatePostMenu.tsx +++ b/src/v4/social/components/CreatePostMenu/CreatePostMenu.tsx @@ -2,10 +2,6 @@ import React from 'react'; import styles from './CreatePostMenu.module.css'; import { CreatePostButton } from '~/v4/social/elements/CreatePostButton'; -import { CreatePollButton } from '~/v4/social/elements/CreatePollButton/CreatePollButton'; -import { CreateLivestreamButton } from '~/v4/social/elements/CreateLivestreamButton'; -import { CreateStoryButton } from '~/v4/social/elements/CreateStoryButton'; - interface CreatePostMenuProps { pageId: string; } @@ -15,9 +11,6 @@ export function CreatePostMenu({ pageId }: CreatePostMenuProps) { return ( <div className={styles.createPostMenu}> <CreatePostButton pageId={pageId} componentId={componentId} /> - <CreateStoryButton pageId={pageId} componentId={componentId} /> - <CreatePollButton pageId={pageId} componentId={componentId} /> - <CreateLivestreamButton pageId={pageId} componentId={componentId} /> </div> ); } diff --git a/src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.module.css b/src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.module.css new file mode 100644 index 000000000..f89a03314 --- /dev/null +++ b/src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.module.css @@ -0,0 +1,21 @@ +.detailedMediaAttachment { + display: block; + width: 100%; + background-color: var(--asc-color-white); + border-top-left-radius: 1.25rem; + border-top-right-radius: 1.25rem; + box-shadow: var(--asc-box-shadow-04); + padding-bottom: 2rem; +} + +.detailedMediaAttachment__swipeDown { + display: flex; + width: 2.25rem; + height: 0.25rem; + margin: 0 auto; + border-radius: 0.75rem; + background-color: var(--asc-color-base-shade3); + margin-top: 0.75rem; + margin-bottom: 1.25rem; + bottom: 0; +} diff --git a/src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.tsx b/src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.tsx new file mode 100644 index 000000000..1ec0da750 --- /dev/null +++ b/src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { useAmityComponent } from '~/v4/core/hooks/uikit'; +import styles from './DetailedMediaAttachment.module.css'; +import { CameraButton } from '~/v4/social/elements/CameraButton'; +import { ImageButton } from '~/v4/social/elements/ImageButton/ImageButton'; +import { VideoButton } from '~/v4/social/elements/VideoButton/VideoButton'; +import { FileButton } from '~/v4/social/elements/FileButton'; + +interface DetailedMediaAttachmentProps { + pageId: string; +} + +export function DetailedMediaAttachment({ pageId }: DetailedMediaAttachmentProps) { + const componentId = 'detailed_media_attachment'; + const { themeStyles, accessibilityId, isExcluded } = useAmityComponent({ pageId, componentId }); + + if (isExcluded) return null; + + return ( + <div + style={themeStyles} + data-qa-anchor={accessibilityId} + className={styles.detailedMediaAttachment} + > + <div className={styles.detailedMediaAttachment__swipeDown} /> + <CameraButton pageId={pageId} componentId={componentId} /> + <ImageButton pageId={pageId} componentId={componentId} /> + <VideoButton pageId={pageId} componentId={componentId} /> + <FileButton pageId={pageId} componentId={componentId} /> + </div> + ); +} diff --git a/src/v4/social/components/DetailedMediaAttachment/index.tsx b/src/v4/social/components/DetailedMediaAttachment/index.tsx new file mode 100644 index 000000000..b48a316a2 --- /dev/null +++ b/src/v4/social/components/DetailedMediaAttachment/index.tsx @@ -0,0 +1 @@ +export { DetailedMediaAttachment } from './DetailedMediaAttachment'; \ No newline at end of file diff --git a/src/v4/social/components/MediaAttachment/MediaAttachment.module.css b/src/v4/social/components/MediaAttachment/MediaAttachment.module.css new file mode 100644 index 000000000..eb505cea7 --- /dev/null +++ b/src/v4/social/components/MediaAttachment/MediaAttachment.module.css @@ -0,0 +1,27 @@ +.mediaAttachment { + display: block; + align-items: center; + justify-content: space-between; + width: 100%; + padding: 0.5rem 1rem; + background-color: var(--asc-color-white); + border-top-left-radius: 1.25rem; + border-top-right-radius: 1.25rem; + box-shadow: var(--asc-box-shadow-04); +} + +.mediaAttachment__swipeDown { + display: block; + width: 2.25rem; + height: 0.25rem; + margin: 0 auto; + border-radius: 0.75rem; + background-color: var(--asc-color-base-shade3); + margin-top: 0.75rem; + margin-bottom: 0.5rem; +} + +.mediaAttachment__wrapMedia { + display: flex; + justify-content: space-between; +} diff --git a/src/v4/social/components/MediaAttachment/MediaAttachment.tsx b/src/v4/social/components/MediaAttachment/MediaAttachment.tsx new file mode 100644 index 000000000..1187ab845 --- /dev/null +++ b/src/v4/social/components/MediaAttachment/MediaAttachment.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import styles from './MediaAttachment.module.css'; +import { useAmityComponent } from '~/v4/core/hooks/uikit'; +import { CameraButton } from '~/v4/social/elements/CameraButton'; +import { ImageButton } from '~/v4/social/elements/ImageButton'; +import { VideoButton } from '~/v4/social/elements/VideoButton'; +import { FileButton } from '~/v4/social/elements/FileButton'; + +interface MediaAttachmentProps { + pageId: string; +} + +export function MediaAttachment({ pageId }: MediaAttachmentProps) { + const componentId = 'media_attachment'; + const { themeStyles, accessibilityId, isExcluded } = useAmityComponent({ pageId, componentId }); + + if (isExcluded) return null; + + return ( + <div style={themeStyles} data-qa-anchor={accessibilityId} className={styles.mediaAttachment}> + <div className={styles.mediaAttachment__swipeDown} /> + <div className={styles.mediaAttachment__wrapMedia}> + <CameraButton pageId={pageId} componentId={componentId} /> + <ImageButton pageId={pageId} componentId={componentId} /> + <VideoButton pageId={pageId} componentId={componentId} /> + <FileButton pageId={pageId} componentId={componentId} /> + </div> + </div> + ); +} diff --git a/src/v4/social/components/MediaAttachment/index.tsx b/src/v4/social/components/MediaAttachment/index.tsx new file mode 100644 index 000000000..daae0afa8 --- /dev/null +++ b/src/v4/social/components/MediaAttachment/index.tsx @@ -0,0 +1 @@ +export { MediaAttachment } from './MediaAttachment'; diff --git a/src/v4/social/components/TopNavigation/TopNavigation.tsx b/src/v4/social/components/TopNavigation/TopNavigation.tsx index cf3c830af..12f92b7c5 100644 --- a/src/v4/social/components/TopNavigation/TopNavigation.tsx +++ b/src/v4/social/components/TopNavigation/TopNavigation.tsx @@ -8,14 +8,14 @@ import { useNavigation } from '~/v4/core/providers/NavigationProvider'; export interface TopNavigationProps { pageId?: string; - // onClickPostCreationButton: (event: React.MouseEvent) => void; - // createPostButtonRef: React.RefObject<HTMLDivElement>; + onClickPostCreationButton: (event: React.MouseEvent) => void; + createPostButtonRef: React.RefObject<HTMLDivElement>; } export function TopNavigation({ pageId = '*', - // onClickPostCreationButton, - // createPostButtonRef, + onClickPostCreationButton, + createPostButtonRef, }: TopNavigationProps) { const componentId = 'top_navigation'; const { isExcluded, themeStyles } = useAmityComponent({ @@ -41,9 +41,8 @@ export function TopNavigation({ <PostCreationButton pageId={pageId} componentId={componentId} - onClick={() => {}} - // onClick={onClickPostCreationButton} - // createPostButtonRef={createPostButtonRef} + onClick={onClickPostCreationButton} + createPostButtonRef={createPostButtonRef} /> </div> </div> diff --git a/src/v4/social/elements/CameraButton/CameraButton.module.css b/src/v4/social/elements/CameraButton/CameraButton.module.css new file mode 100644 index 000000000..6ab4a859b --- /dev/null +++ b/src/v4/social/elements/CameraButton/CameraButton.module.css @@ -0,0 +1,18 @@ +.cameraButton { + display: flex; + align-items: center; + padding: 0.75rem 1rem; +} + +.cameraButton__icon { + width: 1.5rem; + height: 1.5rem; + stroke: var(--asc-color-base-default); + background: var(--asc-color-base-shade4); + border-radius: 50%; + padding: 0.25rem; +} + +.cameraButton__text { + margin-left: 0.75rem; +} diff --git a/src/v4/social/elements/CameraButton/CameraButton.tsx b/src/v4/social/elements/CameraButton/CameraButton.tsx new file mode 100644 index 000000000..c42e6a2e8 --- /dev/null +++ b/src/v4/social/elements/CameraButton/CameraButton.tsx @@ -0,0 +1,75 @@ +import React from 'react'; +import { IconComponent } from '~/v4/core/IconComponent'; +import { Typography } from '~/v4/core/components'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import styles from './CameraButton.module.css'; +import clsx from 'clsx'; + +interface CameraButtonProps { + pageId: string; + componentId?: string; + imgIconClassName?: string; + defaultIconClassName?: string; + onClick?: (e: React.MouseEvent) => void; +} + +const CameraSvg = (props: React.SVGProps<SVGSVGElement>) => { + return ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + {...props} + > + <path + d="M19.5 19.5H4.5C4.10218 19.5 3.72064 19.342 3.43934 19.0607C3.15804 18.7794 3 18.3978 3 18V7.5C3 7.10218 3.15804 6.72064 3.43934 6.43934C3.72064 6.15804 4.10218 6 4.5 6H7.49945L8.99945 3.75H14.9995L16.4995 6H19.5C19.8978 6 20.2794 6.15804 20.5607 6.43934C20.842 6.72064 21 7.10218 21 7.5V18C21 18.3978 20.842 18.7794 20.5607 19.0607C20.2794 19.342 19.8978 19.5 19.5 19.5Z" + stroke={props.stroke} + stroke-width="1.3" + stroke-linecap="round" + stroke-linejoin="round" + /> + <path + d="M12 15.75C13.864 15.75 15.375 14.239 15.375 12.375C15.375 10.511 13.864 9 12 9C10.136 9 8.625 10.511 8.625 12.375C8.625 14.239 10.136 15.75 12 15.75Z" + stroke={props.stroke} + stroke-width="1.3" + stroke-linecap="round" + stroke-linejoin="round" + /> + </svg> + ); +}; + +export function CameraButton({ + pageId = '*', + componentId = '*', + imgIconClassName, + defaultIconClassName, + onClick, +}: CameraButtonProps) { + const elementId = 'camera_button'; + const { themeStyles, isExcluded, config, accessibilityId, uiReference, defaultConfig } = + useAmityElement({ pageId, componentId, elementId }); + + if (isExcluded) return null; + + return ( + <div + style={themeStyles} + data-qa-anchor={accessibilityId} + className={styles.cameraButton} + onClick={() => {}} + > + <IconComponent + defaultIcon={() => ( + <CameraSvg className={clsx(styles.cameraButton__icon, defaultIconClassName)} /> + )} + imgIcon={() => <img src={config.icon} alt={uiReference} className={imgIconClassName} />} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} + /> + <Typography.BodyBold className={styles.cameraButton__text}>{config.text}</Typography.BodyBold> + </div> + ); +} diff --git a/src/v4/social/elements/CameraButton/index.tsx b/src/v4/social/elements/CameraButton/index.tsx new file mode 100644 index 000000000..764fe6ac9 --- /dev/null +++ b/src/v4/social/elements/CameraButton/index.tsx @@ -0,0 +1 @@ +export { CameraButton } from './CameraButton'; diff --git a/src/v4/social/elements/CommunityDisplayName/CommunityDisplayName.module.css b/src/v4/social/elements/CommunityDisplayName/CommunityDisplayName.module.css index abb86cb37..966a1c3a3 100644 --- a/src/v4/social/elements/CommunityDisplayName/CommunityDisplayName.module.css +++ b/src/v4/social/elements/CommunityDisplayName/CommunityDisplayName.module.css @@ -1,7 +1,9 @@ .communityDisplayName { color: var(--asc-color-base-default); max-width: 100%; + display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + font-size: var(--asc-text-font-size-md); } diff --git a/src/v4/social/elements/CommunityDisplayName/CommunityDisplayName.tsx b/src/v4/social/elements/CommunityDisplayName/CommunityDisplayName.tsx index 6fbbe19a6..615492cb2 100644 --- a/src/v4/social/elements/CommunityDisplayName/CommunityDisplayName.tsx +++ b/src/v4/social/elements/CommunityDisplayName/CommunityDisplayName.tsx @@ -6,7 +6,7 @@ import styles from './CommunityDisplayName.module.css'; export interface CommunityDisplayNameProps { pageId?: string; componentId?: string; - community: Amity.Community; + community?: Amity.Community; } export function CommunityDisplayName({ @@ -15,12 +15,11 @@ export function CommunityDisplayName({ community, }: CommunityDisplayNameProps) { const elementId = 'community_display_name'; - const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = - useAmityElement({ - pageId, - componentId, - elementId, - }); + const { accessibilityId, isExcluded, themeStyles } = useAmityElement({ + pageId, + componentId, + elementId, + }); if (isExcluded) return null; @@ -30,7 +29,7 @@ export function CommunityDisplayName({ style={themeStyles} data-qa-anchor={accessibilityId} > - {community.displayName} + {community?.displayName ?? 'My Timeline'} </Typography.BodyBold> ); } diff --git a/src/v4/social/elements/CreateNewPostButton/CreateNewPostButton.module.css b/src/v4/social/elements/CreateNewPostButton/CreateNewPostButton.module.css new file mode 100644 index 000000000..b466d7dab --- /dev/null +++ b/src/v4/social/elements/CreateNewPostButton/CreateNewPostButton.module.css @@ -0,0 +1,12 @@ +.createNewPostButton { + display: flex; + font-weight: var(--asc-text-font-weight-normal); + color: var(--asc-color-primary-default); +} + +.createNewPostButton:disabled { + display: flex; + font-weight: var(--asc-text-font-weight-normal); + color: var(--asc-color-primary-shade2); + cursor: not-allowed; +} diff --git a/src/v4/social/elements/CreateNewPostButton/CreateNewPostButton.tsx b/src/v4/social/elements/CreateNewPostButton/CreateNewPostButton.tsx new file mode 100644 index 000000000..6c31f0227 --- /dev/null +++ b/src/v4/social/elements/CreateNewPostButton/CreateNewPostButton.tsx @@ -0,0 +1,37 @@ +import React, { FormEventHandler } from 'react'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import styles from './CreateNewPostButton.module.css'; + +interface CreateNewPostButtonProps { + pageId: string; + componentId?: string; + isValid: boolean; + onSubmit: (e: React.FormEvent) => void; +} + +export function CreateNewPostButton({ + pageId = '*', + componentId = '*', + isValid, + onSubmit, +}: CreateNewPostButtonProps) { + const elementId = 'create_new_post_button'; + const { config, isExcluded, themeStyles } = useAmityElement({ + pageId, + componentId, + elementId, + }); + if (isExcluded) return null; + + return ( + <button + onSubmit={onSubmit} + style={themeStyles} + disabled={!isValid} + className={styles.createNewPostButton} + type="submit" + > + {config.text} + </button> + ); +} diff --git a/src/v4/social/elements/CreateNewPostButton/index.tsx b/src/v4/social/elements/CreateNewPostButton/index.tsx new file mode 100644 index 000000000..de3f3d05f --- /dev/null +++ b/src/v4/social/elements/CreateNewPostButton/index.tsx @@ -0,0 +1 @@ +export { CreateNewPostButton } from './CreateNewPostButton'; diff --git a/src/v4/social/elements/CreateStoryButtons/CreateStoryButton.module.css b/src/v4/social/elements/CreateStoryButtons/CreateStoryButton.module.css new file mode 100644 index 000000000..8a15884ee --- /dev/null +++ b/src/v4/social/elements/CreateStoryButtons/CreateStoryButton.module.css @@ -0,0 +1,15 @@ +.createStoryButton { + display: flex; + padding: 0.75rem 0; +} + +.createStoryButton__text { + margin-left: 0.5rem; + color: var(--asc-color-base-default); +} + +.createStoryButton__icon { + fill: var(--asc-color-base-default); + width: 1.25rem; + height: 1.25rem; +} diff --git a/src/v4/social/elements/CreateStoryButtons/CreateStoryButton.tsx b/src/v4/social/elements/CreateStoryButtons/CreateStoryButton.tsx new file mode 100644 index 000000000..ec5d1803d --- /dev/null +++ b/src/v4/social/elements/CreateStoryButtons/CreateStoryButton.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { IconComponent } from '~/v4/core/IconComponent'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { Typography } from '~/v4/core/components'; +import styles from './CreateStoryButton.module.css'; +import clsx from 'clsx'; + +const CreateStoryButtonSvg = (props: React.SVGProps<SVGSVGElement>) => ( + <svg width="17" height="18" viewBox="0 0 17 18" xmlns="http://www.w3.org/2000/svg" {...props}> + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="M0.771159 12.2248C1.00906 12.7861 1.30474 13.3088 1.65819 13.7927C1.85468 14.0618 2.2428 14.0749 2.47778 13.8387L2.85238 13.4622C3.05585 13.2578 3.07242 12.9352 2.91359 12.6944C2.68511 12.3479 2.4918 11.9785 2.33366 11.5862C2.17162 11.1841 2.04651 10.7654 1.95831 10.33C1.90156 10.0497 1.66209 9.83785 1.37619 9.83785H0.7981C0.460143 9.83785 0.194237 10.1301 0.252477 10.463C0.359316 11.0737 0.53221 11.661 0.771159 12.2248ZM0.791992 5.77587C0.565717 6.33406 0.392758 6.91522 0.273115 7.51935C0.206005 7.85821 0.473794 8.1628 0.819238 8.1628H1.37619C1.66209 8.1628 1.90156 7.95091 1.95831 7.6707C2.04651 7.23526 2.17162 6.81652 2.33366 6.41448C2.4918 6.0221 2.68511 5.6527 2.91359 5.30628C3.07242 5.06546 3.05585 4.7429 2.85238 4.53841L2.47778 4.16192C2.2428 3.92576 1.85456 3.93884 1.65906 4.2086C1.30847 4.69235 1.01944 5.21477 0.791992 5.77587ZM5.26074 16.7265C5.81526 16.966 6.39491 17.1396 6.9997 17.247C7.33267 17.3061 7.62533 17.0401 7.62533 16.7019V16.1571C7.62533 15.8715 7.41372 15.6325 7.13436 15.5731C6.70488 15.4817 6.29562 15.3497 5.90658 15.177C5.52127 15.0061 5.14859 14.8025 4.78852 14.5662C4.5475 14.4081 4.22544 14.4327 4.02574 14.6406L3.64108 15.041C3.40868 15.2829 3.43286 15.6735 3.7084 15.8649C4.19354 16.2017 4.71098 16.4889 5.26074 16.7265ZM3.71752 2.12947C3.43846 2.32272 3.41731 2.71924 3.65673 2.95986L4.05715 3.36229C4.26214 3.56831 4.58771 3.58533 4.82847 3.42255C5.1704 3.19137 5.53324 2.99173 5.91699 2.82361C6.3137 2.64981 6.73277 2.5172 7.17418 2.42577C7.45449 2.3677 7.66699 2.12833 7.66699 1.84207V1.29669C7.66699 0.959243 7.37557 0.693429 7.04294 0.750218C6.41529 0.857371 5.82122 1.03203 5.26074 1.27419C4.71443 1.51024 4.20002 1.79533 3.71752 2.12947ZM9.82271 15.5735C9.54395 15.6315 9.33366 15.8703 9.33366 16.155V16.6953C9.33366 17.0359 9.63023 17.3024 9.96541 17.2422C11.8264 16.908 13.4005 16.0311 14.6878 14.6117C16.1184 13.0344 16.8337 11.1639 16.8337 9.00033C16.8337 6.83673 16.1184 4.96627 14.6878 3.38894C13.4005 1.96952 11.8264 1.09268 9.96541 0.758409C9.63023 0.698204 9.33366 0.964784 9.33366 1.30532V1.84562C9.33366 2.13035 9.54395 2.36916 9.82271 2.42716C11.2862 2.73166 12.5155 3.45008 13.5107 4.5824C14.6149 5.83868 15.167 7.31132 15.167 9.00033C15.167 10.6893 14.6149 12.162 13.5107 13.4182C12.5155 14.5506 11.2862 15.269 9.82271 15.5735Z" + /> + <path d="M12.7189 8.47933C12.9012 8.47933 13.0835 8.66162 13.0835 8.84391V9.57308C13.0835 9.77816 12.9012 9.93766 12.7189 9.93766H9.43766V13.2189C9.43766 13.424 9.25537 13.5835 9.07308 13.5835H8.34391C8.13883 13.5835 7.97933 13.424 7.97933 13.2189V9.93766H4.69808C4.493 9.93766 4.3335 9.77816 4.3335 9.57308V8.84391C4.3335 8.66162 4.493 8.47933 4.69808 8.47933H7.97933V5.19808C7.97933 5.01579 8.13883 4.8335 8.34391 4.8335H9.07308C9.25537 4.8335 9.43766 5.01579 9.43766 5.19808V8.47933H12.7189Z" /> + </svg> +); + +interface CreateStoryButtonProps { + pageId?: string; + componentId: string; + onClick?: (e: React.MouseEvent) => void; + defaultClassName?: string; +} + +export function CreateStoryButton({ + pageId = '*', + componentId = '*', + onClick, + defaultClassName, +}: CreateStoryButtonProps) { + const elementId = 'create_story_button'; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + return ( + <div + className={styles.createStoryButton} + onClick={() => {}} //TODO : Add event create story + data-qa-anchor={accessibilityId} + style={themeStyles} + > + <IconComponent + defaultIcon={() => ( + <CreateStoryButtonSvg + className={clsx(styles.createStoryButton__icon, defaultClassName)} + /> + )} + imgIcon={() => <img src={config.image} alt={uiReference} />} + configIconName={config.image} + defaultIconName={defaultConfig.image} + /> + <Typography.Body className={styles.createStoryButton__text}>{config.text}</Typography.Body> + </div> + ); +} + +export default CreateStoryButton; diff --git a/src/v4/social/elements/CreateStoryButtons/index.tsx b/src/v4/social/elements/CreateStoryButtons/index.tsx new file mode 100644 index 000000000..701322549 --- /dev/null +++ b/src/v4/social/elements/CreateStoryButtons/index.tsx @@ -0,0 +1 @@ +export { CreateStoryButton } from './CreateStoryButton'; diff --git a/src/v4/social/elements/FileButton/FileButton.module.css b/src/v4/social/elements/FileButton/FileButton.module.css new file mode 100644 index 000000000..a9be121ad --- /dev/null +++ b/src/v4/social/elements/FileButton/FileButton.module.css @@ -0,0 +1,18 @@ +.fileButton { + display: flex; + align-items: center; + padding: 0.75rem 1rem; +} + +.fileButton__icon { + width: 1.5rem; + height: 1.5rem; + stroke: var(--asc-color-base-default); + background: var(--asc-color-base-shade4); + border-radius: 50%; + padding: 0.25rem; +} + +.fileButton__text { + margin-left: 0.75rem; +} diff --git a/src/v4/social/elements/FileButton/FileButton.tsx b/src/v4/social/elements/FileButton/FileButton.tsx new file mode 100644 index 000000000..caa4d4e70 --- /dev/null +++ b/src/v4/social/elements/FileButton/FileButton.tsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { IconComponent } from '~/v4/core/IconComponent'; +import { Typography } from '~/v4/core/components'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import styles from './FileButton.module.css'; +import clsx from 'clsx'; + +interface FileButtonProps { + pageId: string; + componentId?: string; + imgIconClassName?: string; + defaultIconClassName?: string; + onClick?: (e: React.MouseEvent) => void; +} + +const FileButtonSvg = (props: React.SVGProps<SVGSVGElement>) => { + return ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + {...props} + > + <path + d="M14.9995 7.49951L7.18934 15.4393C6.90804 15.7206 6.75 16.1021 6.75 16.5C6.75 16.8978 6.90804 17.2793 7.18934 17.5606C7.47064 17.8419 7.85218 18 8.25 18C8.64783 18 9.02936 17.8419 9.31066 17.5606L18.6208 8.12083C18.8993 7.84226 19.1203 7.51154 19.2711 7.14756C19.4219 6.78359 19.4995 6.39348 19.4995 5.99951C19.4995 5.60555 19.4219 5.21544 19.2711 4.85146C19.1203 4.48748 18.8993 4.15677 18.6208 3.87819C18.3422 3.59962 18.0115 3.37864 17.6475 3.22787C17.2835 3.07711 16.8934 2.99951 16.4995 2.99951C16.1055 2.99951 15.7154 3.07711 15.3514 3.22787C14.9874 3.37864 14.6567 3.59962 14.3781 3.87819L5.06802 13.318C4.22411 14.1619 3.75 15.3065 3.75 16.5C3.75 17.6934 4.22411 18.838 5.06802 19.682C5.91193 20.5259 7.05653 21 8.25 21C9.44348 21 10.5881 20.5259 11.432 19.682L19.1245 11.9995" + stroke={props.stroke} + stroke-width="1.3" + stroke-linecap="round" + stroke-linejoin="round" + /> + </svg> + ); +}; + +export function FileButton({ + pageId = '*', + componentId = '*', + imgIconClassName, + defaultIconClassName, + onClick, +}: FileButtonProps) { + const elementId = 'file_button'; + const { themeStyles, isExcluded, config, accessibilityId, uiReference, defaultConfig } = + useAmityElement({ pageId, componentId, elementId }); + + if (isExcluded) return null; + + return ( + <div + style={themeStyles} + data-qa-anchor={accessibilityId} + className={styles.fileButton} + onClick={() => {}} + > + <IconComponent + defaultIcon={() => ( + <FileButtonSvg className={clsx(styles.fileButton__icon, defaultIconClassName)} /> + )} + imgIcon={() => <img src={config.icon} alt={uiReference} className={imgIconClassName} />} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} + /> + + <Typography.BodyBold className={styles.fileButton__text}>{config.text}</Typography.BodyBold> + </div> + ); +} diff --git a/src/v4/social/elements/FileButton/index.tsx b/src/v4/social/elements/FileButton/index.tsx new file mode 100644 index 000000000..14381ddf8 --- /dev/null +++ b/src/v4/social/elements/FileButton/index.tsx @@ -0,0 +1 @@ +export { FileButton } from './FileButton'; diff --git a/src/v4/social/elements/ImageButton/ImageButton.module.css b/src/v4/social/elements/ImageButton/ImageButton.module.css new file mode 100644 index 000000000..c8c874bd7 --- /dev/null +++ b/src/v4/social/elements/ImageButton/ImageButton.module.css @@ -0,0 +1,18 @@ +.imageButton { + display: flex; + align-items: center; + padding: 0.75rem 1rem; +} + +.imageButton__icon { + width: 1.5rem; + height: 1.5rem; + stroke: var(--asc-color-base-default); + background: var(--asc-color-base-shade4); + border-radius: 50%; + padding: 0.25rem; +} + +.imageButton__text { + margin-left: 0.75rem; +} diff --git a/src/v4/social/elements/ImageButton/ImageButton.tsx b/src/v4/social/elements/ImageButton/ImageButton.tsx new file mode 100644 index 000000000..7aeb86483 --- /dev/null +++ b/src/v4/social/elements/ImageButton/ImageButton.tsx @@ -0,0 +1,76 @@ +import React from 'react'; +import { IconComponent } from '~/v4/core/IconComponent'; +import { Typography } from '~/v4/core/components'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import styles from './ImageButton.module.css'; +import clsx from 'clsx'; + +interface ImageButtonProps { + pageId: string; + componentId?: string; + imgIconClassName?: string; + defaultIconClassName?: string; + onClick?: (e: React.MouseEvent) => void; +} + +const ImageButtonSvg = (props: React.SVGProps<SVGSVGElement>) => { + return ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + {...props} + > + <path + d="M20.25 4.5H3.75C3.33579 4.5 3 4.83579 3 5.25V18.75C3 19.1642 3.33579 19.5 3.75 19.5H20.25C20.6642 19.5 21 19.1642 21 18.75V5.25C21 4.83579 20.6642 4.5 20.25 4.5Z" + stroke={props.stroke} + stroke-width="1.3" + stroke-linecap="round" + stroke-linejoin="round" + /> + <path + d="M3 15.7499L7.71966 11.0302C7.7893 10.9606 7.87198 10.9053 7.96297 10.8676C8.05397 10.8299 8.1515 10.8105 8.24999 10.8105C8.34848 10.8105 8.44601 10.8299 8.537 10.8676C8.62799 10.9053 8.71067 10.9606 8.78032 11.0302L12.9697 15.2196C13.0393 15.2892 13.122 15.3444 13.213 15.3821C13.304 15.4198 13.4015 15.4392 13.5 15.4392C13.5985 15.4392 13.696 15.4198 13.787 15.3821C13.878 15.3444 13.9607 15.2892 14.0303 15.2196L15.9697 13.2802C16.0393 13.2106 16.122 13.1553 16.213 13.1176C16.304 13.0799 16.4015 13.0605 16.5 13.0605C16.5985 13.0605 16.696 13.0799 16.787 13.1176C16.878 13.1553 16.9607 13.2106 17.0303 13.2802L21 17.2499" + stroke={props.stroke} + stroke-width="1.3" + stroke-linecap="round" + stroke-linejoin="round" + /> + <path d="M14.625 10.5C15.2463 10.5 15.75 9.99632 15.75 9.375C15.75 8.75368 15.2463 8.25 14.625 8.25C14.0037 8.25 13.5 8.75368 13.5 9.375C13.5 9.99632 14.0037 10.5 14.625 10.5Z" /> + </svg> + ); +}; + +export function ImageButton({ + pageId = '*', + componentId = '*', + imgIconClassName, + defaultIconClassName, + onClick, +}: ImageButtonProps) { + const elementId = 'image_button'; + const { themeStyles, isExcluded, config, accessibilityId, uiReference, defaultConfig } = + useAmityElement({ pageId, componentId, elementId }); + + if (isExcluded) return null; + + return ( + <div + style={themeStyles} + data-qa-anchor={accessibilityId} + className={styles.imageButton} + onClick={() => {}} + > + <IconComponent + defaultIcon={() => ( + <ImageButtonSvg className={clsx(styles.imageButton__icon, defaultIconClassName)} /> + )} + imgIcon={() => <img src={config.icon} alt={uiReference} className={imgIconClassName} />} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} + /> + <Typography.BodyBold className={styles.imageButton__text}>{config.text}</Typography.BodyBold> + </div> + ); +} diff --git a/src/v4/social/elements/ImageButton/index.tsx b/src/v4/social/elements/ImageButton/index.tsx new file mode 100644 index 000000000..52db3d56b --- /dev/null +++ b/src/v4/social/elements/ImageButton/index.tsx @@ -0,0 +1 @@ +export { ImageButton } from './ImageButton'; diff --git a/src/v4/social/elements/MyTimelineText/MyTimelineText.module.css b/src/v4/social/elements/MyTimelineText/MyTimelineText.module.css index 8a7e9ae61..d35e35048 100644 --- a/src/v4/social/elements/MyTimelineText/MyTimelineText.module.css +++ b/src/v4/social/elements/MyTimelineText/MyTimelineText.module.css @@ -1,5 +1,5 @@ .myTimelineText { - font-size: 0.9375rem; + font-size: var(--asc-text-font-size-md); font-weight: var(--asc-text-font-weight-bold); line-height: var(--asc-line-height-md); letter-spacing: -0.015rem; diff --git a/src/v4/social/elements/PostCreationButton/PostCreationButton.module.css b/src/v4/social/elements/PostCreationButton/PostCreationButton.module.css index efd771ff9..3ac83ccb8 100644 --- a/src/v4/social/elements/PostCreationButton/PostCreationButton.module.css +++ b/src/v4/social/elements/PostCreationButton/PostCreationButton.module.css @@ -7,8 +7,7 @@ border-radius: 50%; width: 2rem; height: 2rem; - - /* cursor: pointer; */ + cursor: pointer; } .postCreationButton__icon { diff --git a/src/v4/social/elements/PostCreationButton/PostCreationButton.tsx b/src/v4/social/elements/PostCreationButton/PostCreationButton.tsx index 1bc8c6dee..03453cd79 100644 --- a/src/v4/social/elements/PostCreationButton/PostCreationButton.tsx +++ b/src/v4/social/elements/PostCreationButton/PostCreationButton.tsx @@ -8,6 +8,7 @@ import styles from './PostCreationButton.module.css'; const PostCreationButtonSvg = (props: React.SVGProps<SVGSVGElement>) => ( <svg + style={{ pointerEvents: 'none' }} width="14" height="15" viewBox="0 0 14 15" @@ -25,7 +26,7 @@ export interface PostCreationButtonProps { defaultClassName?: string; imgClassName?: string; onClick?: (e: React.MouseEvent) => void; - // createPostButtonRef: React.RefObject<HTMLDivElement>; + createPostButtonRef: React.RefObject<HTMLDivElement>; } export function PostCreationButton({ @@ -34,7 +35,7 @@ export function PostCreationButton({ defaultClassName, imgClassName, onClick, - // createPostButtonRef, + createPostButtonRef, }: PostCreationButtonProps) { const elementId = 'post_creation_button'; const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = @@ -54,7 +55,7 @@ export function PostCreationButton({ className={styles.postCreationButton} onClick={onClick} data-qa-anchor={accessibilityId} - // ref={createPostButtonRef} + ref={createPostButtonRef} > <PostCreationButtonSvg className={clsx(styles.postCreationButton__icon, defaultClassName)} diff --git a/src/v4/social/elements/PostTextField/PostTextField.module.css b/src/v4/social/elements/PostTextField/PostTextField.module.css new file mode 100644 index 000000000..c04978472 --- /dev/null +++ b/src/v4/social/elements/PostTextField/PostTextField.module.css @@ -0,0 +1,34 @@ +.editorPlaceholder { + color: #999; + overflow: hidden; + position: absolute; + top: 4.5rem; + left: 1rem; + user-select: none; + pointer-events: none; +} + +.editorParagraph { + height: 100%; + width: 100%; + color: var(--asc-color-base-default); + background-color: var(--asc-color-base-background); +} + +.editorParagraph span { + color: var(--asc-color-base-default); + background-color: var(--asc-color-base-background); +} + +.editorContainer { + height: 100%; + font-size: var(--asc-text-font-size-md); + color: var(--asc-color-base-default); + background-color: var(--asc-color-base-background); + padding: 0 1rem; +} + +.editorContainer :focus { + border: none; + outline: none; +} diff --git a/src/v4/social/elements/PostTextField/PostTextField.tsx b/src/v4/social/elements/PostTextField/PostTextField.tsx new file mode 100644 index 000000000..207c969ce --- /dev/null +++ b/src/v4/social/elements/PostTextField/PostTextField.tsx @@ -0,0 +1,98 @@ +import React, { forwardRef } from 'react'; +import { LexicalComposer } from '@lexical/react/LexicalComposer'; +import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin'; +import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'; +import { ContentEditable } from '@lexical/react/LexicalContentEditable'; +import { $getRoot, EditorState, LexicalEditor, SerializedLexicalNode } from 'lexical'; +import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary'; +import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'; +import { AutoFocusPlugin } from '@lexical/react/LexicalAutoFocusPlugin'; +import { MentionNode } from '~/v4/social/internal-components/MentionTextInput/MentionNodes'; +import { MentionTextInput } from '~/v4/social/internal-components/MentionTextInput/MentionTextInput'; +import { MetaData, createPostParams } from '~/v4/social/pages/PostComposerPage/PostComposerPage'; +import styles from './PostTextField.module.css'; +import { Mentionees } from '~/v4/helpers/utils'; + +const theme = { + ltr: 'ltr', + rtl: 'rtl', + placeholder: styles.editorPlaceholder, + paragraph: styles.editorParagraph, +}; + +const editorConfig = { + namespace: 'PostTextField', + theme: theme, + onError(error: Error) { + throw error; + }, + nodes: [MentionNode], +}; + +interface EditorStateJson extends SerializedLexicalNode { + children: []; +} + +interface PostTextFieldProps { + onChange: (data: createPostParams) => void; +} + +function editorStateToText(editor: LexicalEditor) { + const editorStateTextString: string[] = []; + const paragraphs = editor.getEditorState().toJSON().root.children as EditorStateJson[]; + + const mentioned: MetaData[] = []; + const mentionees: { + type: string; + userIds: string[]; + }[] = []; + let runningIndex = 0; + + paragraphs.forEach((paragraph) => { + const children = paragraph.children; + const paragraphText: string[] = []; + + children.forEach((child: { type: string; text: string; userId: string }) => { + if (child.text) { + paragraphText.push(child.text); + } + if (child.type === 'mention') { + mentioned.push({ + index: runningIndex, + length: child.text.length, + type: 'user', + userId: child.userId, + }); + + mentionees.push({ type: 'user', userIds: [child.userId] }); + console.log('runningIndex', runningIndex, 'child.text.length', child.text.length); + } + runningIndex += child.text.length; + }); + runningIndex += 1; + editorStateTextString.push(paragraphText.join('')); + }); + return { mentioned, text: editorStateTextString.join('\n'), mentionees }; +} + +export const PostTextField = forwardRef<LexicalEditor, PostTextFieldProps>(({ onChange }) => { + return ( + <LexicalComposer initialConfig={editorConfig}> + <div className={styles.editorContainer}> + <RichTextPlugin + contentEditable={<ContentEditable />} + placeholder={<div className={styles.editorPlaceholder}>What’s going on...</div>} + ErrorBoundary={LexicalErrorBoundary} + /> + <OnChangePlugin + onChange={(editorState, editor) => { + onChange(editorStateToText(editor)); + }} + /> + <HistoryPlugin /> + <AutoFocusPlugin /> + <MentionTextInput /> + </div> + </LexicalComposer> + ); +}); diff --git a/src/v4/social/elements/PostTextField/index.tsx b/src/v4/social/elements/PostTextField/index.tsx new file mode 100644 index 000000000..1afb1f1eb --- /dev/null +++ b/src/v4/social/elements/PostTextField/index.tsx @@ -0,0 +1 @@ +export { PostTextField } from './PostTextField'; \ No newline at end of file diff --git a/src/v4/social/elements/VideoButton/VideoButton.module.css b/src/v4/social/elements/VideoButton/VideoButton.module.css new file mode 100644 index 000000000..90da13076 --- /dev/null +++ b/src/v4/social/elements/VideoButton/VideoButton.module.css @@ -0,0 +1,18 @@ +.videoButton { + display: flex; + align-items: center; + padding: 0.75rem 1rem; +} + +.videoButton__icon { + width: 1.5rem; + height: 1.5rem; + stroke: var(--asc-color-base-default); + background: var(--asc-color-base-shade4); + border-radius: 50%; + padding: 0.25rem; +} + +.videoButton__text { + margin-left: 0.75rem; +} diff --git a/src/v4/social/elements/VideoButton/VideoButton.tsx b/src/v4/social/elements/VideoButton/VideoButton.tsx new file mode 100644 index 000000000..bef031691 --- /dev/null +++ b/src/v4/social/elements/VideoButton/VideoButton.tsx @@ -0,0 +1,74 @@ +import React from 'react'; +import { IconComponent } from '~/v4/core/IconComponent'; +import { Typography } from '~/v4/core/components'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import styles from './VideoButton.module.css'; +import clsx from 'clsx'; + +interface VideoButtonProps { + pageId: string; + componentId?: string; + imgIconClassName?: string; + defaultIconClassName?: string; + onClick?: (e: React.MouseEvent) => void; +} + +const VideoButtonSvg = (props: React.SVGProps<SVGSVGElement>) => { + return ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + {...props} + > + <path + d="M12 21C16.9706 21 21 16.9706 21 12C21 7.02944 16.9706 3 12 3C7.02944 3 3 7.02944 3 12C3 16.9706 7.02944 21 12 21Z" + stroke={props.stroke} + stroke-width="1.3" + stroke-miterlimit="10" + /> + <path + d="M16 12L10 8V16L16 12Z" + stroke={props.stroke} + stroke-width="1.3" + stroke-linecap="round" + stroke-linejoin="round" + /> + </svg> + ); +}; + +export function VideoButton({ + pageId = '*', + componentId = '*', + imgIconClassName, + defaultIconClassName, + onClick, +}: VideoButtonProps) { + const elementId = 'video_button'; + const { themeStyles, isExcluded, config, accessibilityId, uiReference, defaultConfig } = + useAmityElement({ pageId, componentId, elementId }); + + if (isExcluded) return null; + + return ( + <div + style={themeStyles} + data-qa-anchor={accessibilityId} + className={styles.videoButton} + onClick={() => {}} + > + <IconComponent + defaultIcon={() => ( + <VideoButtonSvg className={clsx(styles.videoButton__icon, defaultIconClassName)} /> + )} + imgIcon={() => <img src={config.icon} alt={uiReference} className={imgIconClassName} />} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} + /> + <Typography.BodyBold className={styles.videoButton__text}>{config.text}</Typography.BodyBold> + </div> + ); +} diff --git a/src/v4/social/elements/VideoButton/index.tsx b/src/v4/social/elements/VideoButton/index.tsx new file mode 100644 index 000000000..f106baad7 --- /dev/null +++ b/src/v4/social/elements/VideoButton/index.tsx @@ -0,0 +1 @@ +export { VideoButton } from './VideoButton'; diff --git a/src/v4/social/internal-components/CommunityMember/CommunityMember.module.css b/src/v4/social/internal-components/CommunityMember/CommunityMember.module.css new file mode 100644 index 000000000..b4b048958 --- /dev/null +++ b/src/v4/social/internal-components/CommunityMember/CommunityMember.module.css @@ -0,0 +1,27 @@ +.communityMember__item { + width: 100%; + display: flex; + align-items: center; + padding: 0.5rem 1rem; + background-color: var(--asc-color-base-background); + color: var(--asc-color-base-default); +} + +.communityMember__item:focus { + background-color: var(--asc-color-base-shade4); +} + +.communityMember__avatar { + width: 2.5rem; + height: 2.5rem; +} + +.communityMember__displayName { + margin-left: 0.5rem; + font-size: 1rem; + display: block; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + color: var(--asc-color-base-default); +} diff --git a/src/v4/social/internal-components/CommunityMember/CommunityMember.tsx b/src/v4/social/internal-components/CommunityMember/CommunityMember.tsx new file mode 100644 index 000000000..d1dd6a14a --- /dev/null +++ b/src/v4/social/internal-components/CommunityMember/CommunityMember.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import styles from './CommunityMember.module.css'; +import { UserAvatar } from '~/v4/social/internal-components/UserAvatar/UserAvatar'; +import { MentionTypeaheadOption } from '../MentionTextInput/MentionTextInput'; + +interface CommunityMemberProps { + isSelected: boolean; + onClick: () => void; + onMouseEnter: () => void; + option: MentionTypeaheadOption; +} + +export function CommunityMember({ + isSelected, + onClick, + onMouseEnter, + option, +}: CommunityMemberProps) { + let className = 'item'; + if (isSelected) { + className += ' selected'; + } + + return ( + <div + key={option.key} + tabIndex={-1} + className={className} + ref={option.setRefElement} + role="option" + aria-selected={isSelected} + onMouseEnter={onMouseEnter} + onClick={onClick} + > + <div key={option.key} className={styles.communityMember__item}> + <div> + <UserAvatar + className={styles.communityMember__avatar} + userId={option.user.avatarFileId} + /> + </div> + <p className={styles.communityMember__displayName}>{option.user.displayName}</p> + </div> + </div> + ); +} diff --git a/src/v4/social/internal-components/CommunityMember/index.tsx b/src/v4/social/internal-components/CommunityMember/index.tsx new file mode 100644 index 000000000..ebd77e25f --- /dev/null +++ b/src/v4/social/internal-components/CommunityMember/index.tsx @@ -0,0 +1 @@ +export { CommunityMember } from './CommunityMember'; diff --git a/src/v4/social/internal-components/MentionTextInput/MentionNodes.ts b/src/v4/social/internal-components/MentionTextInput/MentionNodes.ts new file mode 100644 index 000000000..6203fa2d2 --- /dev/null +++ b/src/v4/social/internal-components/MentionTextInput/MentionNodes.ts @@ -0,0 +1,195 @@ +import type { Spread } from 'lexical'; + +import { + DOMConversionMap, + DOMConversionOutput, + DOMExportOutput, + EditorConfig, + LexicalNode, + NodeKey, + SerializedTextNode, + TextNode, +} from 'lexical'; + +export type SerializedMentionNode = Spread< + { + mentionName: string; + displayName?: string; + userId?: string; + userInternalId?: string; + userPublicId?: string; + type: 'mention'; + version: 1; + }, + SerializedTextNode +>; + +function convertMentionElement(domNode: HTMLElement): DOMConversionOutput | null { + const textContent = domNode.textContent; + + if (textContent !== null) { + const node = $createMentionNode({ mentionName: textContent }); + return { + node, + }; + } + + return null; +} + +const mentionStyle = `color: var(--asc-color-primary-default)`; + +export class MentionNode extends TextNode { + __mention: string; + __displayName: string | undefined; + __userId: string | undefined; + __userInternalId: string | undefined; + __userPublicId: string | undefined; + + static getType(): string { + return 'mention'; + } + + static clone(node: MentionNode): MentionNode { + return new MentionNode({ + mentionName: node.__mention, + displayName: node.__displayName, + userId: node.__userId, + userInternalId: node.__userInternalId, + userPublicId: node.__userPublicId, + text: node.__text, + key: node.__key, + }); + } + static importJSON(serializedNode: SerializedMentionNode): MentionNode { + const node = $createMentionNode({ + mentionName: serializedNode.mentionName, + displayName: serializedNode.displayName, + userId: serializedNode.userId, + userInternalId: serializedNode.userInternalId, + userPublicId: serializedNode.userPublicId, + }); + node.setTextContent(serializedNode.text); + node.setFormat(serializedNode.format); + node.setDetail(serializedNode.detail); + node.setMode(serializedNode.mode); + node.setStyle(serializedNode.style); + return node; + } + + constructor({ + mentionName, + displayName, + userId, + userInternalId, + userPublicId, + text, + key, + }: { + mentionName: string; + text?: string; + key?: NodeKey; + displayName?: string; + userId?: string; + userInternalId?: string; + userPublicId?: string; + }) { + super(text ?? mentionName, key); + this.__mention = mentionName; + this.__displayName = displayName; + this.__userId = userId; + this.__userInternalId = userInternalId; + this.__userPublicId = userPublicId; + + } + + exportJSON(): SerializedMentionNode { + return { + ...super.exportJSON(), + mentionName: this.__mention, + displayName: this.__displayName, + userId: this.__userId, + userInternalId: this.__userInternalId, + userPublicId: this.__userPublicId, + type: 'mention', + version: 1, + }; + } + + createDOM(config: EditorConfig): HTMLElement { + const dom = super.createDOM(config); + dom.style.cssText = mentionStyle; //class name css + dom.className = 'mention'; //create css + return dom; + } + + exportDOM(): DOMExportOutput { + const element = document.createElement('span'); + element.setAttribute('data-lexical-mention', 'true'); + element.textContent = this.__text; + return { element }; + } + + isSegmented(): false { + return false; + } + + static importDOM(): DOMConversionMap | null { + return { + span: (domNode: HTMLElement) => { + if (!domNode.hasAttribute('data-lexical-mention')) { + return null; + } + return { + conversion: convertMentionElement, + priority: 1, + }; + }, + }; + } + + isTextEntity(): true { + return true; + } + + isToken(): true { + return true; + } + + canInsertTextBefore(): boolean { + return false; + } + + canInsertTextAfter(): boolean { + return false; + } +} + +export function $createMentionNode({ + mentionName, + displayName, + userId, + userInternalId, + userPublicId, +}: { + mentionName: string; + displayName?: string; + userId?: string; + userInternalId?: string; + userPublicId?: string; +}): MentionNode { + const mentionNode = new MentionNode({ + mentionName: `@${mentionName}`, + displayName: displayName, + userId: userId, + userInternalId: userInternalId, + userPublicId: userPublicId, + }) + .setMode('segmented') + .toggleDirectionless(); + return mentionNode; +} + +export function $isMentionNode(node: LexicalNode | null | undefined): node is MentionNode { + return node instanceof MentionNode; +} diff --git a/src/v4/social/internal-components/MentionTextInput/MentionTextInput.module.css b/src/v4/social/internal-components/MentionTextInput/MentionTextInput.module.css new file mode 100644 index 000000000..38bcd95fc --- /dev/null +++ b/src/v4/social/internal-components/MentionTextInput/MentionTextInput.module.css @@ -0,0 +1,6 @@ +.mentionTextInput_item { + position: fixed; + bottom: 0; + left: 0; + width: 100vw; +} diff --git a/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx b/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx new file mode 100644 index 000000000..dc70699ab --- /dev/null +++ b/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx @@ -0,0 +1,228 @@ +import styles from './MentionTextInput.module.css'; +import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; +import { + LexicalTypeaheadMenuPlugin, + MenuOption, + MenuTextMatch, +} from '@lexical/react/LexicalTypeaheadMenuPlugin'; +import { TextNode } from 'lexical'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import ReactDOM from 'react-dom'; +import { $createMentionNode } from './MentionNodes'; +import { CommunityMember } from '../CommunityMember'; +import { UserRepository } from '@amityco/ts-sdk'; + +const MAX_LENGTH = 5000; + +const PUNCTUATION = '\\.,\\+\\*\\?\\$\\@\\|#{}\\(\\)\\^\\-\\[\\]\\\\/!%\'"~=<>_:;'; +const NAME = '\\b[A-Z][^\\s' + PUNCTUATION + ']'; + +const DocumentMentionsRegex = { + NAME, + PUNCTUATION, +}; + +const PUNC = DocumentMentionsRegex.PUNCTUATION; + +const TRIGGERS = ['@'].join(''); + +// Chars we expect to see in a mention (non-space, non-punctuation). +const VALID_CHARS = '[^' + TRIGGERS + PUNC + '\\s]'; + +// Non-standard series of chars. Each series must be preceded and followed by +// a valid char. +const VALID_JOINS = + '(?:' + + '\\.[ |$]|' + // E.g. "r. " in "Mr. Smith" + ' |' + // E.g. " " in "Josh Duck" + '[' + + PUNC + + ']|' + // E.g. "-' in "Salier-Hellendag" + ')'; + +const LENGTH_LIMIT = 75; + +const AtSignMentionsRegex = new RegExp( + '(^|\\s|\\()(' + + '[' + + TRIGGERS + + ']' + + '((?:' + + VALID_CHARS + + VALID_JOINS + + '){0,' + + LENGTH_LIMIT + + '})' + + ')$', +); + +// 50 is the longest alias length limit. +const ALIAS_LENGTH_LIMIT = 50; + +// Regex used to match alias. +const AtSignMentionsRegexAliasRegex = new RegExp( + '(^|\\s|\\()(' + + '[' + + TRIGGERS + + ']' + + '((?:' + + VALID_CHARS + + '){0,' + + ALIAS_LENGTH_LIMIT + + '})' + + ')$', +); + +function checkForAtSignMentions(text: string, minMatchLength: number): MenuTextMatch | null { + let match = AtSignMentionsRegex.exec(text); + + if (match === null) { + match = AtSignMentionsRegexAliasRegex.exec(text); + } + if (match !== null) { + // The strategy ignores leading whitespace but we need to know it's + // length to add it to the leadOffset + const maybeLeadingWhitespace = match[1]; + + const matchingString = match[3]; + if (matchingString.length >= minMatchLength) { + return { + leadOffset: match.index + maybeLeadingWhitespace.length, + matchingString, + replaceableString: match[2], + }; + } + } + return null; +} + +function getPossibleQueryMatch(text: string): MenuTextMatch | null { + return checkForAtSignMentions(text, 1); +} + +export class MentionTypeaheadOption extends MenuOption { + user: Amity.User; + + constructor(user: Amity.User) { + super(user.userId); + this.user = user; + } +} + +export const useUserQueryByDisplayName = (displayName: string) => { + const [items, setItems] = useState<Amity.User[]>([]); + const [isLoading, setIsLoading] = useState(false); + const [hasMore, setHasMore] = useState(false); + const [loadMoreHasBeenCalled, setLoadMoreHasBeenCalled] = useState(false); + const loadMoreRef = useRef<(() => void) | null>(null); + const unSubRef = useRef<(() => void) | null>(null); + + const loadMore = () => { + if (loadMoreRef.current) { + loadMoreRef.current(); + setLoadMoreHasBeenCalled(true); + } + }; + + useEffect(() => { + if (unSubRef.current) { + unSubRef.current(); + unSubRef.current = null; + } + + const unSubFn = UserRepository.searchUserByDisplayName({ displayName }, (response) => { + setHasMore(response.hasNextPage || false); + setIsLoading(response.loading); + loadMoreRef.current = response.onNextPage || null; + setItems(response.data); + }); + unSubRef.current = unSubFn; + + return () => { + unSubRef.current?.(); + unSubRef.current = null; + }; + }, [displayName]); + + return { + users: items, + isLoading, + hasMore, + loadMore, + loadMoreHasBeenCalled, + }; +}; + +export function MentionTextInput(): JSX.Element | null { + const [editor] = useLexicalComposerContext(); + + const [queryString, setQueryString] = useState<string | null>(null); + + const { users } = useUserQueryByDisplayName(queryString || '' ); + + const options = useMemo(() => users.map((user) => new MentionTypeaheadOption(user)), [users]); + + const onSelectOption = useCallback( + ( + selectedOption: MentionTypeaheadOption, + nodeToReplace: TextNode | null, + closeMenu: () => void, + ) => { + editor.update(() => { + const mentionNode = $createMentionNode({ + mentionName: selectedOption.key, + displayName: selectedOption.user.displayName, + userId: selectedOption.user.userId, + }); + if (nodeToReplace) { + nodeToReplace.replace(mentionNode); + } + mentionNode.select(); + closeMenu(); + }); + }, + [editor], + ); + + const checkForMentionMatch = useCallback( + (text: string) => { + return getPossibleQueryMatch(text); + }, + [editor], + ); + + return ( + <LexicalTypeaheadMenuPlugin + onQueryChange={setQueryString} + onSelectOption={onSelectOption} + triggerFn={checkForMentionMatch} + options={options} + menuRenderFn={( + anchorElementRef, + { selectedIndex, selectOptionAndCleanUp, setHighlightedIndex }, + ) => + anchorElementRef.current && users.length + ? ReactDOM.createPortal( + <div className={styles.mentionTextInput_item}> + {options.map((option, i: number) => ( + <CommunityMember + isSelected={selectedIndex === i} + onClick={() => { + setHighlightedIndex(i); + selectOptionAndCleanUp(option); + }} + onMouseEnter={() => { + setHighlightedIndex(i); + }} + key={option.key} + option={option} + /> + ))} + </div>, + anchorElementRef.current, + ) + : null + } + /> + ); +} diff --git a/src/v4/social/internal-components/MentionTextInput/index.tsx b/src/v4/social/internal-components/MentionTextInput/index.tsx new file mode 100644 index 000000000..8c7baec93 --- /dev/null +++ b/src/v4/social/internal-components/MentionTextInput/index.tsx @@ -0,0 +1 @@ +export { MentionTextInput } from './MentionTextInput'; diff --git a/src/v4/social/pages/Application/index.tsx b/src/v4/social/pages/Application/index.tsx index 7f7ff625a..34554908f 100644 --- a/src/v4/social/pages/Application/index.tsx +++ b/src/v4/social/pages/Application/index.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { SocialHomePage } from '~/v4/social/pages/SocialHomePage'; +import { PostComposerPage } from '~/v4/social/pages/PostComposerPage'; import { PostDetailPage } from '~/v4/social/pages/PostDetailPage'; import { PageTypes, useNavigation } from '~/v4/core/providers/NavigationProvider'; import { StoryProvider } from '~/v4/social/providers/StoryProvider'; @@ -22,7 +23,8 @@ const Application = () => { {page.type === PageTypes.ViewStoryPage && ( <ViewStoryPage type="globalFeed" targetId={page.context.targetId} /> )} - {/* {page.type === PageTypes.SelectPostTargetPage && <SelectPostTargetPage />} */} + {page.type === PageTypes.PostComposerPage && <PostComposerPage mode={page.context.mode} targetId={page.context.targetId} targetType={page.context.targetType} community={page.context.community} post={page.context.post} />} + {page.type === PageTypes.SelectPostTargetPage && <SelectPostTargetPage />} </div> </StoryProvider> ); diff --git a/src/v4/social/pages/PostComposerPage/PostComposerPage.module.css b/src/v4/social/pages/PostComposerPage/PostComposerPage.module.css new file mode 100644 index 000000000..62dc5e203 --- /dev/null +++ b/src/v4/social/pages/PostComposerPage/PostComposerPage.module.css @@ -0,0 +1,36 @@ +.postComposerPage { + display: block; + background-color: var(--asc-color-base-background); + height: 100%; +} + +.selectPostTargetPage__topBar { + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + padding: var(--asc-spacing-m1); +} + +.selectPostTargetPage__status { + position: absolute; + left: 1rem; + bottom: 0; + width: calc(100% - 2rem); + display: flex; + justify-content: start; + padding: unset; + font-size: var(--asc-text-font-size-md); + gap: 0.75rem; + margin: 0 auto 1rem; + border-radius: 0.5rem; + box-shadow: var(--asc-box-shadow-03); + background-color: var(--asc-color-secondary-default); + color: var(--asc-color-base-background); +} + +.selectPostTargetPag_infoIcon { + width: 1.5rem; + height: 1.5rem; + fill: var(--asc-color-base-background); +} diff --git a/src/v4/social/pages/PostComposerPage/PostComposerPage.stories.tsx b/src/v4/social/pages/PostComposerPage/PostComposerPage.stories.tsx new file mode 100644 index 000000000..2a709f4a9 --- /dev/null +++ b/src/v4/social/pages/PostComposerPage/PostComposerPage.stories.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { PostComposerPage } from './PostComposerPage'; + +export default { + title: 'v4-social/pages/PostComposerPage', +}; + +export const PostComposerPageStories = { + render: () => { + return <PostComposerPage />; + }, +}; diff --git a/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx b/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx new file mode 100644 index 000000000..643387e68 --- /dev/null +++ b/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx @@ -0,0 +1,156 @@ +import React, { useRef, useState } from 'react'; +import styles from './PostComposerPage.module.css'; +import { useAmityPage } from '~/v4/core/hooks/uikit'; +import { CloseButton } from '~/v4/social/elements/CloseButton/CloseButton'; +import { useNavigation } from '~/v4/core/providers/NavigationProvider'; +import { CommunityDisplayName } from '~/v4/social/elements/CommunityDisplayName'; +import { CreateNewPostButton } from '~/v4/social/elements/CreateNewPostButton'; +import { PostTextField } from '~/v4/social/elements/PostTextField'; +import { PostRepository } from '@amityco/ts-sdk'; +import { LexicalEditor } from 'lexical'; +import { useMutation } from '@tanstack/react-query'; +import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; +import { Notification } from '~/v4/core/components/Notification'; +import { Spinner } from '../../internal-components/Spinner'; +import ExclamationCircle from '~/v4/icons/ExclamationCircle'; +import { useForm } from 'react-hook-form'; +import { Mentioned } from '~/v4/helpers/utils'; + +export enum Mode { + CREATE = 'create', + EDIT = 'edit', +} +interface AmityPostComposerEditOptions { + mode: Mode.EDIT; + post?: Amity.Post; +} + +interface AmityPostComposerCreateOptions { + mode: Mode.CREATE; + targetId: string | null; + targetType: 'community' | 'user'; + community?: Amity.Community; +} + +export interface MetaData { + index: number; + length: number; + type: string; + userId?: string; +} + +type PostComposerPageProps = AmityPostComposerCreateOptions | AmityPostComposerEditOptions; + +const isCreatePage = (props: PostComposerPageProps): props is AmityPostComposerCreateOptions => { + return props.mode === Mode.CREATE; +}; + +export function PostComposerPage(props: PostComposerPageProps) { + if (isCreatePage(props)) { + const { targetId, targetType, community } = props; + return ( + <CreateInternal + mode={Mode.CREATE} + targetId={targetId} + targetType={targetType} + community={community} + /> + ); + } else { + return null; + } +} + +export type createPostParams = { + text: string; + mentioned: Mentioned[]; + mentionees: { + type: string; + userIds: string[]; + }[]; +}; + +const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCreateOptions) => { + const pageId = 'post_composer_page'; + const { themeStyles } = useAmityPage({ + pageId, + }); + const { onBack } = useNavigation(); + const editorRef = useRef<LexicalEditor | null>(null); + const { AmityPostComposerPageBehavior } = usePageBehavior(); + + const [textValue, setTextValue] = useState<createPostParams>({ + text: '', + mentioned: [], + mentionees: [ + { + type: 'user', + userIds: [''], + }, + ], + }); + + const useMutateCreatePost = () => + useMutation({ + mutationFn: async () => { + return PostRepository.createPost({ + targetId: targetId, + targetType: targetType, + data: { text: textValue.text }, + dataType: 'text', + metadata: { mentioned: textValue.mentioned }, + mentionees: textValue.mentionees, + }); + }, + onSuccess: () => { + AmityPostComposerPageBehavior.goToSocialHomePage(); + }, + onError: (error) => { + console.error('Failed to create post', error); + }, + }); + + const { mutateAsync: mutateCreatePostAsync, isPending, isError } = useMutateCreatePost(); + + const { handleSubmit } = useForm(); + + const onSubmit = () => { + mutateCreatePostAsync(); + }; + + const onChange = (val: createPostParams) => { + setTextValue(val); + }; + + return ( + <div className={styles.postComposerPage} style={themeStyles}> + <form onSubmit={handleSubmit(onSubmit)}> + <div className={styles.selectPostTargetPage__topBar}> + <CloseButton pageId={pageId} onPress={onBack} /> + <CommunityDisplayName pageId={pageId} community={community} /> + <CreateNewPostButton + pageId={pageId} + onSubmit={handleSubmit(onSubmit)} + isValid={textValue.text.length > 0} + /> + </div> + + <PostTextField ref={editorRef} onChange={onChange} /> + {isPending && ( + <Notification + content="Posting..." + icon={<Spinner />} + className={styles.selectPostTargetPage__status} + /> + )} + {isError && ( + <Notification + content="Failed to create post" + icon={<ExclamationCircle className={styles.selectPostTargetPag_infoIcon} />} + className={styles.selectPostTargetPage__status} + /> + )} + </form> + </div> + ); +}; diff --git a/src/v4/social/pages/PostComposerPage/index.tsx b/src/v4/social/pages/PostComposerPage/index.tsx new file mode 100644 index 000000000..662fb7624 --- /dev/null +++ b/src/v4/social/pages/PostComposerPage/index.tsx @@ -0,0 +1 @@ +export { PostComposerPage } from './PostComposerPage'; \ No newline at end of file diff --git a/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.module.css b/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.module.css index a57d1b193..47addeb0b 100644 --- a/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.module.css +++ b/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.module.css @@ -10,6 +10,7 @@ justify-content: space-between; align-items: center; padding: var(--asc-spacing-m1) var(--asc-spacing-none); + background-color: var(--asc-color-base-background); } .selectPostTargetPage__timeline { @@ -28,7 +29,7 @@ text-align: left; color: var(--asc-color-base-default); padding: var(--asc-spacing-m1) var(--asc-spacing-none) var(--asc-spacing-s1); - font-size: 0.9375rem; + font-size: var(--asc-text-font-size-md); line-height: var(--asc-line-height-md); letter-spacing: -0.015rem; opacity: 0.4; diff --git a/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.stories.tsx b/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.stories.tsx new file mode 100644 index 000000000..1c5bfbf45 --- /dev/null +++ b/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.stories.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { SelectPostTargetPage } from './SelectPostTargetPage'; + +export default { + title: 'v4-social/pages/SelectPostTargetPage', +}; + +export const SelectPostTargetPageStories = { + render: () => { + return <SelectPostTargetPage />; + }, +}; diff --git a/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.tsx b/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.tsx index 7cae309e0..8c6d8aa62 100644 --- a/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.tsx +++ b/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.tsx @@ -13,6 +13,9 @@ import { CommunityDisplayName } from '~/v4/social/elements/CommunityDisplayName' import { CommunityAvatar } from '~/v4/social/elements/CommunityAvatar'; import useIntersectionObserver from '~/v4/core/hooks/useIntersectionObserver'; import useUser from '~/v4/core/hooks/objects/useUser'; +import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; +import useSDK from '~/v4/core/hooks/useSDK'; +import { Mode } from '../PostComposerPage/PostComposerPage'; export function SelectPostTargetPage() { const pageId = 'select_post_target_page'; @@ -24,9 +27,11 @@ export function SelectPostTargetPage() { sortBy: 'displayName', limit: 20, }); - const intersectionRef = useRef<HTMLDivElement>(null); - const { user } = useUser(); + const { AmityPostTargetSelectionPage } = usePageBehavior(); + const intersectionRef = useRef<HTMLDivElement>(null); + const { currentUserId } = useSDK(); + const { user } = useUser(currentUserId); useIntersectionObserver({ onIntersect: () => { if (hasMore && isLoading === false) { @@ -40,7 +45,12 @@ export function SelectPostTargetPage() { return ( <div onClick={() => { - //TODO: Navigate to create post page + AmityPostTargetSelectionPage.goToPostComposerPage({ + targetId: community.communityId, + targetType: 'community', + mode: Mode.CREATE, + community: community, + }); }} key={community.communityId} className={styles.selectPostTargetPage__timeline} @@ -63,14 +73,19 @@ export function SelectPostTargetPage() { <CloseButton imgClassName={styles.selectPostTargetPage__closeButton} pageId={pageId} - onClick={onBack} + onPress={onBack} /> <Title pageId={pageId} titleClassName={styles.selectPostTargetPage__title} /> <div /> </div> <div onClick={() => { - // TODO: Navigate to create post page + AmityPostTargetSelectionPage.goToPostComposerPage({ + mode: Mode.CREATE, + targetId: null, + targetType: 'user', + community: undefined, + }); }} className={styles.selectPostTargetPage__timeline} > diff --git a/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx b/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx index 9db2cb64c..c65a10721 100644 --- a/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx +++ b/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx @@ -8,7 +8,7 @@ import { ExploreButton } from '~/v4/social/elements/ExploreButton'; import { MyCommunitiesButton } from '~/v4/social/elements/MyCommunitiesButton'; import { Newsfeed } from '~/v4/social/components/Newsfeed'; import { useAmityPage } from '~/v4/core/hooks/uikit'; -// import { CreatePostMenu } from '~/v4/social/components/CreatePostMenu'; +import { CreatePostMenu } from '~/v4/social/components/CreatePostMenu'; enum EnumTabNames { Newsfeed = 'Newsfeed', @@ -24,47 +24,45 @@ export function SocialHomePage() { const [activeTab, setActiveTab] = useState(EnumTabNames.Newsfeed); - // const [isShowCreatePostMenu, setIsShowCreatePostMenu] = useState(false); - // const createPostMenuRef = useRef<HTMLDivElement | null>(null); - // const createPostButtonRef = useRef<HTMLDivElement>(null); + const [isShowCreatePostMenu, setIsShowCreatePostMenu] = useState(false); + const createPostMenuRef = useRef<HTMLDivElement | null>(null); + const createPostButtonRef = useRef<HTMLDivElement>(null); - // const handleClickButton = (event: React.MouseEvent) => { - // event.stopPropagation(); - // setIsShowCreatePostMenu((prev) => !prev); - // }; - - // Hide code for current release + const handleClickButton = (event: React.MouseEvent) => { + event.stopPropagation(); + setIsShowCreatePostMenu((prev) => !prev); + }; - // useEffect(() => { - // const handleClickOutside = (event: MouseEvent) => { - // if ( - // createPostMenuRef.current && - // !createPostMenuRef.current.contains(event.target as Node) && - // createPostButtonRef.current !== event.target - // ) { - // setIsShowCreatePostMenu(false); - // } - // }; + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + createPostMenuRef.current && + !createPostMenuRef.current.contains(event.target as Node) && + createPostButtonRef.current !== event.target + ) { + setIsShowCreatePostMenu(false); + } + }; - // document.addEventListener('mousedown', handleClickOutside); - // return () => { - // document.removeEventListener('mousedown', handleClickOutside); - // }; - // }, []); + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); return ( <div className={styles.socialHomePage} style={themeStyles}> <div className={styles.socialHomePage__topBar}> <TopNavigation pageId={pageId} - // onClickPostCreationButton={handleClickButton} - // createPostButtonRef={createPostButtonRef} + onClickPostCreationButton={handleClickButton} + createPostButtonRef={createPostButtonRef} /> - {/* {isShowCreatePostMenu && ( + {isShowCreatePostMenu && ( <div ref={createPostMenuRef}> <CreatePostMenu pageId={pageId} /> </div> - )} */} + )} <div className={styles.socialHomePage__tabs}> <NewsfeedButton pageId={pageId} From f24f71003eee3261efa3f0200ee1d3efac22cf5a Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Tue, 25 Jun 2024 15:41:12 +0700 Subject: [PATCH 160/300] fix: remove log (#445) --- src/v4/social/elements/PostTextField/PostTextField.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/v4/social/elements/PostTextField/PostTextField.tsx b/src/v4/social/elements/PostTextField/PostTextField.tsx index 207c969ce..6e5f9cea8 100644 --- a/src/v4/social/elements/PostTextField/PostTextField.tsx +++ b/src/v4/social/elements/PostTextField/PostTextField.tsx @@ -65,7 +65,6 @@ function editorStateToText(editor: LexicalEditor) { }); mentionees.push({ type: 'user', userIds: [child.userId] }); - console.log('runningIndex', runningIndex, 'child.text.length', child.text.length); } runningIndex += child.text.length; }); @@ -76,7 +75,7 @@ function editorStateToText(editor: LexicalEditor) { } export const PostTextField = forwardRef<LexicalEditor, PostTextFieldProps>(({ onChange }) => { - return ( + return ( <LexicalComposer initialConfig={editorConfig}> <div className={styles.editorContainer}> <RichTextPlugin From 137ac9d10b185d4a1c46a05bf4f171278d3e0573 Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Wed, 26 Jun 2024 17:41:41 +0700 Subject: [PATCH 161/300] feat: ASC-00000 - Add empty user (#447) * feat: add user7 * fix: remove mock userId --- .github/workflows/dev.yaml | 1 + .github/workflows/staging.yaml | 1 + .storybook/decorators/UiKitDecorator.tsx | 4 ++++ .storybook/decorators/UiKitV4Decorator.tsx | 4 ++++ 4 files changed, 10 insertions(+) diff --git a/.github/workflows/dev.yaml b/.github/workflows/dev.yaml index 1e1dccf41..e8cc56112 100644 --- a/.github/workflows/dev.yaml +++ b/.github/workflows/dev.yaml @@ -24,6 +24,7 @@ jobs: STORYBOOK_USER4: ${{ secrets.STORYBOOK_USER4 }} STORYBOOK_USER5: ${{ secrets.STORYBOOK_USER5 }} STORYBOOK_USER6: ${{ secrets.STORYBOOK_USER6 }} + STORYBOOK_USER7: ${{ secrets.STORYBOOK_USER7 }} steps: - name: git checkout diff --git a/.github/workflows/staging.yaml b/.github/workflows/staging.yaml index c125d6b21..3ecb19841 100644 --- a/.github/workflows/staging.yaml +++ b/.github/workflows/staging.yaml @@ -23,6 +23,7 @@ jobs: STORYBOOK_USER4: ${{ secrets.STORYBOOK_USER4 }} STORYBOOK_USER5: ${{ secrets.STORYBOOK_USER5 }} STORYBOOK_USER6: ${{ secrets.STORYBOOK_USER6 }} + STORYBOOK_USER7: ${{ secrets.STORYBOOK_USER7 }} steps: - name: git checkout diff --git a/.storybook/decorators/UiKitDecorator.tsx b/.storybook/decorators/UiKitDecorator.tsx index 5043a0d3f..ad8bdd958 100644 --- a/.storybook/decorators/UiKitDecorator.tsx +++ b/.storybook/decorators/UiKitDecorator.tsx @@ -37,6 +37,10 @@ const global = { value: import.meta.env.STORYBOOK_USER6, title: import.meta.env.STORYBOOK_USER6?.split(',')[1], }, + { + value: import.meta.env.STORYBOOK_USER7, + title: import.meta.env.STORYBOOK_USER7?.split(',')[1], + }, ], }, }, diff --git a/.storybook/decorators/UiKitV4Decorator.tsx b/.storybook/decorators/UiKitV4Decorator.tsx index afb8dde46..3f2bea832 100644 --- a/.storybook/decorators/UiKitV4Decorator.tsx +++ b/.storybook/decorators/UiKitV4Decorator.tsx @@ -38,6 +38,10 @@ const global = { value: import.meta.env.STORYBOOK_USER6, title: import.meta.env.STORYBOOK_USER6?.split(',')[1], }, + { + value: import.meta.env.STORYBOOK_USER7, + title: import.meta.env.STORYBOOK_USER7?.split(',')[1], + }, ], }, }, From ca21dada767748b6dfc275446ad02b8ded54f806 Mon Sep 17 00:00:00 2001 From: Pitchaya T <33589608+ptchayap@users.noreply.github.com> Date: Thu, 27 Jun 2024 19:46:46 +0700 Subject: [PATCH 162/300] feat: ASC-23125 - ads on post (#448) * feat: add AdsPost Component * feat: add TODO * feat: add use themeStyle from config * feat: align theme by component * fix: pr comments * fix: refactor Avatar V4 * feat: add TODO --- .../components/AmityLiveChatHeader/index.tsx | 7 +- .../AmityLiveChatHeader/styles.module.css | 5 ++ .../MessageBubbleContainer/index.tsx | 7 +- .../ChatContainer/ReplyMessagePlaceholder.tsx | 3 +- .../ChatContainer/styles.module.css | 2 + .../core/components/Avatar/Avatar.module.css | 90 +------------------ src/v4/core/components/Avatar/Avatar.tsx | 73 ++------------- src/v4/core/components/Avatar/index.ts | 2 +- .../components/SocialMentionItem/index.tsx | 6 +- .../SocialMentionItem/styles.module.css | 5 ++ src/v4/icons/Broadcast.tsx | 20 +++++ src/v4/icons/InfoCircle.tsx | 19 ++++ src/v4/icons/Star.tsx | 19 ++++ .../ReactionList/ReactionList.module.css | 5 ++ .../ReactionList/ReactionListPanel.tsx | 14 +-- .../AdsBadge/AdsBadge.module.css | 32 +++++++ .../AdsBadge/AdsBadge.stories.tsx | 10 +++ .../internal-components/AdsBadge/AdsBadge.tsx | 14 +++ .../Comment/UIComment.module.css | 2 + .../internal-components/Comment/UIComment.tsx | 5 +- .../CommentComposeBar.module.css | 4 +- .../CommentComposeBar/CommentComposeBar.tsx | 6 +- .../PostAds/PostAds.module.css | 72 +++++++++++++++ .../PostAds/PostAds.stories.tsx | 14 +++ .../internal-components/PostAds/PostAds.tsx | 73 +++++++++++++++ .../StoryPreview/StoryPreview.module.css | 5 ++ .../StoryPreview/StoryPreview.tsx | 4 +- 27 files changed, 345 insertions(+), 173 deletions(-) create mode 100644 src/v4/icons/Broadcast.tsx create mode 100644 src/v4/icons/InfoCircle.tsx create mode 100644 src/v4/icons/Star.tsx create mode 100644 src/v4/social/internal-components/AdsBadge/AdsBadge.module.css create mode 100644 src/v4/social/internal-components/AdsBadge/AdsBadge.stories.tsx create mode 100644 src/v4/social/internal-components/AdsBadge/AdsBadge.tsx create mode 100644 src/v4/social/internal-components/PostAds/PostAds.module.css create mode 100644 src/v4/social/internal-components/PostAds/PostAds.stories.tsx create mode 100644 src/v4/social/internal-components/PostAds/PostAds.tsx diff --git a/src/v4/chat/components/AmityLiveChatHeader/index.tsx b/src/v4/chat/components/AmityLiveChatHeader/index.tsx index 93e238217..0b48ce96d 100644 --- a/src/v4/chat/components/AmityLiveChatHeader/index.tsx +++ b/src/v4/chat/components/AmityLiveChatHeader/index.tsx @@ -8,7 +8,7 @@ import ConnectionSpinner from '~/v4/icons/ConnectionSpinner'; import { Typography } from '~/v4/core/components'; import styles from './styles.module.css'; import useChatInfo from '~/v4/chat/hooks/useChatInfo'; -import { Avatar, AVATAR_SIZE } from '~/v4/core/components/Avatar/Avatar'; +import { Avatar } from '~/v4/core/components/Avatar/Avatar'; import { useAmityComponent } from '~/v4/core/hooks/uikit/index'; interface AmityLiveChatHeaderProps { @@ -20,14 +20,15 @@ interface AmityLiveChatHeaderProps { export const AmityLiveChatHeader = ({ channel, pageId = '*' }: AmityLiveChatHeaderProps) => { const componentId = 'chat_header'; const { themeStyles } = useAmityComponent({ pageId, componentId }); - const { chatName, chatAvatar } = useChatInfo({ channel }); const { formatMessage } = useIntl(); const isOnline = useConnectionStates(); return ( <div className={styles.messageListHeader} style={themeStyles}> - <Avatar size={AVATAR_SIZE.MEDIUM} avatar={chatAvatar} defaultImage={<Chat />} /> + <div className={styles.avatar}> + <Avatar avatarUrl={chatAvatar} defaultImage={<Chat />} /> + </div> <div> <div className={styles.displayName}> <Typography.Title>{chatName || formatMessage({ id: 'loading' })}</Typography.Title> diff --git a/src/v4/chat/components/AmityLiveChatHeader/styles.module.css b/src/v4/chat/components/AmityLiveChatHeader/styles.module.css index 09580e17d..04f84bc4a 100644 --- a/src/v4/chat/components/AmityLiveChatHeader/styles.module.css +++ b/src/v4/chat/components/AmityLiveChatHeader/styles.module.css @@ -22,3 +22,8 @@ margin-right: var(--asc-spacing-xxs2); fill: var(--asc-color-base-default); } + +.avatar { + width: 3rem; + height: 3rem; +} diff --git a/src/v4/chat/components/LiveChatMessageContent/MessageBubbleContainer/index.tsx b/src/v4/chat/components/LiveChatMessageContent/MessageBubbleContainer/index.tsx index 4031d2b1f..275396b1e 100644 --- a/src/v4/chat/components/LiveChatMessageContent/MessageBubbleContainer/index.tsx +++ b/src/v4/chat/components/LiveChatMessageContent/MessageBubbleContainer/index.tsx @@ -2,7 +2,7 @@ import React from 'react'; import styles from './styles.module.css'; import User from '~/v4/icons/User'; import { Typography } from '~/v4/core/components'; -import { Avatar, AVATAR_SIZE } from '~/v4/core/components/Avatar'; +import { Avatar } from '~/v4/core/components/Avatar'; interface MessageBubbleContainerProps { avatarUrl?: string; @@ -17,8 +17,9 @@ const MessageBubbleContainer = ({ }: MessageBubbleContainerProps) => { return ( <div className={styles.messageItemContainer}> - <Avatar size={AVATAR_SIZE.SMALL} avatar={avatarUrl} defaultImage={<User />} /> - + <div className={styles.avatar}> + <Avatar avatarUrl={avatarUrl} defaultImage={<User />} /> + </div> <div> <div className={styles.userDisplayName}> {/* TODO: release 1.1 hide moderator badge, will be implemented in release 1.2 */} diff --git a/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ReplyMessagePlaceholder.tsx b/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ReplyMessagePlaceholder.tsx index af51ca5b6..65e8d5604 100644 --- a/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ReplyMessagePlaceholder.tsx +++ b/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ReplyMessagePlaceholder.tsx @@ -5,7 +5,6 @@ import { FormattedMessage } from 'react-intl'; import CloseIcon from '~/v4/icons/Close'; import { Avatar } from '~/v4/core/components'; import User from '~/v4/icons/User'; -import { AVATAR_SIZE } from '~/v4/core/components/Avatar/Avatar'; interface ReplyMessagePlaceholderProps { replyMessage: Amity.Message<'text'>; @@ -20,7 +19,7 @@ const ReplyMessagePlaceholder = ({ replyMessage, onDismiss }: ReplyMessagePlaceh return ( <div className={styles.replyPlaceholderContainer}> <div className={styles.replyAvatar}> - <Avatar avatar={profile.avatar?.fileUrl} size={AVATAR_SIZE.SMALL} defaultImage={<User />} /> + <Avatar avatarUrl={profile.avatar?.fileUrl} defaultImage={<User />} /> </div> <div className={styles.replyProfile}> <div className={styles.replyProfileName}> diff --git a/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/styles.module.css b/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/styles.module.css index ba002e639..d0692b235 100644 --- a/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/styles.module.css +++ b/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/styles.module.css @@ -64,6 +64,8 @@ .replyAvatar { flex: 0 0 auto; + width: 2rem; + height: 2rem; } .replyProfile { diff --git a/src/v4/core/components/Avatar/Avatar.module.css b/src/v4/core/components/Avatar/Avatar.module.css index c2e500469..f34cd59b2 100644 --- a/src/v4/core/components/Avatar/Avatar.module.css +++ b/src/v4/core/components/Avatar/Avatar.module.css @@ -1,100 +1,16 @@ .avatarContainer { - position: relative; - flex-shrink: 0; overflow: hidden; border-radius: 50%; - background-color: var(--asc-color-base-shade3); -} - -.avatarContainer.clickable:hover { - cursor: pointer; -} - -.avatarContainer.small { - width: 2rem; - height: 2rem; -} - -.avatarContainer.medium { - width: 3rem; - height: 3rem; -} - -.avatarContainer.large { - width: 4rem; - height: 4rem; -} - -.skeleton { width: 100%; height: 100%; - display: block; - border-radius: 50%; - background-color: var(--asc-color-base-shade4); -} - -.skeleton-small { - width: 2rem; - height: 2rem; } -.skeleton-medium { - width: 3rem; - height: 3rem; -} - -.skeleton-large { - width: 4rem; - height: 4rem; +.avatarContainer[data-clickable='true']:hover { + cursor: pointer; } -.avatarOverlay { - position: absolute; - z-index: 2; - opacity: 0.5; - background-color: #000; +.avatarImage { width: 100%; height: 100%; -} - -.avatarOverlay-small { - width: 2rem; - height: 2rem; -} - -.avatarOverlay-medium { - width: 3rem; - height: 3rem; -} - -.avatarOverlay-large { - width: 4rem; - height: 4rem; -} - -.img { - height: 100%; - width: 100%; object-fit: cover; - opacity: 0; - transition: opacity 0.3s; -} - -.img-small { - height: 2rem; - width: 2rem; -} - -.img-medium { - height: 3rem; - width: 3rem; -} - -.img-large { - height: 4rem; - width: 4rem; -} - -.avatarContainer.visible .img { - opacity: 1; } diff --git a/src/v4/core/components/Avatar/Avatar.tsx b/src/v4/core/components/Avatar/Avatar.tsx index 8a324f3dd..6c49890aa 100644 --- a/src/v4/core/components/Avatar/Avatar.tsx +++ b/src/v4/core/components/Avatar/Avatar.tsx @@ -1,75 +1,20 @@ -import React, { useState, useCallback } from 'react'; -import clsx from 'clsx'; +import React from 'react'; import styles from './Avatar.module.css'; -export const enum AVATAR_SIZE { - SMALL = 'small', - MEDIUM = 'medium', - LARGE = 'large', -} - export interface AvatarProps { - className?: string; - avatar?: string | null; - showOverlay?: boolean; + avatarUrl?: string | null; + defaultImage: React.ReactNode; onClick?: () => void; - loading?: boolean; - defaultImage?: React.ReactNode; - size?: AVATAR_SIZE; } -export const Avatar = ({ - className = '', - avatar = null, - showOverlay, - onClick, - loading, - size = AVATAR_SIZE.MEDIUM, - defaultImage, - ...props -}: AvatarProps) => { - const [visible, setVisible] = useState(false); - const onLoad = useCallback(() => setVisible(true), []); - const onError = useCallback(() => setVisible(false), []); - +export const Avatar = ({ avatarUrl, defaultImage, onClick }: AvatarProps) => { return ( - <div - className={clsx( - styles.avatarContainer, - className, - visible && styles.visible, - onClick && styles.clickable, - styles[size], - )} - onClick={onClick} - {...props} - > - {loading ? ( - <div className={clsx(styles.skeleton, styles[`skeleton-${size}`])} /> - ) : avatar ? ( - showOverlay ? ( - <div className={clsx(styles.avatarOverlay, styles[`avatarOverlay-${size}`])}> - <img - className={clsx(styles.img, styles[`img-${size}`])} - data-img={size} - src={avatar} - onError={onError} - onLoad={onLoad} - alt="Avatar" - /> - </div> - ) : ( - <img - className={clsx(styles.img)} - data-img={size} - src={avatar} - onError={onError} - onLoad={onLoad} - alt="Avatar" - /> - ) + <div className={styles.avatarContainer} data-clickable={!!onClick} onClick={onClick}> + {avatarUrl ? ( + // TODO: add handler if cannot fetch the url + <img className={styles.avatarImage} src={avatarUrl} alt="Avatar" /> ) : ( - defaultImage ?? null + defaultImage )} </div> ); diff --git a/src/v4/core/components/Avatar/index.ts b/src/v4/core/components/Avatar/index.ts index 0fb0a0baa..d3fb6dfa7 100644 --- a/src/v4/core/components/Avatar/index.ts +++ b/src/v4/core/components/Avatar/index.ts @@ -1 +1 @@ -export { Avatar, AVATAR_SIZE } from './Avatar'; +export { Avatar } from './Avatar'; diff --git a/src/v4/core/components/SocialMentionItem/index.tsx b/src/v4/core/components/SocialMentionItem/index.tsx index dc818cbc9..76b8f9778 100644 --- a/src/v4/core/components/SocialMentionItem/index.tsx +++ b/src/v4/core/components/SocialMentionItem/index.tsx @@ -8,7 +8,7 @@ import styles from './styles.module.css'; import { MentionIcon } from '~/icons'; import { FormattedMessage } from 'react-intl'; import { Typography } from '../index'; -import { Avatar, AVATAR_SIZE } from '~/v4/core/components/Avatar'; +import { Avatar } from '~/v4/core/components/Avatar'; import User from '~/v4/icons/User'; interface SocialMentionItemProps { @@ -57,7 +57,9 @@ const UserMentionItem = ({ className={clsx(styles.mentionItem, user?.isGlobalBanned && 'isBanned')} onMouseEnter={(e) => onMouseEnter(e, user?.isGlobalBanned)} > - <Avatar size={AVATAR_SIZE.SMALL} avatar={avatarFileUrl} defaultImage={<User />} /> + <div className={styles.avatar}> + <Avatar avatarUrl={avatarFileUrl} defaultImage={<User />} /> + </div> <div className={styles.userDisplayName}> <Typography.Body>{user?.displayName}</Typography.Body> </div> diff --git a/src/v4/core/components/SocialMentionItem/styles.module.css b/src/v4/core/components/SocialMentionItem/styles.module.css index e04ffe2a8..01dcbaa6d 100644 --- a/src/v4/core/components/SocialMentionItem/styles.module.css +++ b/src/v4/core/components/SocialMentionItem/styles.module.css @@ -37,3 +37,8 @@ align-items: center; } } + +.avatar { + width: 2rem; + height: 2rem; +} diff --git a/src/v4/icons/Broadcast.tsx b/src/v4/icons/Broadcast.tsx new file mode 100644 index 000000000..e2c0bbab8 --- /dev/null +++ b/src/v4/icons/Broadcast.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +const Broadcast = (props: React.SVGProps<SVGSVGElement>) => ( + <svg + width="100%" + height="100%" + viewBox="0 0 64 64" + fill="none" + xmlns="http://www.w3.org/2000/svg" + {...props} + > + <rect width="64" height="64" rx="32" fill="#D9E5FC" /> + <path + d="M20 28.5358C20.5156 28.5358 20.9375 28.8639 21.2188 29.2858L41 23.2858V41.2858L34.8125 39.4576C34.2969 41.3795 32.5625 42.7858 30.5 42.7858C28.0156 42.7858 26 40.7701 26 38.2858C26 37.817 26.0938 37.3014 26.2344 36.8795L21.2188 35.3326C20.9375 35.7545 20.5156 36.0358 20 36.0358C19.1562 36.0358 18.5 35.3795 18.5 34.5358V30.0358C18.5 29.2389 19.1562 28.5358 20 28.5358ZM28.3906 37.4889C28.2969 37.7701 28.25 38.0045 28.25 38.2858C28.25 39.5514 29.2344 40.5358 30.5 40.5358C31.5312 40.5358 32.4219 39.7858 32.6562 38.8014L28.3906 37.4889ZM44.75 21.7858C45.125 21.7858 45.5 22.1608 45.5 22.5358V42.0358C45.5 42.4576 45.125 42.7858 44.75 42.7858H43.25C42.8281 42.7858 42.5 42.4576 42.5 42.0358V22.5358C42.5 22.1608 42.8281 21.7858 43.25 21.7858H44.75Z" + fill="white" + /> + </svg> +); + +export default Broadcast; diff --git a/src/v4/icons/InfoCircle.tsx b/src/v4/icons/InfoCircle.tsx new file mode 100644 index 000000000..26bc3ad35 --- /dev/null +++ b/src/v4/icons/InfoCircle.tsx @@ -0,0 +1,19 @@ +import React from 'react'; + +const InfoCircle = (props: React.SVGProps<SVGSVGElement>) => ( + <svg + width="16" + height="17" + viewBox="0 0 16 17" + fill="none" + xmlns="http://www.w3.org/2000/svg" + {...props} + > + <path + d="M8 2.25006C4.25 2.25006 1.25 5.28131 1.25 9.00006C1.25 12.7501 4.25 15.7501 8 15.7501C11.7188 15.7501 14.75 12.7501 14.75 9.00006C14.75 5.28131 11.6875 2.25006 8 2.25006ZM8 1.25006C12.25 1.25006 15.75 4.75006 15.75 9.00006C15.75 13.2813 12.25 16.7501 8 16.7501C3.71875 16.7501 0.25 13.2813 0.25 9.00006C0.25 4.75006 3.71875 1.25006 8 1.25006ZM6.875 12.0001H7.25V8.25006H6.875C6.65625 8.25006 6.5 8.09381 6.5 7.87506V7.62506C6.5 7.43756 6.65625 7.25006 6.875 7.25006H8.375C8.5625 7.25006 8.75 7.43756 8.75 7.62506V12.0001H9.125C9.3125 12.0001 9.5 12.1876 9.5 12.3751V12.6251C9.5 12.8438 9.3125 13.0001 9.125 13.0001H6.875C6.65625 13.0001 6.5 12.8438 6.5 12.6251V12.3751C6.5 12.1876 6.65625 12.0001 6.875 12.0001ZM8 4.50006C8.53125 4.50006 9 4.96881 9 5.50006C9 6.06256 8.53125 6.50006 8 6.50006C7.4375 6.50006 7 6.06256 7 5.50006C7 4.96881 7.4375 4.50006 8 4.50006Z" + fill="#A5A9B5" + /> + </svg> +); + +export default InfoCircle; diff --git a/src/v4/icons/Star.tsx b/src/v4/icons/Star.tsx new file mode 100644 index 000000000..e39303b4d --- /dev/null +++ b/src/v4/icons/Star.tsx @@ -0,0 +1,19 @@ +import React from 'react'; + +const Star = ({ color = 'white', ...props }: React.SVGProps<SVGSVGElement>) => ( + <svg + width="12" + height="12" + viewBox="0 0 12 12" + fill="none" + xmlns="http://www.w3.org/2000/svg" + {...props} + > + <path + d="M5.54688 1.78125C5.73438 1.40625 6.26562 1.42188 6.4375 1.78125L7.46875 3.85938L9.75 4.1875C10.1562 4.25 10.3125 4.75 10.0156 5.04688L8.375 6.65625L8.76562 8.92188C8.82812 9.32812 8.39062 9.64062 8.03125 9.45312L6 8.375L3.95312 9.45312C3.59375 9.64062 3.15625 9.32812 3.21875 8.92188L3.60938 6.65625L1.96875 5.04688C1.67188 4.75 1.82812 4.25 2.23438 4.1875L4.53125 3.85938L5.54688 1.78125Z" + fill={color} + /> + </svg> +); + +export default Star; diff --git a/src/v4/social/components/ReactionList/ReactionList.module.css b/src/v4/social/components/ReactionList/ReactionList.module.css index b1f1f9d7f..808dd8a36 100644 --- a/src/v4/social/components/ReactionList/ReactionList.module.css +++ b/src/v4/social/components/ReactionList/ReactionList.module.css @@ -154,3 +154,8 @@ .reactionPanel { height: 100%; } + +.avatar { + width: 2rem; + height: 2rem; +} diff --git a/src/v4/social/components/ReactionList/ReactionListPanel.tsx b/src/v4/social/components/ReactionList/ReactionListPanel.tsx index c3ff78ee2..c40a85874 100644 --- a/src/v4/social/components/ReactionList/ReactionListPanel.tsx +++ b/src/v4/social/components/ReactionList/ReactionListPanel.tsx @@ -4,7 +4,7 @@ import FallbackReaction from '~/v4/icons/FallbackReaction'; import { ReactionIcon } from '~/v4/social/components/ReactionList/ReactionIcon'; import { useCustomReaction } from '~/v4/core/providers/CustomReactionProvider'; import InfiniteScroll from 'react-infinite-scroll-component'; -import { AVATAR_SIZE } from '~/v4/core/components/Avatar/Avatar'; +import User from '~/v4/icons/User'; import useSDK from '~/v4/core/hooks/useSDK'; import styles from './ReactionList.module.css'; @@ -51,11 +51,13 @@ export const ReactionListPanel = ({ <div className={styles.userItem}> <div className={styles.userDetailsContainer}> <div className={styles.userDetailsProfile}> - <Avatar - data-qa-anchor="user_avatar_view" - size={AVATAR_SIZE.SMALL} - avatar={reaction.user?.avatar?.fileUrl} - /> + <div className={styles.avatar}> + <Avatar + data-qa-anchor="user_avatar_view" + avatarUrl={reaction.user?.avatar?.fileUrl} + defaultImage={<User />} + /> + </div> <Typography.BodyBold data-qa-anchor="user_display_name"> {reaction.user?.displayName} {currentUserId === reaction.user?.userId && showReactionUserDetails && ( diff --git a/src/v4/social/internal-components/AdsBadge/AdsBadge.module.css b/src/v4/social/internal-components/AdsBadge/AdsBadge.module.css new file mode 100644 index 000000000..40c7d20ae --- /dev/null +++ b/src/v4/social/internal-components/AdsBadge/AdsBadge.module.css @@ -0,0 +1,32 @@ +.badge { + position: relative; + border-radius: var(--asc-border-radius-full); + padding: var(--asc-spacing-none) var(--asc-spacing-xxs3); +} + +.badge::before { + content: ''; + position: absolute; + inset: 0; + background-color: var(--asc-color-base-shade1); + opacity: 0.5; + border-radius: inherit; +} + +.badge__icon { + width: 0.75rem; + height: 0.75rem; +} + +.badge__text { + font-size: var(--asc-text-font-size-xs); + line-height: var(--asc-line-height-sm); + color: var(--asc-color-white); +} + +.badge__child { + position: relative; + display: flex; + gap: var(--asc-spacing-xxs1); + align-items: center; +} diff --git a/src/v4/social/internal-components/AdsBadge/AdsBadge.stories.tsx b/src/v4/social/internal-components/AdsBadge/AdsBadge.stories.tsx new file mode 100644 index 000000000..79a2debc1 --- /dev/null +++ b/src/v4/social/internal-components/AdsBadge/AdsBadge.stories.tsx @@ -0,0 +1,10 @@ +import React, { Component } from 'react'; +import { AdsBadge } from './AdsBadge'; + +export default { + title: 'v4-social/internal-components/AdsBadge', +}; + +export const AdsBadgeStory = { + render: () => <AdsBadge />, +}; diff --git a/src/v4/social/internal-components/AdsBadge/AdsBadge.tsx b/src/v4/social/internal-components/AdsBadge/AdsBadge.tsx new file mode 100644 index 000000000..01f81594b --- /dev/null +++ b/src/v4/social/internal-components/AdsBadge/AdsBadge.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import Star from '~/v4/icons/Star'; +import styles from './AdsBadge.module.css'; + +export const AdsBadge = () => { + return ( + <div className={styles.badge}> + <div className={styles.badge__child}> + <Star className={styles.badge__icon} /> + <div className={styles.badge__text}>Premium Sponser</div> + </div> + </div> + ); +}; diff --git a/src/v4/social/internal-components/Comment/UIComment.module.css b/src/v4/social/internal-components/Comment/UIComment.module.css index 84035a6f7..78786861c 100644 --- a/src/v4/social/internal-components/Comment/UIComment.module.css +++ b/src/v4/social/internal-components/Comment/UIComment.module.css @@ -1,5 +1,7 @@ .avatar { margin-right: var(--asc-spacing-s1); + width: 2rem; + height: 2rem; } .container { diff --git a/src/v4/social/internal-components/Comment/UIComment.tsx b/src/v4/social/internal-components/Comment/UIComment.tsx index 09a421751..815e2c703 100644 --- a/src/v4/social/internal-components/Comment/UIComment.tsx +++ b/src/v4/social/internal-components/Comment/UIComment.tsx @@ -19,6 +19,7 @@ import { Avatar, Typography } from '~/v4/core/components'; import Button from '~/v4/core/components/Button/Button'; import clsx from 'clsx'; import { ModeratorBadge } from '../../elements/ModeratorBadge/ModeratorBadge'; +import User from '~/v4/icons/User'; interface StyledCommentProps { commentId?: string; @@ -110,7 +111,9 @@ const UIComment = ({ }: StyledCommentProps) => { return ( <div className={styles.container}> - <Avatar size="small" avatar={authorAvatar || UserImage} /> + <div className={styles.avatar}> + <Avatar avatarUrl={authorAvatar || UserImage} defaultImage={<User />} /> + </div> <div className={styles.content}> <div className={styles.commentHeader}> <Typography.CaptionBold>{authorName}</Typography.CaptionBold> diff --git a/src/v4/social/internal-components/CommentComposeBar/CommentComposeBar.module.css b/src/v4/social/internal-components/CommentComposeBar/CommentComposeBar.module.css index 7e4bd4904..37f2e2f6e 100644 --- a/src/v4/social/internal-components/CommentComposeBar/CommentComposeBar.module.css +++ b/src/v4/social/internal-components/CommentComposeBar/CommentComposeBar.module.css @@ -1,5 +1,7 @@ .avatar { - margin-right: 8px; + margin-right: var(--asc-spacing-s1); + width: 2rem; + height: 2rem; } .replyContainer { diff --git a/src/v4/social/internal-components/CommentComposeBar/CommentComposeBar.tsx b/src/v4/social/internal-components/CommentComposeBar/CommentComposeBar.tsx index efbb07195..2803fab43 100644 --- a/src/v4/social/internal-components/CommentComposeBar/CommentComposeBar.tsx +++ b/src/v4/social/internal-components/CommentComposeBar/CommentComposeBar.tsx @@ -8,7 +8,7 @@ import useSDK from '~/core/hooks/useSDK'; import { FormattedMessage, useIntl } from 'react-intl'; import styles from './CommentComposeBar.module.css'; -import { backgroundImage as UserImage } from '~/icons/User'; +import User, { backgroundImage as UserImage } from '~/icons/User'; import useImage from '~/core/hooks/useImage'; import { useConfirmContext } from '~/core/providers/ConfirmProvider'; import InputText from '~/v4/core/components/InputText'; @@ -85,7 +85,9 @@ export const CommentComposeBar = ({ return ( <div className={styles.commentComposeBarContainer}> - <Avatar size="small" avatar={avatarFileUrl} backgroundImage={UserImage} /> + <div className={styles.avatar}> + <Avatar avatarUrl={avatarFileUrl} defaultImage={<User />} /> + </div> <InputText ref={commentInputRef} data-qa-anchor="comment-compose-bar-textarea" diff --git a/src/v4/social/internal-components/PostAds/PostAds.module.css b/src/v4/social/internal-components/PostAds/PostAds.module.css new file mode 100644 index 000000000..7ab22bb1c --- /dev/null +++ b/src/v4/social/internal-components/PostAds/PostAds.module.css @@ -0,0 +1,72 @@ +.container { + position: relative; + background-color: var(asc-color-base-background); + border-radius: var(--asc-border-radius-md); + border: 1px solid var(--asc-color-base-shade4); + overflow: hidden; +} + +.innerContainer { + width: calc(100% - (var(--asc-spacing-m1) * 2)); + margin: var(--asc-spacing-xxs2) auto; +} + +.header { + display: flex; + flex-direction: row; + align-items: center; + gap: var(--asc-spacing-s2); + padding: var(--asc-spacing-s1) 0; +} + +.header__title { + color: var(--asc-color-base-default); +} + +.header__avatar { + width: 3rem; + height: 3rem; +} + +.content__text { + color: var(--asc-color-base-default); + margin: var(--asc-spacing-s1) 0; +} + +.content__image { + margin: var(--asc-spacing-s1) 0; + width: 100%; + height: 100%; + border-radius: var(--asc-border-radius-md); +} + +.footer { + background-color: var(--asc-color-base-shade4); + padding: var(--asc-spacing-m1) var(--asc-spacing-s2); + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + margin-top: var(--asc-spacing-s1); +} + +.footer__content__title { + color: var(--asc-color-base-shade1); +} + +.footer__content__description { + color: var(--asc-color-base-default); +} + +.footer__content__button { + flex-shrink: 0; +} + +.infoIcon { + position: absolute; + width: 1rem; + height: 1rem; + fill: var(--asc-color-base-shade3); + right: 0.25rem; + top: 0.25rem; +} diff --git a/src/v4/social/internal-components/PostAds/PostAds.stories.tsx b/src/v4/social/internal-components/PostAds/PostAds.stories.tsx new file mode 100644 index 000000000..90918ee65 --- /dev/null +++ b/src/v4/social/internal-components/PostAds/PostAds.stories.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { PostAds } from './PostAds'; + +export default { + title: 'v4-social/internal-components/PostAds', +}; + +export const PostAdsStory = { + render: () => ( + <div style={{ width: '80%', margin: 'auto' }}> + <PostAds /> + </div> + ), +}; diff --git a/src/v4/social/internal-components/PostAds/PostAds.tsx b/src/v4/social/internal-components/PostAds/PostAds.tsx new file mode 100644 index 000000000..f37f0090b --- /dev/null +++ b/src/v4/social/internal-components/PostAds/PostAds.tsx @@ -0,0 +1,73 @@ +import React from 'react'; +import styles from './PostAds.module.css'; +import { Avatar, Button, Typography } from '~/v4/core/components'; +import { AdsBadge } from '../AdsBadge/AdsBadge'; +import Broadcast from '~/v4/icons/Broadcast'; +import InfoCircle from '~/v4/icons/InfoCircle'; +import { useAmityComponent } from '~/v4/core/hooks/uikit/index'; + +export const PostAds = ({ pageId = '*' }: { pageId?: string }) => { + // TODO: add fetching the ads + // const ads = useAds(); + // const avatarUrl = useFile(avatarFileId); + const componentId = 'post_content'; + const { themeStyles } = useAmityComponent({ + pageId, + componentId, + }); + + return ( + // TODO: confirm the themeStyle is used from page level or component level + <div className={styles.container} style={themeStyles}> + <div className={styles.innerContainer}> + <div className={styles.header}> + <div className={styles.header__avatar}> + <Avatar + avatarUrl={ + 'https://s3-alpha-sig.figma.com/img/eef3/8e4e/780e841c018e87aafef8a9b6b6652024?Expires=1720396800&Key-Pair-Id=APKAQ4GOSFWCVNEHN3O4&Signature=kibPgi17krUs5Hm8fbQUs2K0BtGn6OYMm66m8eV70qjgUYZkY0I76K5KJqRA~gu-Zyi2Sex~KCizs5hQGBhBnb9ANNrUxfAKlXkS1taaZKt4fhxEgOWrHo8Hj6O7R5ZPuj7OOMHcd1fcE~1m9uLQrQGXsq-s8fwdCzLPTHBt~rNKq2sW4M4RoAiljCQNS5sITEe~e9Mu88PFj-9ygzlS4xR1KjNcSbki0tSEhMvY5Ma5R9si6DsOEFZv3s-X1YK-tsszAhe1elNSht7PxjfoygnC3Mtqye~S72Rj~tQbGGaNZwLqYgQwWr1EnJIk7v0JZctTZoVHtkOSAA4kZ5ASfg__' + } + defaultImage={<Broadcast />} + /> + </div> + <div> + <Typography.BodyBold className={styles.header__title}>{'Amity'}</Typography.BodyBold> + <AdsBadge /> + </div> + </div> + <div> + <Typography.Body className={styles.content__text}> + {/* TODO: change to use ads content */} + { + 'Social features are proven to drive engagement, boost retention, and increase revenue. Discover how you can grow your product with Amity Social Cloud, no matter which industry you’re in. 🥰 📱' + } + </Typography.Body> + + <img + className={styles.content__image} + // TODO: change to use ads content + src={ + 'https://s3-alpha-sig.figma.com/img/0074/9e2f/62f75af189ae259254a5a9c895b0a720?Expires=1720396800&Key-Pair-Id=APKAQ4GOSFWCVNEHN3O4&Signature=if15s1aRBec0sxOI1LWXKauQgN75CeJzfpalOXd-OxggaTyJHKmPJwFVldnz~RB~ICaboLN9xKtbrPnAZY~GodOnxyOGvmAHRbmtE39Cjw-QMUlPbZ-C1YB72fwA9eDvpDkI-1pbvPF2Vh~TPT8x7Li8MVk8ifeyAt1ddRbyY546wEQibRf9xK6ilpdK6UJWkOW~0Paj~8OwdGqRgm8suXDSpmmnDcbpTUE5ntQb4ibOLQq6Cw9R1fVitGaIrfyDaRjH7qD~GiOBBVfNqoAX4LdfuavKpyDJrWTKWTODzR20VlhbOjsHQxh7ZI4g92IkxvPs9pUQST-xzmrzZ8C5vA__' + } + /> + </div> + </div> + <InfoCircle className={styles.infoIcon} /> + + <div className={styles.footer}> + <div> + {/* TODO: change to use ads content */} + <Typography.Body className={styles.footer__content__title}> + {'Social media'} + </Typography.Body> + <Typography.BodyBold className={styles.footer__content__description}> + {'Powering the social networks of tomorrow!'} + </Typography.BodyBold> + </div> + {/* TODO: change to use ads content */} + <Button className={styles.footer__content__button} variant="primary"> + {'Talk to sales'} + </Button> + </div> + </div> + ); +}; diff --git a/src/v4/social/internal-components/StoryPreview/StoryPreview.module.css b/src/v4/social/internal-components/StoryPreview/StoryPreview.module.css index c290f3fd0..ad5440977 100644 --- a/src/v4/social/internal-components/StoryPreview/StoryPreview.module.css +++ b/src/v4/social/internal-components/StoryPreview/StoryPreview.module.css @@ -79,3 +79,8 @@ height: 100%; object-fit: cover; } + +.avatar { + width: 2.5rem; + height: 2.5rem; +} diff --git a/src/v4/social/internal-components/StoryPreview/StoryPreview.tsx b/src/v4/social/internal-components/StoryPreview/StoryPreview.tsx index f7b93ad95..772922cbd 100644 --- a/src/v4/social/internal-components/StoryPreview/StoryPreview.tsx +++ b/src/v4/social/internal-components/StoryPreview/StoryPreview.tsx @@ -109,7 +109,9 @@ export const StoryPreview: React.FC<StoryPreviewProps> = ({ <div className={styles.progressFill} style={{ width: `${progress}%` }} /> </div> <div className={styles.userInfo}> - <Avatar avatar={avatar} defaultImage={<Community />} /> + <div className={styles.avatar}> + <Avatar avatarUrl={avatar} defaultImage={<Community />} /> + </div> <Typography.BodyBold className={styles.storyPreviewTitle}> <span className={styles.nameContainer}> {title} {isOfficial && <Verified fill="white" />} From 1ab826a26641707d61da6559bc839b68ebffb076 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Thu, 27 Jun 2024 21:51:49 +0700 Subject: [PATCH 163/300] fix: delete first multiple segment story should navigate to next story (#441) --- .../StoryViewer/Renderers/Image.tsx | 1 + .../StoryViewer/Renderers/Video.tsx | 5 +-- .../pages/StoryPage/CommunityFeedStory.tsx | 36 ++++++++++------- .../pages/StoryPage/GlobalFeedStory.tsx | 39 ++++++++++--------- 4 files changed, 47 insertions(+), 34 deletions(-) diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index 8ff838b96..30ac0d589 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -237,6 +237,7 @@ export const renderer: CustomRenderer = ({ className={styles.actionButton} onClick={() => { bottomSheetAction.action(); + closeBottomSheet(); }} variant="secondary" > diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx index 4b5e8fbf4..8a4b32ded 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx @@ -269,13 +269,12 @@ export const renderer: CustomRenderer = ({ className={clsx(rendererStyles.actionButton)} onClick={() => { bottomSheetAction.action(); + closeBottomSheet(); }} variant="secondary" > {bottomSheetAction?.icon && bottomSheetAction.icon} - <Typography.BodyBold> - {formatMessage({ id: bottomSheetAction.name })} - </Typography.BodyBold> + <Typography.BodyBold>{bottomSheetAction.name}</Typography.BodyBold> </Button> ))} </BottomSheet> diff --git a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx index b7cfe7a1b..620ef42ed 100644 --- a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx @@ -2,7 +2,6 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import useStories from '~/social/hooks/useStories'; import useSDK from '~/core/hooks/useSDK'; -import { useIntl } from 'react-intl'; import { FinalColor } from 'extract-colors/lib/types/Color'; import { StoryRepository } from '@amityco/ts-sdk'; import { CreateNewStoryButton } from '~/v4/social/elements/CreateNewStoryButton'; @@ -25,10 +24,11 @@ import clsx from 'clsx'; import { ArrowLeftButton } from '~/v4/social/elements/ArrowLeftButton'; import { ArrowRightButton } from '~/v4/social/elements/ArrowRightButton'; -import styles from './StoryPage.module.css'; import { useAmityPage } from '~/v4/core/hooks/uikit'; import { FileTrigger } from 'react-aria-components'; +import styles from './StoryPage.module.css'; + interface CommunityFeedStoryProps { pageId?: string; communityId: string; @@ -98,8 +98,6 @@ export const CommunityFeedStory = ({ const { client, currentUserId } = useSDK(); - const { formatMessage } = useIntl(); - const [currentIndex, setCurrentIndex] = useState(0); const { file, setFile } = useStoryContext(); const [colors, setColors] = useState<FinalColor[]>([]); @@ -108,17 +106,29 @@ export const CommunityFeedStory = ({ const isModerator = checkStoryPermission(client, communityId); const confirmDeleteStory = (storyId: string) => { + const isLastStory = currentIndex === stories.length - 1; confirm({ pageId, - title: formatMessage({ id: 'storyViewer.action.confirmModal.title' }), - content: formatMessage({ id: 'storyViewer.action.confirmModal.content' }), - okText: formatMessage({ id: 'delete' }), + title: 'Delete this story?', + content: + 'This story will be permanently deleted. You’ll no longer to see and find this story.', + okText: 'Delete', onOk: async () => { await StoryRepository.softDeleteStory(storyId); notification.success({ - content: formatMessage({ id: 'storyViewer.notification.deleted' }), + content: 'Story deleted', }); - if (stories.length === 1) onClose(communityId); + if (stories.length === 1) { + // If it's the only story, close the ViewStory screen + onClose(communityId); + } else if (isLastStory) { + // If it's the last story, move to the previous one + setCurrentIndex((prevIndex) => prevIndex - 1); + } else { + // For any other case (including first story), stay on the same index + // The next story will automatically take its place + setCurrentIndex((prevIndex) => prevIndex); + } }, }); }; @@ -149,7 +159,7 @@ export const CommunityFeedStory = ({ ); if (imageData) { notification.success({ - content: formatMessage({ id: 'storyViewer.notification.success' }), + content: 'Successfully shared story', }); } } else { @@ -163,18 +173,18 @@ export const CommunityFeedStory = ({ ); if (videoData) { notification.success({ - content: formatMessage({ id: 'storyViewer.notification.success' }), + content: 'Successfully shared story', }); } } } } catch (error) { notification.error({ - content: formatMessage({ id: 'storyViewer.notification.error' }), + content: 'Failed to share story', }); } }, - [currentUserId, formatMessage, notification, setFile], + [currentUserId, notification, setFile], ); const discardStory = () => { diff --git a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx index 5f55a4a11..d9acd1a3e 100644 --- a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx @@ -1,5 +1,4 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { useIntl } from 'react-intl'; import { FinalColor } from 'extract-colors/lib/types/Color'; import { StoryRepository } from '@amityco/ts-sdk'; import { isNonNullable } from '~/v4/helpers/utils'; @@ -21,10 +20,10 @@ import { } from '~/v4/social/internal-components/StoryViewer/Renderers/types'; import { TrashIcon } from '~/v4/social/icons'; import { CreateNewStoryButton } from '~/v4/social/elements/CreateNewStoryButton'; +import { useAmityPage } from '~/v4/core/hooks/uikit'; +import { FileTrigger } from 'react-aria-components'; import styles from './StoryPage.module.css'; -import { useAmityPage } from '~/v4/core/hooks/uikit/index'; -import { FileTrigger } from 'react-aria-components'; const DURATION = 5000; @@ -59,7 +58,6 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ const { accessibilityId } = useAmityPage({ pageId }); const { confirm } = useConfirmContext(); const notification = useNotifications(); - const { formatMessage } = useIntl(); const { client, currentUserId } = useSDK(); const { file, setFile } = useStoryContext(); @@ -98,23 +96,28 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ }; const confirmDeleteStory = (storyId: string) => { - const isLastStory = currentIndex === 0; + const isLastStory = currentIndex === stories.length - 1; confirm({ pageId, - title: formatMessage({ id: 'storyViewer.action.confirmModal.title' }), - content: formatMessage({ id: 'storyViewer.action.confirmModal.content' }), - okText: formatMessage({ id: 'delete' }), + title: 'Delete this story?', + content: + 'This story will be permanently deleted. You’ll no longer to see and find this story.', + okText: 'Delete', onOk: async () => { - previousStory(); - if (isLastStory) onChangePage?.(); await StoryRepository.softDeleteStory(storyId); notification.success({ - content: formatMessage({ id: 'storyViewer.notification.deleted' }), + content: 'Story deleted', }); - if (isLastStory && stories.length > 1) { - setCurrentIndex((prevIndex) => prevIndex - 1); - } else if (stories.length === 1) { + if (stories.length === 1) { + // If it's the only story, close the ViewStory screen onChangePage?.(); + } else if (isLastStory) { + // If it's the last story, move to the previous one + setCurrentIndex((prevIndex) => prevIndex - 1); + } else { + // For any other case (including first story), stay on the same index + // The next story will automatically take its place + setCurrentIndex((prevIndex) => prevIndex); } }, }); @@ -146,7 +149,7 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ ); if (imageData) { notification.success({ - content: formatMessage({ id: 'storyViewer.notification.success' }), + content: 'Successfully shared story', }); } } else { @@ -160,18 +163,18 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ ); if (videoData) { notification.success({ - content: formatMessage({ id: 'storyViewer.notification.success' }), + content: 'Successfully shared story', }); } } } } catch (error) { notification.error({ - content: formatMessage({ id: 'storyViewer.notification.error' }), + content: 'Failed to share story', }); } }, - [currentUserId, formatMessage, notification, setFile], + [currentUserId, notification, setFile], ); const discardStory = () => { From dffd0af0eb10a98176c2d1029574d024bca09a62 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Thu, 27 Jun 2024 21:56:52 +0700 Subject: [PATCH 164/300] fix: ASC-20694 - wrong notification content (#442) * fix: delete first multiple segment story should navigate to next story * fix: wrong notification text content --- .../StoryViewer/Renderers/Wrappers/Footer/index.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx index c7ceb79eb..8968c5869 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx @@ -6,12 +6,12 @@ import { LIKE_REACTION_KEY } from '~/constants'; import Spinner from '~/social/components/Spinner'; import { useNotifications } from '~/v4/core/providers/NotificationProvider'; -import styles from './Footer.module.css'; -import clsx from 'clsx'; import { StoryCommentButton } from '~/v4/social/elements/StoryCommentButton/StoryCommentButton'; import { StoryReactionButton } from '~/v4/social/elements/StoryReactionButton/StoryReactionButton'; import { StoryImpressionButton } from '~/v4/social/elements/StoryImpressionButton/StoryImpressionButton'; +import styles from './Footer.module.css'; + const Footer: React.FC< React.PropsWithChildren<{ storyId: string; @@ -42,8 +42,8 @@ const Footer: React.FC< const handleClickReaction = async () => { try { if (!isMember) { - notification.show({ - content: 'You need to be a member to like this story', + notification.info({ + content: 'Join community to interact with all stories', }); return; } From 185eaefd50f4480c6e9cb0295bcbcd4a0960813b Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Thu, 27 Jun 2024 22:08:36 +0700 Subject: [PATCH 165/300] fix: ASC-22312 - comment moderator badge condition (#443) * fix: delete first multiple segment story should navigate to next story * fix: wrong notification text content * fix: comment author * fix: remove isBanned prop * fix: remove console.log --- .../internal-components/Comment/UIComment.tsx | 4 +- .../internal-components/Comment/index.tsx | 41 ++++++++----------- 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/src/v4/social/internal-components/Comment/UIComment.tsx b/src/v4/social/internal-components/Comment/UIComment.tsx index 815e2c703..1589fc716 100644 --- a/src/v4/social/internal-components/Comment/UIComment.tsx +++ b/src/v4/social/internal-components/Comment/UIComment.tsx @@ -13,7 +13,6 @@ import { EllipsisH, FireIcon, HeartIcon, LikedIcon } from '~/icons'; import millify from 'millify'; import { FIRE_REACTION_KEY, LIKE_REACTION_KEY, LOVE_REACTION_KEY } from '~/constants'; -import styles from './UIComment.module.css'; import InputText from '~/v4/core/components/InputText'; import { Avatar, Typography } from '~/v4/core/components'; import Button from '~/v4/core/components/Button/Button'; @@ -21,6 +20,8 @@ import clsx from 'clsx'; import { ModeratorBadge } from '../../elements/ModeratorBadge/ModeratorBadge'; import User from '~/v4/icons/User'; +import styles from './UIComment.module.css'; + interface StyledCommentProps { commentId?: string; authorName?: string; @@ -99,7 +100,6 @@ const UIComment = ({ onChange, queryMentionees, isMember = false, - isBanned, isLiked, mentionees, options, diff --git a/src/v4/social/internal-components/Comment/index.tsx b/src/v4/social/internal-components/Comment/index.tsx index 573f7dbd2..af3be9dd8 100644 --- a/src/v4/social/internal-components/Comment/index.tsx +++ b/src/v4/social/internal-components/Comment/index.tsx @@ -1,9 +1,7 @@ import React, { useState } from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; - import useComment from '~/social/hooks/useComment'; import useMention from '~/v4/chat/hooks/useMention'; - import { extractMetadata, isCommunityMember, @@ -12,33 +10,27 @@ import { Metadata, parseMentionsMarkup, } from '~/v4/helpers/utils'; - -import useSDK from '~/core/hooks/useSDK'; -import useUser from '~/core/hooks/useUser'; import { CommentRepository, ReactionRepository } from '@amityco/ts-sdk'; - import useCommentPermission from '~/social/hooks/useCommentPermission'; -import useCommentSubscription from '~/social/hooks/useCommentSubscription'; -import useImage from '~/core/hooks/useImage'; - import UIComment from './UIComment'; - import { LIKE_REACTION_KEY } from '~/constants'; import { CommentList } from '~/v4/social/internal-components/CommentList'; import { ReactionList } from '~/v4/social/components/ReactionList'; -import useGetStoryByStoryId from '../../hooks/useGetStoryByStoryId'; +import useGetStoryByStoryId from '~/v4/social/hooks/useGetStoryByStoryId'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; import { useNotifications } from '~/v4/core/providers/NotificationProvider'; - import { Button, BottomSheet, Typography } from '~/v4/core/components'; - -import styles from './Comment.module.css'; import { TrashIcon, PenIcon, FlagIcon, MinusCircleIcon } from '~/v4/social/icons'; import { LoadingIndicator } from '~/v4/social/internal-components/LoadingIndicator'; import useCommunityMembersCollection from '~/v4/social/hooks/collections/useCommunityMembersCollection'; import { useCommentFlaggedByMe } from '~/v4/social/hooks'; import { isModerator } from '~/helpers/permissions'; +import useImage from '~/v4/core/hooks/useImage'; +import useSDK from '~/v4/core/hooks/useSDK'; + +import styles from './Comment.module.css'; + const REPLIES_PER_PAGE = 5; function getCommentData(comment: Amity.Comment | null) { @@ -80,9 +72,12 @@ export const Comment = ({ const { confirm } = useConfirmContext(); const notification = useNotifications(); - const commentAuthor = useUser(comment?.userId); - const commentAuthorAvatar = useImage({ fileId: commentAuthor?.avatarFileId, imageSize: 'small' }); - const { userRoles, currentUserId } = useSDK(); + const commentAuthor = members?.find((member) => member.userId === comment?.userId); + const commentAuthorAvatar = useImage({ + fileId: commentAuthor?.user?.avatarFileId, + imageSize: 'small', + }); + const { userRoles } = useSDK(); const { toggleFlagComment, isFlaggedByMe } = useCommentFlaggedByMe(commentId); const [isEditing, setIsEditing] = useState(false); @@ -193,9 +188,8 @@ export const Comment = ({ }); }; - const currentMember = members.find((member) => member.userId === currentUserId); - const isCommunityModerator = isModerator(currentMember?.roles); - const isMember = isCommunityMember(currentMember); + const isCommunityModerator = isModerator(commentAuthor?.roles); + const isMember = isCommunityMember(commentAuthor); const options = [ canEdit @@ -242,7 +236,9 @@ export const Comment = ({ <UIComment commentId={comment?.commentId} authorName={ - commentAuthor?.displayName || commentAuthor?.userId || formatMessage({ id: 'anonymous' }) + commentAuthor?.user?.displayName || + commentAuthor?.userId || + formatMessage({ id: 'anonymous' }) } authorAvatar={commentAuthorAvatar} canDelete={canDelete} @@ -250,7 +246,6 @@ export const Comment = ({ canLike={canLike} canReply={canReply} canReport={canReport} - isBanned={commentAuthor?.isGlobalBanned} createdAt={comment?.createdAt ? new Date(comment.createdAt) : undefined} editedAt={comment?.editedAt ? new Date(comment?.editedAt) : undefined} mentionees={comment?.metadata?.mentioned as Mentioned[]} @@ -276,7 +271,7 @@ export const Comment = ({ options={options} onClickReply={() => onClickReply?.( - commentAuthor?.displayName, + commentAuthor?.user?.displayName, comment.referenceType, comment.referenceId, comment.commentId, From 8196664a562f5d5f6249dbae01ee4a2d092e9909 Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Fri, 28 Jun 2024 13:50:21 +0700 Subject: [PATCH 166/300] feat: sync api v5 (#455) --- src/v4/core/hooks/collections/useGlobalFeed.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/v4/core/hooks/collections/useGlobalFeed.ts b/src/v4/core/hooks/collections/useGlobalFeed.ts index b7afa6c72..c27fa7e9e 100644 --- a/src/v4/core/hooks/collections/useGlobalFeed.ts +++ b/src/v4/core/hooks/collections/useGlobalFeed.ts @@ -4,13 +4,13 @@ import { useEffect, useMemo, useState } from 'react'; const useGlobalFeed = () => { const [items, setItems] = useState<Amity.Post[]>([]); const [isLoading, setIsLoading] = useState(false); - const [queryToken, setQueryToken] = useState<number | null>(null); + const [queryToken, setQueryToken] = useState<string | null>(null); const [loadMoreHasBeenCalled, setLoadMoreHasBeenCalled] = useState(false); async function fetchMore() { try { setIsLoading(true); - const newPosts = await FeedRepository.queryGlobalFeed({ + const newPosts = await FeedRepository.getCustomRankingGlobalFeed({ limit: 10, queryToken: queryToken || undefined, }); From 215f0bf66599fa73244c378a6c6325204949cce8 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Fri, 28 Jun 2024 13:55:09 +0700 Subject: [PATCH 167/300] fix: use preferred theme as light on v3 (#454) --- src/core/providers/UiKitProvider/index.tsx | 6 +++++- .../components/StoryTab/StoryTabCommunity.tsx | 18 ++++++++++++++++-- .../components/StoryTab/StoryTabGlobalFeed.tsx | 14 +++++++++++++- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/core/providers/UiKitProvider/index.tsx b/src/core/providers/UiKitProvider/index.tsx index 88c9d573c..7b050bdfd 100644 --- a/src/core/providers/UiKitProvider/index.tsx +++ b/src/core/providers/UiKitProvider/index.tsx @@ -163,7 +163,11 @@ const UiKitProvider = ({ <ConfirmProviderV4> <NotificationProvider> <NotificationProviderV4> - <CustomizationProvider initialConfig={{}}> + <CustomizationProvider + initialConfig={{ + preferred_theme: 'light', + }} + > <CustomComponentsProvider config={customComponents}> <ConfigProvider config={{ diff --git a/src/v4/social/components/StoryTab/StoryTabCommunity.tsx b/src/v4/social/components/StoryTab/StoryTabCommunity.tsx index ef39ba2e3..7e6e8c375 100644 --- a/src/v4/social/components/StoryTab/StoryTabCommunity.tsx +++ b/src/v4/social/components/StoryTab/StoryTabCommunity.tsx @@ -10,9 +10,11 @@ import { isAdmin, isModerator } from '~/v4/utils/permissions'; import { checkStoryPermission } from '~/v4/social/utils'; import { useCommunityInfo } from '~/v4/social/hooks/useCommunityInfo'; -import styles from './StoryTabCommunity.module.css'; import { CreateNewStoryButton } from '~/v4/social/elements/CreateNewStoryButton'; import { CommunityAvatar } from '~/v4/social/elements/CommunityAvatar'; +import { useAmityComponent } from '~/v4/core/hooks/uikit'; + +import styles from './StoryTabCommunity.module.css'; const ErrorIcon = (props: React.SVGProps<SVGSVGElement>) => { return ( @@ -48,11 +50,17 @@ export const StoryTabCommunityFeed: React.FC<StoryTabCommunityFeedProps> = ({ onFileChange, onStoryClick, }) => { + const { isExcluded, accessibilityId, themeStyles } = useAmityComponent({ + pageId, + componentId, + }); + const { stories } = useGetActiveStoriesByTarget({ targetId: communityId, targetType: 'community', options: { orderBy: 'asc', sortBy: 'createdAt' }, }); + const { community } = useCommunityInfo(communityId); const { currentUserId, client } = useSDK(); @@ -71,10 +79,16 @@ export const StoryTabCommunityFeed: React.FC<StoryTabCommunityFeedProps> = ({ onStoryClick(); }; + if (isExcluded) return null; + if (!hasStories && !hasStoryPermission) return null; return ( - <div className={clsx(styles.storyTabContainer)}> + <div + data-qa-anchor={accessibilityId} + style={themeStyles} + className={clsx(styles.storyTabContainer)} + > <div className={clsx(styles.storyWrapper)}> {hasStories && ( <StoryRing diff --git a/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx b/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx index 18bf070ef..5b8ff15b7 100644 --- a/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx +++ b/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx @@ -2,6 +2,7 @@ import React, { useRef, useEffect } from 'react'; import styles from './StoryTabGlobalFeed.module.css'; import { StoryTabItem } from './StoryTabItem'; import { useGlobalStoryTargets } from '~/v4/social/hooks/collections/useGlobalStoryTargets'; +import { useAmityComponent } from '~/v4/core/hooks/uikit/index'; const STORIES_PER_PAGE = 10; @@ -19,6 +20,10 @@ export const StoryTabGlobalFeed = ({ componentId = '*', goToViewStoryPage, }: StoryTabGlobalFeedProps) => { + const { isExcluded, accessibilityId, themeStyles } = useAmityComponent({ + pageId, + componentId, + }); const { stories, isLoading, hasMore, loadMoreStories } = useGlobalStoryTargets({ seenState: 'smart' as Amity.StorySeenQuery, limit: STORIES_PER_PAGE, @@ -68,10 +73,17 @@ export const StoryTabGlobalFeed = ({ ); } + if (isExcluded) return null; + if (stories?.length === 0) return null; return ( - <div className={styles.storyTabContainer} ref={containerRef}> + <div + data-qa-anchor={accessibilityId} + style={themeStyles} + className={styles.storyTabContainer} + ref={containerRef} + > {stories.map((story) => { return ( <StoryTabItem From 83a1cfda613d13a36bcf3250f377e38372f88b8b Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Fri, 28 Jun 2024 14:08:25 +0700 Subject: [PATCH 168/300] fix: select file to pause story progress bar (#456) --- .../StoryViewer/Renderers/Image.tsx | 21 ++++++++--------- .../StoryViewer/Renderers/Video.tsx | 23 ++++++++----------- 2 files changed, 18 insertions(+), 26 deletions(-) diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index 30ac0d589..8008b41a6 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -145,18 +145,15 @@ export const renderer: CustomRenderer = ({ }; useEffect(() => { - if (isPaused || isOpenBottomSheet || isOpenCommentSheet) { - action('pause', true); - } else { - action('play', true); - } - }, [isPaused, isOpenBottomSheet, isOpenCommentSheet, action]); - - useEffect(() => { - action('pause', true); if (fileInputRef.current) { - const handleClick = () => action('pause', true); - const handleCancel = () => action('play', true); + const handleClick = () => { + action('pause', true); + setIsPaused(true); + }; + const handleCancel = () => { + action('play', true); + setIsPaused(false); + }; fileInputRef.current.addEventListener('click', handleClick); fileInputRef.current.addEventListener('cancel', handleCancel); @@ -168,7 +165,7 @@ export const renderer: CustomRenderer = ({ } }; } - }, [action, fileInputRef]); + }, [fileInputRef]); return ( <motion.div diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx index 8a4b32ded..6545bc208 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx @@ -38,7 +38,6 @@ export const renderer: CustomRenderer = ({ onClose, onClickCommunity, }) => { - const { formatMessage } = useIntl(); const [loaded, setLoaded] = useState(false); const [muted, setMuted] = useState(false); const [isPaused, setIsPaused] = useState(false); @@ -161,9 +160,13 @@ export const renderer: CustomRenderer = ({ onClose(); }; - const handleProgressComplete = useCallback(() => { - increaseIndex(); - }, [increaseIndex]); + const handleProgressComplete = () => { + if (currentIndex + 1 < storiesCount) { + increaseIndex(); + } else { + onClose(); + } + }; useEffect(() => { if (vid.current) { @@ -181,21 +184,13 @@ export const renderer: CustomRenderer = ({ if (fileInputRef.current) { fileInputRef.current.addEventListener('click', () => { action('pause', true); + setIsPaused(true); }); fileInputRef.current.addEventListener('cancel', () => { action('play', true); + setIsPaused(false); }); } - return () => { - if (fileInputRef.current) { - fileInputRef.current.removeEventListener('cancel', () => { - action('play', true); - }); - fileInputRef.current.removeEventListener('click', () => { - action('pause', true); - }); - } - }; }, []); return ( From 4a2db3253c5598637afaa59c682dc5de7734c334 Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Fri, 28 Jun 2024 14:29:05 +0700 Subject: [PATCH 169/300] fix: ASC-23557 - scroll mention list (#453) * style: scroll mention list * fix: style and ref * refactor: rename MentionTextInput --- .../elements/PostTextField/PostTextField.tsx | 1 - .../MentionTextInput.module.css | 10 +- .../MentionTextInput/MentionTextInput.tsx | 154 ++++++++++-------- .../PostComposerPage.module.css | 1 + 4 files changed, 91 insertions(+), 75 deletions(-) diff --git a/src/v4/social/elements/PostTextField/PostTextField.tsx b/src/v4/social/elements/PostTextField/PostTextField.tsx index 6e5f9cea8..addcaa190 100644 --- a/src/v4/social/elements/PostTextField/PostTextField.tsx +++ b/src/v4/social/elements/PostTextField/PostTextField.tsx @@ -11,7 +11,6 @@ import { MentionNode } from '~/v4/social/internal-components/MentionTextInput/Me import { MentionTextInput } from '~/v4/social/internal-components/MentionTextInput/MentionTextInput'; import { MetaData, createPostParams } from '~/v4/social/pages/PostComposerPage/PostComposerPage'; import styles from './PostTextField.module.css'; -import { Mentionees } from '~/v4/helpers/utils'; const theme = { ltr: 'ltr', diff --git a/src/v4/social/internal-components/MentionTextInput/MentionTextInput.module.css b/src/v4/social/internal-components/MentionTextInput/MentionTextInput.module.css index 38bcd95fc..2b4a017b7 100644 --- a/src/v4/social/internal-components/MentionTextInput/MentionTextInput.module.css +++ b/src/v4/social/internal-components/MentionTextInput/MentionTextInput.module.css @@ -1,6 +1,12 @@ .mentionTextInput_item { - position: fixed; + position: absolute; bottom: 0; left: 0; - width: 100vw; + width: 100%; + height: 12.5rem; + overflow-y: scroll; +} + +.mentionTextInput_item::-webkit-scrollbar { + display: none; } diff --git a/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx b/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx index dc70699ab..fa6fcadc8 100644 --- a/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx +++ b/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx @@ -6,7 +6,7 @@ import { MenuTextMatch, } from '@lexical/react/LexicalTypeaheadMenuPlugin'; import { TextNode } from 'lexical'; -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import React, { RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import ReactDOM from 'react-dom'; import { $createMentionNode } from './MentionNodes'; import { CommunityMember } from '../CommunityMember'; @@ -153,76 +153,86 @@ export const useUserQueryByDisplayName = (displayName: string) => { }; }; -export function MentionTextInput(): JSX.Element | null { - const [editor] = useLexicalComposerContext(); - - const [queryString, setQueryString] = useState<string | null>(null); - - const { users } = useUserQueryByDisplayName(queryString || '' ); - - const options = useMemo(() => users.map((user) => new MentionTypeaheadOption(user)), [users]); - - const onSelectOption = useCallback( - ( - selectedOption: MentionTypeaheadOption, - nodeToReplace: TextNode | null, - closeMenu: () => void, - ) => { - editor.update(() => { - const mentionNode = $createMentionNode({ - mentionName: selectedOption.key, - displayName: selectedOption.user.displayName, - userId: selectedOption.user.userId, - }); - if (nodeToReplace) { - nodeToReplace.replace(mentionNode); - } - mentionNode.select(); - closeMenu(); - }); - }, - [editor], - ); - - const checkForMentionMatch = useCallback( - (text: string) => { - return getPossibleQueryMatch(text); - }, - [editor], - ); - +export const MentionTextInput = () => { + const mentionTextInputItemRef = useRef<HTMLDivElement>(null); return ( - <LexicalTypeaheadMenuPlugin - onQueryChange={setQueryString} - onSelectOption={onSelectOption} - triggerFn={checkForMentionMatch} - options={options} - menuRenderFn={( - anchorElementRef, - { selectedIndex, selectOptionAndCleanUp, setHighlightedIndex }, - ) => - anchorElementRef.current && users.length - ? ReactDOM.createPortal( - <div className={styles.mentionTextInput_item}> - {options.map((option, i: number) => ( - <CommunityMember - isSelected={selectedIndex === i} - onClick={() => { - setHighlightedIndex(i); - selectOptionAndCleanUp(option); - }} - onMouseEnter={() => { - setHighlightedIndex(i); - }} - key={option.key} - option={option} - /> - ))} - </div>, - anchorElementRef.current, - ) - : null - } - /> + <div> + <div ref={mentionTextInputItemRef}></div> + <Mention anchorRef={mentionTextInputItemRef} /> + </div> ); -} +}; + + function Mention({ anchorRef }: { anchorRef: RefObject<HTMLDivElement> }) { + const [editor] = useLexicalComposerContext(); + + const [queryString, setQueryString] = useState<string | null>(null); + + const { users } = useUserQueryByDisplayName(queryString || ''); + + const options = useMemo(() => users.map((user) => new MentionTypeaheadOption(user)), [users]); + + const onSelectOption = useCallback( + ( + selectedOption: MentionTypeaheadOption, + nodeToReplace: TextNode | null, + closeMenu: () => void, + ) => { + editor.update(() => { + const mentionNode = $createMentionNode({ + mentionName: selectedOption.key, + displayName: selectedOption.user.displayName, + userId: selectedOption.user.userId, + }); + if (nodeToReplace) { + nodeToReplace.replace(mentionNode); + } + mentionNode.select(); + closeMenu(); + }); + }, + [editor], + ); + + const checkForMentionMatch = useCallback( + (text: string) => { + return getPossibleQueryMatch(text); + }, + [editor], + ); + + return ( + <LexicalTypeaheadMenuPlugin + onQueryChange={setQueryString} + onSelectOption={onSelectOption} + triggerFn={checkForMentionMatch} + options={options} + menuRenderFn={( + anchorElementRef, + { selectedIndex, selectOptionAndCleanUp, setHighlightedIndex }, + ) => + anchorRef.current && users.length + ? ReactDOM.createPortal( + <div className={styles.mentionTextInput_item}> + {options.map((option, i: number) => ( + <CommunityMember + isSelected={selectedIndex === i} + onClick={() => { + setHighlightedIndex(i); + selectOptionAndCleanUp(option); + }} + onMouseEnter={() => { + setHighlightedIndex(i); + }} + key={option.key} + option={option} + /> + ))} + </div>, + anchorRef.current, + ) + : null + } + /> + ); + } diff --git a/src/v4/social/pages/PostComposerPage/PostComposerPage.module.css b/src/v4/social/pages/PostComposerPage/PostComposerPage.module.css index 62dc5e203..685f37be9 100644 --- a/src/v4/social/pages/PostComposerPage/PostComposerPage.module.css +++ b/src/v4/social/pages/PostComposerPage/PostComposerPage.module.css @@ -1,4 +1,5 @@ .postComposerPage { + position: relative; display: block; background-color: var(--asc-color-base-background); height: 100%; From c50c03353efaddb5812d965b1ae3232fb696343c Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Fri, 28 Jun 2024 15:41:59 +0700 Subject: [PATCH 170/300] fix: ASC-20957 - story tab ring loading state (#444) * fix: delete first multiple segment story should navigate to next story * fix: wrong notification text content * fix: comment author * fix: remove isBanned prop * fix: remove unused * fix: apply useAmityComponent to StoryTab * fix: story tab v4 * fix: remove unused * fix: type --- src/social/components/StoryTab/StoryTab.tsx | 21 ++--- src/social/hooks/useStories.ts | 87 ------------------- src/social/pages/CommunityFeed/index.tsx | 7 +- .../social/components/StoryTab/StoryTab.tsx | 24 ++--- .../components/StoryTab/StoryTabCommunity.tsx | 19 ++-- .../StoryTab/StoryTabGlobalFeed.tsx | 6 +- .../elements/StoryRing/StoryRing.module.css | 5 +- .../social/elements/StoryRing/StoryRing.tsx | 5 +- src/v4/social/hooks/useGetStoryByStoryId.ts | 3 +- .../internal-components/Comment/index.tsx | 3 +- .../pages/StoryPage/CommunityFeedStory.tsx | 4 +- 11 files changed, 36 insertions(+), 148 deletions(-) delete mode 100644 src/social/hooks/useStories.ts diff --git a/src/social/components/StoryTab/StoryTab.tsx b/src/social/components/StoryTab/StoryTab.tsx index c928efe51..de1d6a1bc 100644 --- a/src/social/components/StoryTab/StoryTab.tsx +++ b/src/social/components/StoryTab/StoryTab.tsx @@ -1,34 +1,29 @@ import React from 'react'; import { useNavigation } from '~/social/providers/NavigationProvider'; + import { StoryTabCommunityFeed } from '~/v4/social/components/StoryTab/StoryTabCommunity'; import { StoryTabGlobalFeed } from '~/v4/social/components/StoryTab/StoryTabGlobalFeed'; import { useStoryContext } from '~/v4/social/providers/StoryProvider'; -type StoryTabType = 'communityFeed' | 'globalFeed'; - -type StoryTabProps<T extends StoryTabType> = { - type: T; - communityId?: T extends 'communityFeed' ? string : never; -}; +type StoryTabProps = { type: 'communityFeed'; communityId: string } | { type: 'globalFeed' }; -export const StoryTab = <T extends StoryTabType>({ type, communityId }: StoryTabProps<T>) => { +export const StoryTab: React.FC<StoryTabProps> = (props) => { const componentId = 'story_tab_component'; const { onClickStory, goToDraftStoryPage } = useNavigation(); - const { setFile } = useStoryContext(); const renderStoryTab = () => { - switch (type) { + switch (props.type) { case 'communityFeed': return ( <StoryTabCommunityFeed componentId={componentId} - communityId={communityId || ''} + communityId={props.communityId} onFileChange={(file) => { setFile(file); if (file) { goToDraftStoryPage( - communityId || '', + props.communityId, 'community', file.type.includes('image') ? { type: 'image', url: URL.createObjectURL(file) } @@ -37,7 +32,7 @@ export const StoryTab = <T extends StoryTabType>({ type, communityId }: StoryTab ); } }} - onStoryClick={() => onClickStory(communityId || '', 'communityFeed')} + onStoryClick={() => onClickStory(props.communityId, 'communityFeed')} /> ); case 'globalFeed': @@ -53,8 +48,6 @@ export const StoryTab = <T extends StoryTabType>({ type, communityId }: StoryTab }} /> ); - default: - return null; } }; diff --git a/src/social/hooks/useStories.ts b/src/social/hooks/useStories.ts deleted file mode 100644 index c441fa403..000000000 --- a/src/social/hooks/useStories.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { StoryRepository } from '@amityco/ts-sdk'; -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { isNonNullable } from '~/helpers/utils'; -import useCommunityStoriesSubscription from './useCommunityStoriesSubscription'; - -type UseStories = { - stories: (Amity.Story | undefined)[]; - hasMore: boolean; - loadMore: () => void; -}; - -const useStories = (params: Amity.GetStoriesByTargetParam): UseStories => { - const disposeFnRef = useRef<(() => void) | null>(null); - const [stories, setStories] = useState<Amity.Story[]>([]); - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState<Error | null>(null); - const [hasMore, setHasMore] = useState<boolean>(false); - const loadMoreFnRef = useRef<(() => void) | undefined | null>(null); - const [loadMoreHasBeenCalled, setLoadMoreHasBeenCalled] = useState(false); - - const loadMore = useCallback(() => { - if (loadMoreFnRef.current) { - setLoadMoreHasBeenCalled(true); - loadMoreFnRef.current?.(); - } - }, [loadMoreFnRef, loadMoreHasBeenCalled, isLoading, setIsLoading]); - - useEffect(() => { - async function run() { - if (disposeFnRef.current) { - disposeFnRef.current(); - } - - disposeFnRef.current = StoryRepository.getActiveStoriesByTarget( - { - targetId: params.targetId, - targetType: params.targetType, - options: params.options, - }, - async ({ data, hasNextPage, onNextPage }) => { - if (data) { - if (params.options?.orderBy === 'asc' && params.options?.sortBy === 'createdAt') { - const sortedData = data.filter(isNonNullable).sort((a, b) => { - // Place stories with syncing state at the end - if (a.syncState === 'syncing' && b.syncState !== 'syncing') { - return 1; - } else if (a.syncState !== 'syncing' && b.syncState === 'syncing') { - return -1; - } else { - // For other cases, maintain the original order - return 0; - } - }); - setStories(sortedData); - } else { - setStories(data.filter(isNonNullable)); - } - } - - hasNextPage && setHasMore(hasNextPage); - loadMoreFnRef.current = onNextPage; - }, - ); - } - - run(); - - return () => { - if (disposeFnRef.current) { - disposeFnRef.current(); - } - }; - }, [params.targetId]); - - useCommunityStoriesSubscription({ - targetId: params.targetId, - targetType: params.targetType as Amity.StoryTargetType, // TO FIX: type issue - }); - - return { - stories, - hasMore, - loadMore, - }; -}; - -export default useStories; diff --git a/src/social/pages/CommunityFeed/index.tsx b/src/social/pages/CommunityFeed/index.tsx index b0ca3f9bf..f2eb42bbe 100644 --- a/src/social/pages/CommunityFeed/index.tsx +++ b/src/social/pages/CommunityFeed/index.tsx @@ -19,20 +19,17 @@ import useCommunitySubscription from '~/social/hooks/useCommunitySubscription'; import usePostsCollection from '~/social/hooks/collections/usePostsCollection'; -import useFile from '~/core/hooks/useFile'; - import { CommunitySideMenuOverlay, HeadTitle, MobileContainer, StyledCommunitySideMenu, } from '../NewsFeed/styles'; -import useStories from '~/social/hooks/useStories'; import { BarsIcon } from '~/icons'; -import { useStoryContext } from '~/v4/social/providers/StoryProvider'; import { useNavigation } from '~/social/providers/NavigationProvider'; +import { useGetActiveStoriesByTarget } from '~/v4/social/hooks/useGetActiveStories'; interface CommunityFeedProps { communityId: string; @@ -43,7 +40,7 @@ interface CommunityFeedProps { const CommunityFeed = ({ communityId, isNewCommunity, isOpen, toggleOpen }: CommunityFeedProps) => { const { goToDraftStoryPage } = useNavigation(); - const { stories } = useStories({ + const { stories } = useGetActiveStoriesByTarget({ targetId: communityId, targetType: 'community', options: { diff --git a/src/v4/social/components/StoryTab/StoryTab.tsx b/src/v4/social/components/StoryTab/StoryTab.tsx index 74bf74384..418448989 100644 --- a/src/v4/social/components/StoryTab/StoryTab.tsx +++ b/src/v4/social/components/StoryTab/StoryTab.tsx @@ -6,37 +6,26 @@ import { useStoryContext } from '~/v4/social/providers/StoryProvider'; import { StoryTabCommunityFeed } from './StoryTabCommunity'; import { StoryTabGlobalFeed } from './StoryTabGlobalFeed'; -type StoryTabType = 'communityFeed' | 'globalFeed'; +type StoryTabProps = { type: 'communityFeed'; communityId: string } | { type: 'globalFeed' }; -type StoryTabProps<T extends StoryTabType> = { - pageId?: string; - type: T; - communityId?: T extends 'communityFeed' ? string : never; -}; - -export const StoryTab = <T extends StoryTabType>({ - pageId = '*', - type, - communityId, -}: StoryTabProps<T>) => { +export const StoryTab: React.FC<StoryTabProps> = (props) => { const componentId = 'story_tab_component'; const { AmityGlobalFeedComponentBehavior } = usePageBehavior(); const { goToViewStoryPage, goToDraftStoryPage } = useNavigation(); const { setFile } = useStoryContext(); const renderStoryTab = () => { - switch (type) { + switch (props.type) { case 'communityFeed': return ( <StoryTabCommunityFeed - pageId={pageId} componentId={componentId} - communityId={communityId || ''} + communityId={props.communityId || ''} onFileChange={(file) => { setFile(file); if (file) { goToDraftStoryPage({ - targetId: communityId || '', + targetId: props.communityId || '', targetType: 'community', mediaType: file.type.includes('image') ? { type: 'image', url: URL.createObjectURL(file) } @@ -47,7 +36,7 @@ export const StoryTab = <T extends StoryTabType>({ }} onStoryClick={() => goToViewStoryPage({ - targetId: communityId || '', + targetId: props.communityId || '', targetType: 'community', storyType: 'communityFeed', }) @@ -57,7 +46,6 @@ export const StoryTab = <T extends StoryTabType>({ case 'globalFeed': return ( <StoryTabGlobalFeed - pageId={pageId} componentId={componentId} goToViewStoryPage={({ storyTarget, storyTargets }) => { AmityGlobalFeedComponentBehavior.goToViewStoryPage({ diff --git a/src/v4/social/components/StoryTab/StoryTabCommunity.tsx b/src/v4/social/components/StoryTab/StoryTabCommunity.tsx index 7e6e8c375..93ec60246 100644 --- a/src/v4/social/components/StoryTab/StoryTabCommunity.tsx +++ b/src/v4/social/components/StoryTab/StoryTabCommunity.tsx @@ -9,7 +9,6 @@ import useUser from '~/v4/core/hooks/objects/useUser'; import { isAdmin, isModerator } from '~/v4/utils/permissions'; import { checkStoryPermission } from '~/v4/social/utils'; import { useCommunityInfo } from '~/v4/social/hooks/useCommunityInfo'; - import { CreateNewStoryButton } from '~/v4/social/elements/CreateNewStoryButton'; import { CommunityAvatar } from '~/v4/social/elements/CommunityAvatar'; import { useAmityComponent } from '~/v4/core/hooks/uikit'; @@ -90,16 +89,14 @@ export const StoryTabCommunityFeed: React.FC<StoryTabCommunityFeedProps> = ({ className={clsx(styles.storyTabContainer)} > <div className={clsx(styles.storyWrapper)}> - {hasStories && ( - <StoryRing - pageId={pageId} - componentId={componentId} - hasUnseen={hasUnSeen} - uploading={uploading} - isErrored={isErrored} - size={48} - /> - )} + <StoryRing + pageId={pageId} + componentId={componentId} + hasUnseen={hasUnSeen} + uploading={uploading} + isErrored={isErrored} + size={48} + /> <button className={clsx(styles.storyAvatarContainer)} onClick={handleOnClick}> <CommunityAvatar pageId={pageId} componentId={componentId} community={community} /> diff --git a/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx b/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx index 5b8ff15b7..638049f4e 100644 --- a/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx +++ b/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx @@ -2,7 +2,7 @@ import React, { useRef, useEffect } from 'react'; import styles from './StoryTabGlobalFeed.module.css'; import { StoryTabItem } from './StoryTabItem'; import { useGlobalStoryTargets } from '~/v4/social/hooks/collections/useGlobalStoryTargets'; -import { useAmityComponent } from '~/v4/core/hooks/uikit/index'; +import { useAmityComponent } from '~/v4/core/hooks/uikit'; const STORIES_PER_PAGE = 10; @@ -60,9 +60,11 @@ export const StoryTabGlobalFeed = ({ }; }, [stories, hasMore, loadMoreStories]); + if (isExcluded) return null; + if (isLoading) { return ( - <div className={styles.storyTabContainer}> + <div style={themeStyles} className={styles.storyTabContainer}> {Array.from({ length: 10 }).map((_, index) => ( <div key={index} className={styles.storyTabSkeleton}> <div className={styles.storyTabSkeletonAvatar} /> diff --git a/src/v4/social/elements/StoryRing/StoryRing.module.css b/src/v4/social/elements/StoryRing/StoryRing.module.css index 5752587d1..778241587 100644 --- a/src/v4/social/elements/StoryRing/StoryRing.module.css +++ b/src/v4/social/elements/StoryRing/StoryRing.module.css @@ -12,10 +12,11 @@ stroke-dasharray: 339; stroke-dashoffset: 339; transform: rotate(-90deg); - animation: progress 2s linear infinite; + transform-origin: 50% 50%; + animation: upload-progress 2s linear infinite; } -@keyframes progress { +@keyframes upload-progress { 0% { stroke-dashoffset: 339; } diff --git a/src/v4/social/elements/StoryRing/StoryRing.tsx b/src/v4/social/elements/StoryRing/StoryRing.tsx index 9a6c86826..396cb611c 100644 --- a/src/v4/social/elements/StoryRing/StoryRing.tsx +++ b/src/v4/social/elements/StoryRing/StoryRing.tsx @@ -152,9 +152,6 @@ const UploadingRingSvg = ({ strokeLinecap="round" stroke="url(#story-ring-gradient)" fill="none" - strokeDasharray={339} - strokeDashoffset={339} - transform={`rotate(-90 ${size / 2} ${size / 2})`} /> </svg> ); @@ -179,7 +176,7 @@ export const StoryRing = ({ ...props }: StoryRingProps) => { const elementId = 'story_ring'; - const { isExcluded, config } = useAmityElement({ + const { isExcluded } = useAmityElement({ pageId, componentId, elementId, diff --git a/src/v4/social/hooks/useGetStoryByStoryId.ts b/src/v4/social/hooks/useGetStoryByStoryId.ts index 403a6b911..6839e752d 100644 --- a/src/v4/social/hooks/useGetStoryByStoryId.ts +++ b/src/v4/social/hooks/useGetStoryByStoryId.ts @@ -1,6 +1,5 @@ import { StoryRepository } from '@amityco/ts-sdk'; - -import useLiveObject from '~/core/hooks/useLiveObject'; +import useLiveObject from '~/v4/core/hooks/useLiveObject'; const useGetStoryByStoryId = (storyId: string | undefined) => { const story = useLiveObject({ diff --git a/src/v4/social/internal-components/Comment/index.tsx b/src/v4/social/internal-components/Comment/index.tsx index af3be9dd8..c6e5e1b5c 100644 --- a/src/v4/social/internal-components/Comment/index.tsx +++ b/src/v4/social/internal-components/Comment/index.tsx @@ -64,7 +64,8 @@ export const Comment = ({ onClickReply, }: CommentProps) => { const comment = useComment(commentId); - const story = useGetStoryByStoryId(comment?.referenceId); + + const { item: story } = useGetStoryByStoryId(comment?.referenceId); const { members } = useCommunityMembersCollection(story?.community?.communityId); const [bottomSheet, setBottomSheet] = useState(false); diff --git a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx index 620ef42ed..faecbb09e 100644 --- a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx @@ -1,6 +1,5 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import useStories from '~/social/hooks/useStories'; import useSDK from '~/core/hooks/useSDK'; import { FinalColor } from 'extract-colors/lib/types/Color'; import { StoryRepository } from '@amityco/ts-sdk'; @@ -28,6 +27,7 @@ import { useAmityPage } from '~/v4/core/hooks/uikit'; import { FileTrigger } from 'react-aria-components'; import styles from './StoryPage.module.css'; +import { useGetActiveStoriesByTarget } from '~/v4/social/hooks/useGetActiveStories'; interface CommunityFeedStoryProps { pageId?: string; @@ -66,7 +66,7 @@ export const CommunityFeedStory = ({ const { confirm } = useConfirmContext(); const notification = useNotifications(); - const { stories } = useStories({ + const { stories } = useGetActiveStoriesByTarget({ targetId: communityId, targetType: 'community', options: { From a96493b150ba6ecf23248e81bc150d1e83f8d2b6 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Fri, 28 Jun 2024 15:57:09 +0700 Subject: [PATCH 171/300] fix: ASC-22315 - failed noti to use BE message (#446) * fix: delete first multiple segment story should navigate to next story * fix: wrong notification text content * fix: comment author * fix: remove isBanned prop * fix: remove unused * fix: apply useAmityComponent to StoryTab * fix: story tab v4 * fix: failed noti should use BE response message * fix: use accessibilityId instead of uiReference * fix: remove unused * fix: remove unused --- src/v4/social/pages/DraftsPage/DraftsPage.tsx | 56 +++++++++---------- .../pages/StoryPage/CommunityFeedStory.tsx | 25 ++++----- .../pages/StoryPage/GlobalFeedStory.tsx | 10 ++-- 3 files changed, 41 insertions(+), 50 deletions(-) diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index 63a2f5b8b..70e4629d5 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -1,5 +1,4 @@ import React, { useEffect, useState } from 'react'; -import { useIntl } from 'react-intl'; import { extractColors } from 'extract-colors'; import { readFileAsync } from '~/helpers'; import { SubmitHandler } from 'react-hook-form'; @@ -18,9 +17,10 @@ import { useNotifications } from '~/v4/core/providers/NotificationProvider'; import { useCommunityInfo } from '~/social/components/CommunityInfo/hooks'; import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; import { useNavigation } from '~/v4/core/providers/NavigationProvider'; +import { VideoPreview } from '~/v4/social/internal-components/VideoPreview'; +import { useAmityPage } from '~/v4/core/hooks/uikit'; import styles from './DraftsPage.module.css'; -import { VideoPreview } from '~/v4/social/internal-components/VideoPreview'; export type AmityStoryMediaType = { type: 'image'; url: string } | { type: 'video'; url: string }; @@ -51,6 +51,12 @@ export const PlainDraftStoryPage = ({ storyType: 'communityFeed' | 'globalFeed'; }) => { const pageId = 'create_story_page'; + const { accessibilityId, themeStyles, isExcluded } = useAmityPage({ + pageId, + }); + + if (isExcluded) return null; + const { file, setFile } = useStoryContext(); const { community } = useCommunityInfo(targetId); const [isHyperLinkBottomSheetOpen, setIsHyperLinkBottomSheetOpen] = useState(false); @@ -76,8 +82,6 @@ export const PlainDraftStoryPage = ({ setIsHyperLinkBottomSheetOpen(false); }; - const { formatMessage } = useIntl(); - const [imageMode, setImageMode] = useState<'fit' | 'fill'>('fit'); const [colors, setColors] = useState<Awaited<ReturnType<typeof extractColors>>>([]); @@ -105,7 +109,7 @@ export const PlainDraftStoryPage = ({ goToCommunityPage(targetId); } if (mediaType?.type === 'image' && targetId) { - const { data: imageData } = await StoryRepository.createImageStory( + await StoryRepository.createImageStory( targetType, targetId, formData, @@ -113,38 +117,28 @@ export const PlainDraftStoryPage = ({ imageMode, items, ); - if (imageData) { - notification.success({ - content: formatMessage({ id: 'storyViewer.notification.success' }), - }); - } } else if (mediaType?.type === 'video' && targetId) { - const { data: videoData } = await StoryRepository.createVideoStory( - targetType, - targetId, - formData, - metadata, - items, - ); - if (videoData) { - notification.success({ - content: formatMessage({ id: 'storyViewer.notification.success' }), - }); - } + await StoryRepository.createVideoStory(targetType, targetId, formData, metadata, items); } - } catch (error) { - notification.error({ - content: formatMessage({ id: 'storyViewer.notification.error' }), + notification.success({ + content: 'Successfully shared story', }); + } catch (error: unknown) { + if (error instanceof Error) { + notification.info({ + content: error.message ?? 'Failed to share story', + }); + } } }; const discardCreateStory = () => { confirm({ - title: formatMessage({ id: 'storyViewer.action.confirmModal.title' }), - content: formatMessage({ id: 'storyViewer.action.confirmModal.content' }), - cancelText: formatMessage({ id: 'general.action.cancel' }), - okText: formatMessage({ id: 'delete' }), + title: 'Delete this story?', + content: + 'This story will be permanently deleted. You’ll no longer to see and find this story.', + cancelText: 'Cancel', + okText: 'Delete', onOk: () => { setFile(null); onDiscardCreateStory(); @@ -181,7 +175,7 @@ export const PlainDraftStoryPage = ({ const handleOnClickHyperLinkActionButton = () => { if (hyperLink[0]?.data?.url) { notification.info({ - content: formatMessage({ id: 'storyDraft.notification.hyperlink.error' }), + content: 'Can’t add more than one link to your story.', }); return; } @@ -219,7 +213,7 @@ export const PlainDraftStoryPage = ({ }, [file, imageMode, mediaType]); return ( - <div className={styles.storyWrapper}> + <div data-qa-anchor={accessibilityId} style={themeStyles} className={styles.storyWrapper}> <div id="asc-uikit-create-story" className={styles.draftPageContainer}> <div className={styles.headerContainer}> <div className={styles.header}> diff --git a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx index faecbb09e..77bd35ef7 100644 --- a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx @@ -149,7 +149,7 @@ export const CommunityFeedStory = ({ formData.append('files', file); setFile(null); if (file?.type.includes('image') && currentUserId) { - const { data: imageData } = await StoryRepository.createImageStory( + await StoryRepository.createImageStory( 'user', currentUserId, formData, @@ -157,31 +157,26 @@ export const CommunityFeedStory = ({ imageMode, items, ); - if (imageData) { - notification.success({ - content: 'Successfully shared story', - }); - } } else { if (currentUserId) { - const { data: videoData } = await StoryRepository.createVideoStory( + await StoryRepository.createVideoStory( 'user', currentUserId, formData, metadata, items, ); - if (videoData) { - notification.success({ - content: 'Successfully shared story', - }); - } } } - } catch (error) { - notification.error({ - content: 'Failed to share story', + notification.success({ + content: 'Successfully shared story', }); + } catch (error: unknown) { + if (error instanceof Error) { + notification.info({ + content: error.message ?? 'Failed to share story', + }); + } } }, [currentUserId, notification, setFile], diff --git a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx index d9acd1a3e..663b6663e 100644 --- a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx @@ -168,10 +168,12 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ } } } - } catch (error) { - notification.error({ - content: 'Failed to share story', - }); + } catch (error: unknown) { + if (error instanceof Error) { + notification.info({ + content: error.message ?? 'Failed to share story', + }); + } } }, [currentUserId, notification, setFile], From 382d52eaa405988baa2df26bf46d6909ca358470 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Fri, 28 Jun 2024 16:00:36 +0700 Subject: [PATCH 172/300] fix: showImpression condition (#451) --- .../internal-components/StoryViewer/Renderers/Image.tsx | 4 +++- .../internal-components/StoryViewer/Renderers/Video.tsx | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index 8008b41a6..b9b8a21f4 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -294,7 +294,9 @@ export const renderer: CustomRenderer = ({ isLiked={isLiked} myReactions={myReactions} onClickComment={openCommentSheet} - showImpression={isCreator || haveStoryPermission} + showImpression={ + isCreator || isCommunityModerator || checkStoryPermission(client, community?.communityId) + } isMember={isMember} /> </motion.div> diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx index 6545bc208..31906edc6 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx @@ -318,7 +318,10 @@ export const renderer: CustomRenderer = ({ isLiked={isLiked} onClickComment={openCommentSheet} myReactions={myReactions} - showImpression={isCreator || haveStoryPermission} + // only show impression if user is creator, community moderator or has story permission + showImpression={ + isCreator || isCommunityModerator || checkStoryPermission(client, community?.communityId) + } isMember={isMember} /> </motion.div> From 82605a2ce30fe6d88f4ba08256da1219e7c68f17 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Fri, 28 Jun 2024 16:29:03 +0700 Subject: [PATCH 173/300] chore: ASC-00000 - uikit core api (#457) * chore: change shouldCall type * chore: update IconComponent api * fix: fix button in story from CancelButton to EditCancelButton * chore: update configuration --- src/v4/core/IconComponent.tsx | 20 ++----- src/v4/core/hooks/collections/useCommunity.ts | 6 +- .../collections/useFollowersCollection.ts | 2 +- .../collections/useFollowingsCollection.ts | 2 +- .../hooks/collections/useUsersCollection.ts | 38 ++++++++---- src/v4/core/hooks/objects/usePost.ts | 2 +- src/v4/core/hooks/objects/useUser.ts | 2 +- .../subscriptions/useCommentSubscription.ts | 6 +- .../subscriptions/useCommunitySubscription.ts | 6 +- .../subscriptions/usePostSubscription.ts | 6 +- .../hooks/subscriptions/useSubscription.ts | 4 +- .../subscriptions/useUserSubscription.ts | 6 +- src/v4/core/hooks/useLiveCollection.ts | 10 ++-- src/v4/core/hooks/useLiveObject.ts | 8 +-- src/v4/core/natives/Button.tsx | 7 +++ .../core/providers/CustomizationProvider.tsx | 4 +- .../CommentEdition/CommentEdition.tsx | 5 +- .../HyperLinkConfig/HyperLinkConfig.tsx | 10 +++- .../TopNavigation/TopNavigation.tsx | 2 +- .../components/TopSearchBar/TopSearchBar.tsx | 15 ++--- .../AspectRatioButton/AspectRatioButton.tsx | 21 ++++--- .../social/elements/BackButton/BackButton.tsx | 25 ++++---- .../CancelButton/CancelButton.module.css | 16 +---- .../elements/CancelButton/CancelButton.tsx | 19 +++--- .../elements/ClearButton/ClearButton.tsx | 15 +++-- .../CloseButton/CloseButton.module.css | 5 +- .../elements/CloseButton/CloseButton.tsx | 22 +++---- .../CommentBubbleDeleted.tsx | 48 ++++++++------- .../elements/CommentButton/CommentButton.tsx | 36 ++++++------ .../CreateNewStoryButton.tsx | 20 ++++--- .../EditCancelButton.module.css | 5 ++ .../EditCancelButton.stories.tsx | 15 +++++ .../EditCancelButton/EditCancelButton.tsx | 40 +++++++++++++ .../elements/EditCancelButton/index.tsx | 1 + .../GlobalSearchButton.module.css | 6 +- .../GlobalSearchButton/GlobalSearchButton.tsx | 58 +++++++++---------- .../HyperLinkButton/HyperLinkButton.tsx | 23 ++++---- .../OverflowMenuButton/OverflowMenuButton.tsx | 27 +++++---- .../social/elements/SearchIcon/SearchIcon.tsx | 11 ++-- .../SpeakerButton/SpeakerButton.module.css | 2 +- .../elements/SpeakerButton/SpeakerButton.tsx | 29 ++++++---- .../StoryCommentButton/StoryCommentButton.tsx | 45 +++++++------- .../StoryReactionButton.tsx | 27 +++++---- .../collections/useCommunitiesCollection.ts | 13 +++-- .../useCommunityModeratorsCollection.ts | 2 +- .../collections/useGlobalStoryTargets.ts | 3 +- src/v4/social/hooks/useCommunityInfo.ts | 2 +- .../hooks/useCommunityStoriesSubscription.ts | 6 +- src/v4/social/hooks/useGetActiveStories.ts | 2 +- .../internal-components/PostMenu/PostMenu.tsx | 4 +- 50 files changed, 412 insertions(+), 297 deletions(-) create mode 100644 src/v4/core/natives/Button.tsx create mode 100644 src/v4/social/elements/EditCancelButton/EditCancelButton.module.css create mode 100644 src/v4/social/elements/EditCancelButton/EditCancelButton.stories.tsx create mode 100644 src/v4/social/elements/EditCancelButton/EditCancelButton.tsx create mode 100644 src/v4/social/elements/EditCancelButton/index.tsx diff --git a/src/v4/core/IconComponent.tsx b/src/v4/core/IconComponent.tsx index 1755baddb..cc32144e3 100644 --- a/src/v4/core/IconComponent.tsx +++ b/src/v4/core/IconComponent.tsx @@ -1,29 +1,19 @@ -import React from 'react'; -import { Button, PressEvent } from 'react-aria-components'; - export interface IconComponentProps { defaultIcon: () => JSX.Element; imgIcon: () => JSX.Element; - onPress?: (e: PressEvent) => void; defaultIconName?: string; configIconName?: string; - 'data-qa-anchor'?: string; - style?: React.CSSProperties; - className?: string; } export const IconComponent = ({ defaultIcon, imgIcon, - onPress, - style, defaultIconName, configIconName, - className, }: IconComponentProps) => { - return ( - <Button className={className} data-qa-anchor={'data-qa-anchor'} onPress={onPress} style={style}> - {defaultIconName === configIconName ? defaultIcon() : imgIcon()} - </Button> - ); + if (defaultIconName === configIconName) { + return defaultIcon(); + } + + return imgIcon(); }; diff --git a/src/v4/core/hooks/collections/useCommunity.ts b/src/v4/core/hooks/collections/useCommunity.ts index 7810c2c79..375df784a 100644 --- a/src/v4/core/hooks/collections/useCommunity.ts +++ b/src/v4/core/hooks/collections/useCommunity.ts @@ -3,15 +3,15 @@ import useLiveObject from '~/v4/core/hooks/useLiveObject'; const useCommunity = ({ communityId, - shouldCall = () => true, + shouldCall = true, }: { communityId: string | null | undefined; - shouldCall?: () => boolean; + shouldCall?: boolean; }) => { const { item, ...rest } = useLiveObject({ fetcher: CommunityRepository.getCommunity, params: communityId, - shouldCall: () => shouldCall() && !!communityId, + shouldCall: shouldCall && !!communityId, }); return { diff --git a/src/v4/core/hooks/collections/useFollowersCollection.ts b/src/v4/core/hooks/collections/useFollowersCollection.ts index b6e0999f4..c2c3668d1 100644 --- a/src/v4/core/hooks/collections/useFollowersCollection.ts +++ b/src/v4/core/hooks/collections/useFollowersCollection.ts @@ -16,7 +16,7 @@ export default function useFollowersCollection({ userId: userId as string, status: status ?? undefined, }, - shouldCall: () => !!userId, + shouldCall: !!userId, }); return { diff --git a/src/v4/core/hooks/collections/useFollowingsCollection.ts b/src/v4/core/hooks/collections/useFollowingsCollection.ts index 6c506670e..efcdb585e 100644 --- a/src/v4/core/hooks/collections/useFollowingsCollection.ts +++ b/src/v4/core/hooks/collections/useFollowingsCollection.ts @@ -16,7 +16,7 @@ export default function useFollowingsCollection({ userId: userId as string, status: status ?? undefined, }, - shouldCall: () => !!userId, + shouldCall: !!userId, }); return { diff --git a/src/v4/core/hooks/collections/useUsersCollection.ts b/src/v4/core/hooks/collections/useUsersCollection.ts index 139a8e2a8..b873ab92e 100644 --- a/src/v4/core/hooks/collections/useUsersCollection.ts +++ b/src/v4/core/hooks/collections/useUsersCollection.ts @@ -3,11 +3,15 @@ import { useEffect, useRef, useState } from 'react'; import useLiveCollection from '~/v4/core/hooks/useLiveCollection'; -const MINIMUM_STRING_LENGTH_TO_TRIGGER_QUERY = 1; - -export const useUserQueryByDisplayName = ( - params: Parameters<typeof UserRepository.searchUserByDisplayName>[0], -) => { +export const useUserQueryByDisplayName = ({ + displayName, + limit, + enabled, +}: { + displayName: string; + limit: number; + enabled: boolean; +}) => { const [items, setItems] = useState<Amity.User[]>([]); const [isLoading, setIsLoading] = useState(false); const [hasMore, setHasMore] = useState(false); @@ -28,19 +32,29 @@ export const useUserQueryByDisplayName = ( unSubRef.current = null; } - const unSubFn = UserRepository.searchUserByDisplayName(params, (response) => { - setHasMore(response.hasNextPage || false); - setIsLoading(response.loading); - loadMoreRef.current = response.onNextPage || null; - setItems(response.data); - }); + if (!enabled) { + return; + } + + const unSubFn = UserRepository.searchUserByDisplayName( + { + displayName, + limit, + }, + (response) => { + setHasMore(response.hasNextPage || false); + setIsLoading(response.loading); + loadMoreRef.current = response.onNextPage || null; + setItems(response.data); + }, + ); unSubRef.current = unSubFn; return () => { unSubRef.current?.(); unSubRef.current = null; }; - }, [params]); + }, [displayName, limit, enabled]); return { users: items, diff --git a/src/v4/core/hooks/objects/usePost.ts b/src/v4/core/hooks/objects/usePost.ts index 47190cb98..dcd859c02 100644 --- a/src/v4/core/hooks/objects/usePost.ts +++ b/src/v4/core/hooks/objects/usePost.ts @@ -6,7 +6,7 @@ const usePost = (postId?: string) => { const { item, ...rest } = useLiveObject({ fetcher: PostRepository.getPost, params: postId as string, - shouldCall: () => !!postId, + shouldCall: !!postId, }); return { diff --git a/src/v4/core/hooks/objects/useUser.ts b/src/v4/core/hooks/objects/useUser.ts index 398a41da7..e8d379382 100644 --- a/src/v4/core/hooks/objects/useUser.ts +++ b/src/v4/core/hooks/objects/useUser.ts @@ -6,7 +6,7 @@ const useUser = (userId?: string | null) => { const { item, ...rest } = useLiveObject({ fetcher: UserRepository.getUser, params: userId, - shouldCall: () => !!userId, + shouldCall: !!userId, }); return { diff --git a/src/v4/core/hooks/subscriptions/useCommentSubscription.ts b/src/v4/core/hooks/subscriptions/useCommentSubscription.ts index 2b19a9c1e..c80a29e72 100644 --- a/src/v4/core/hooks/subscriptions/useCommentSubscription.ts +++ b/src/v4/core/hooks/subscriptions/useCommentSubscription.ts @@ -3,18 +3,18 @@ import useSubscription from './useSubscription'; export default function useCommentSubscription({ commentId, - shouldSubscribe = () => true, + shouldSubscribe = true, callback, }: { commentId?: string | null; - shouldSubscribe?: () => boolean; + shouldSubscribe?: boolean; callback?: Amity.Listener; }) { return useSubscription({ fetcher: CommentRepository.getComment, params: commentId, callback, - shouldSubscribe: () => !!commentId && shouldSubscribe(), + shouldSubscribe: !!commentId && shouldSubscribe, getSubscribedTopic: ({ data: comment }) => getCommentTopic(comment), }); } diff --git a/src/v4/core/hooks/subscriptions/useCommunitySubscription.ts b/src/v4/core/hooks/subscriptions/useCommunitySubscription.ts index 6397daad6..f56e89c38 100644 --- a/src/v4/core/hooks/subscriptions/useCommunitySubscription.ts +++ b/src/v4/core/hooks/subscriptions/useCommunitySubscription.ts @@ -4,19 +4,19 @@ import useSubscription from './useSubscription'; export default function useCommunitySubscription({ communityId, level, - shouldSubscribe = () => true, + shouldSubscribe = true, callback, }: { communityId?: string | null; level: Parameters<typeof getCommunityTopic>[1]; - shouldSubscribe?: () => boolean; + shouldSubscribe?: boolean; callback?: Amity.Listener; }) { return useSubscription({ fetcher: CommunityRepository.getCommunity, params: communityId, callback, - shouldSubscribe: () => !!communityId && shouldSubscribe(), + shouldSubscribe: !!communityId && shouldSubscribe, getSubscribedTopic: ({ data: community }) => getCommunityTopic(community, level), }); } diff --git a/src/v4/core/hooks/subscriptions/usePostSubscription.ts b/src/v4/core/hooks/subscriptions/usePostSubscription.ts index 212aa515a..0f79a4479 100644 --- a/src/v4/core/hooks/subscriptions/usePostSubscription.ts +++ b/src/v4/core/hooks/subscriptions/usePostSubscription.ts @@ -4,19 +4,19 @@ import useSubscription from './useSubscription'; export default function usePostSubscription({ postId, level, - shouldSubscribe = () => true, + shouldSubscribe = true, callback, }: { postId?: string | null; level: Parameters<typeof getPostTopic>[1]; - shouldSubscribe?: () => boolean; + shouldSubscribe?: boolean; callback?: Amity.Listener; }) { return useSubscription({ fetcher: PostRepository.getPost, params: postId, callback, - shouldSubscribe: () => !!postId && shouldSubscribe(), + shouldSubscribe: !!postId && shouldSubscribe, getSubscribedTopic: ({ data: post }) => getPostTopic(post, level), }); } diff --git a/src/v4/core/hooks/subscriptions/useSubscription.ts b/src/v4/core/hooks/subscriptions/useSubscription.ts index d3b5c515b..9694f4743 100644 --- a/src/v4/core/hooks/subscriptions/useSubscription.ts +++ b/src/v4/core/hooks/subscriptions/useSubscription.ts @@ -7,7 +7,7 @@ export default function useSubscription<TParams, TCallback, TConfig>({ params, callback = () => {}, options, - shouldSubscribe = () => true, + shouldSubscribe = true, getSubscribedTopic, }: { fetcher: ( @@ -18,7 +18,7 @@ export default function useSubscription<TParams, TCallback, TConfig>({ params: TParams | undefined | null; callback?: Amity.Listener; options?: Amity.LiveObjectOptions<TConfig>; - shouldSubscribe?: () => boolean; + shouldSubscribe?: boolean; getSubscribedTopic: (response: Amity.LiveObject<TCallback>) => string; }) { const { subscribe } = useSDKSubscribersConnector(); diff --git a/src/v4/core/hooks/subscriptions/useUserSubscription.ts b/src/v4/core/hooks/subscriptions/useUserSubscription.ts index 792f18805..14e9c83f5 100644 --- a/src/v4/core/hooks/subscriptions/useUserSubscription.ts +++ b/src/v4/core/hooks/subscriptions/useUserSubscription.ts @@ -4,19 +4,19 @@ import useSubscription from './useSubscription'; export default function useUserSubscription({ userId, level, - shouldSubscribe = () => true, + shouldSubscribe = true, callback, }: { userId?: string | null; level: Parameters<typeof getUserTopic>[1]; - shouldSubscribe?: () => boolean; + shouldSubscribe?: boolean; callback?: Amity.Listener; }) { return useSubscription({ fetcher: UserRepository.getUser, params: userId, callback, - shouldSubscribe: () => !!userId && shouldSubscribe(), + shouldSubscribe: !!userId && shouldSubscribe, getSubscribedTopic: ({ data: user }) => getUserTopic(user, level), }); } diff --git a/src/v4/core/hooks/useLiveCollection.ts b/src/v4/core/hooks/useLiveCollection.ts index dcb5ae63a..e53b58f4f 100644 --- a/src/v4/core/hooks/useLiveCollection.ts +++ b/src/v4/core/hooks/useLiveCollection.ts @@ -6,7 +6,7 @@ function useLiveCollection<TCallback, TParams>({ params, callback = () => {}, config, - shouldCall = () => true, + shouldCall = true, }: { fetcher: ( params: Amity.LiveCollectionParams<TParams>, @@ -16,7 +16,7 @@ function useLiveCollection<TCallback, TParams>({ params: Amity.LiveCollectionParams<TParams>; callback?: Amity.LiveCollectionCallback<TCallback>; config?: Amity.LiveCollectionConfig; - shouldCall?: () => boolean; + shouldCall?: boolean; }): { items: TCallback[]; isLoading: boolean; @@ -27,7 +27,7 @@ function useLiveCollection<TCallback, TParams>({ } { const { subscribe } = useSDKLiveCollectionConnector(); const [loadMoreHasBeenCalled, setLoadMoreHasBeenCalled] = useState(false); - const [isLoading, setIsLoading] = useState(shouldCall ? shouldCall() : true); + const [isLoading, setIsLoading] = useState(shouldCall ? shouldCall : true); const [items, setItems] = useState<TCallback[]>([]); const [error, setError] = useState<Error | null>(null); const [hasMore, setHasMore] = useState(false); @@ -42,7 +42,7 @@ function useLiveCollection<TCallback, TParams>({ const callbackFn = useCallback( (response) => { - if (!shouldCall()) return; + if (!shouldCall) return; if (response.data) setItems(response.data); setIsLoading(response.loading); setHasMore(response.hasNextPage); @@ -54,7 +54,7 @@ function useLiveCollection<TCallback, TParams>({ ); useEffect(() => { - if (!shouldCall()) return; + if (shouldCall) return; const { unsubscribe } = subscribe({ fetcher, params, diff --git a/src/v4/core/hooks/useLiveObject.ts b/src/v4/core/hooks/useLiveObject.ts index 78c145e31..7a1542e36 100644 --- a/src/v4/core/hooks/useLiveObject.ts +++ b/src/v4/core/hooks/useLiveObject.ts @@ -7,7 +7,7 @@ function useLiveObject<TParams, TCallback, TConfig>({ params, callback = () => {}, options, - shouldCall = () => true, + shouldCall = true, getSubscribedTopic, }: { fetcher: ( @@ -18,7 +18,7 @@ function useLiveObject<TParams, TCallback, TConfig>({ params: TParams | undefined | null; callback?: Amity.LiveObjectCallback<TCallback>; options?: Amity.LiveObjectOptions<TConfig>; - shouldCall?: () => boolean; + shouldCall?: boolean; getSubscribedTopic?: () => string; }) { const { subscribe } = useSDKLiveObjectConnector(); @@ -31,7 +31,7 @@ function useLiveObject<TParams, TCallback, TConfig>({ const callbackFn: Amity.LiveObjectCallback<TCallback> = useCallback( (response) => { - if (shouldCall && !shouldCall()) return; + if (!shouldCall) return; if (params == null) return; setIsLoading(response.loading); if (response.data) setItem(response.data); @@ -54,7 +54,7 @@ function useLiveObject<TParams, TCallback, TConfig>({ useEffect(() => { if (params == null) return; - if (shouldCall && !shouldCall()) return; + if (!shouldCall) return; const { unsubscribe } = subscribe({ fetcher, diff --git a/src/v4/core/natives/Button.tsx b/src/v4/core/natives/Button.tsx new file mode 100644 index 000000000..733aa0f85 --- /dev/null +++ b/src/v4/core/natives/Button.tsx @@ -0,0 +1,7 @@ +import React from 'react'; + +import { Button } from 'react-aria-components'; + +export type ButtonProps = React.ComponentProps<typeof Button>; + +export { Button }; diff --git a/src/v4/core/providers/CustomizationProvider.tsx b/src/v4/core/providers/CustomizationProvider.tsx index 9ddd2bcbe..6fde2c8ee 100644 --- a/src/v4/core/providers/CustomizationProvider.tsx +++ b/src/v4/core/providers/CustomizationProvider.tsx @@ -213,7 +213,7 @@ export const defaultConfig: DefaultConfig = { '*/edit_comment_component/*': { theme: {}, }, - '*/edit_comment_component/cancel_button': { + '*/edit_comment_component/edit_cancel_button': { cancel_icon: '', cancel_button_text: 'cancel', background_color: '#1243EE', @@ -231,7 +231,7 @@ export const defaultConfig: DefaultConfig = { done_button_text: 'Done', background_color: '#1243EE', }, - '*/hyper_link_config_component/cancel_button': { + '*/hyper_link_config_component/edit_cancel_button': { cancel_icon: '', cancel_button_text: 'Cancel', }, diff --git a/src/v4/social/components/CommentEdition/CommentEdition.tsx b/src/v4/social/components/CommentEdition/CommentEdition.tsx index 41d5aa571..7bc126a63 100644 --- a/src/v4/social/components/CommentEdition/CommentEdition.tsx +++ b/src/v4/social/components/CommentEdition/CommentEdition.tsx @@ -5,8 +5,9 @@ import { ButtonContainer, CommentEditContainer, CommentEditTextarea } from './st import { QueryMentioneesFnType } from '~/v4/chat/hooks/useMention'; import { useTheme } from 'styled-components'; -import { CancelButton, SaveButton } from '../../elements'; +import { SaveButton } from '../../elements'; import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; +import { EditCancelButton } from '../../elements/EditCancelButton/EditCancelButton'; interface CommentEditionProps { pageId?: '*'; @@ -49,7 +50,7 @@ export const CommentEdition = ({ onChange={(data) => onChange?.(data)} /> <ButtonContainer> - <CancelButton pageId={pageId} componentId={componentId} onClick={onCancel} /> + <EditCancelButton pageId={pageId} componentId={componentId} onPress={onCancel} /> <SaveButton pageId={pageId} componentId={componentId} onClick={onSubmit} /> </ButtonContainer> </CommentEditContainer> diff --git a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx index 2e16cc813..b2f9ee187 100644 --- a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx +++ b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx @@ -13,8 +13,8 @@ import { Button } from '~/v4/core/components/Button'; import useSDK from '~/v4/core/hooks/useSDK'; import Trash from '~/v4/social/icons/trash'; import { useAmityComponent } from '~/v4/core/hooks/uikit'; -import { CancelButton } from '~/v4/social/elements/CancelButton'; import { DoneButton } from '~/v4/social/elements/DoneButton'; +import { EditCancelButton } from '../../elements/EditCancelButton/EditCancelButton'; interface HyperLinkConfigProps { pageId: string; @@ -164,11 +164,15 @@ export const HyperLinkConfig = ({ className={styles.bottomSheet} > <div className={styles.headerContainer}> - <CancelButton pageId="*" componentId={componentId} onClick={handleClose} /> + <EditCancelButton pageId={pageId} componentId={componentId} onPress={handleClose} /> <Typography.Title> {formatMessage({ id: 'storyCreation.hyperlink.bottomSheet.title' })} </Typography.Title> - <DoneButton pageId="*" componentId={componentId} onClick={handleSubmit(onSubmitForm)} /> + <DoneButton + pageId={pageId} + componentId={componentId} + onClick={handleSubmit(onSubmitForm)} + /> </div> <div className={styles.divider} /> <div className={styles.hyperlinkFormContainer}> diff --git a/src/v4/social/components/TopNavigation/TopNavigation.tsx b/src/v4/social/components/TopNavigation/TopNavigation.tsx index 12f92b7c5..9f2360b96 100644 --- a/src/v4/social/components/TopNavigation/TopNavigation.tsx +++ b/src/v4/social/components/TopNavigation/TopNavigation.tsx @@ -36,7 +36,7 @@ export function TopNavigation({ <GlobalSearchButton pageId={pageId} componentId={componentId} - onClick={() => goToSocialGlobalSearchPage()} + onPress={onGlobalSearchButtonClick} /> <PostCreationButton pageId={pageId} diff --git a/src/v4/social/components/TopSearchBar/TopSearchBar.tsx b/src/v4/social/components/TopSearchBar/TopSearchBar.tsx index e963aa063..a4d53a4c9 100644 --- a/src/v4/social/components/TopSearchBar/TopSearchBar.tsx +++ b/src/v4/social/components/TopSearchBar/TopSearchBar.tsx @@ -1,9 +1,7 @@ import React, { useEffect, useState } from 'react'; -import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; import { ClearButton } from '~/v4/social/elements/ClearButton'; import { CancelButton } from '~/v4/social/elements/CancelButton'; import { SearchIcon } from '~/v4/social/elements/SearchIcon'; -import { useGenerateStylesShadeColors } from '~/v4/core/providers/ThemeProvider'; import styles from './TopSearchBar.module.css'; import { useAmityComponent } from '~/v4/core/hooks/uikit'; @@ -16,11 +14,10 @@ export interface TopSearchBarProps { export function TopSearchBar({ pageId = '*', search }: TopSearchBarProps) { const componentId = 'top_search_bar'; - const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = - useAmityComponent({ - pageId, - componentId, - }); + const { config, isExcluded, themeStyles } = useAmityComponent({ + pageId, + componentId, + }); const { onBack } = useNavigation(); @@ -54,13 +51,13 @@ export function TopSearchBar({ pageId = '*', search }: TopSearchBarProps) { componentId={componentId} defaultClassName={styles.topSearchBar__clearButton} imgClassName={styles.topSearchBar__clearButton__img} - onClick={() => { + onPress={() => { setSearchValue(''); }} /> ) : null} </div> - <CancelButton pageId={pageId} componentId={componentId} onClick={() => onBack()} /> + <CancelButton pageId={pageId} componentId={componentId} onPress={() => onBack()} /> </div> ); } diff --git a/src/v4/social/elements/AspectRatioButton/AspectRatioButton.tsx b/src/v4/social/elements/AspectRatioButton/AspectRatioButton.tsx index cc1a057de..47f1b3d34 100644 --- a/src/v4/social/elements/AspectRatioButton/AspectRatioButton.tsx +++ b/src/v4/social/elements/AspectRatioButton/AspectRatioButton.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import clsx from 'clsx'; import { IconComponent } from '~/v4/core/IconComponent'; import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { Button, ButtonProps } from '~/v4/core/natives/Button'; import styles from './AspectRatioButton.module.css'; @@ -20,7 +20,7 @@ interface AspectRatioButtonProps { componentId?: string; defaultIconClassName?: string; imgIconClassName?: string; - onPress: () => void; + onPress: ButtonProps['onPress']; } export function AspectRatioButton({ @@ -40,14 +40,13 @@ export function AspectRatioButton({ if (isExcluded) return null; return ( - <IconComponent - data-qa-anchor={accessibilityId} - className={clsx(styles.aspectRatioButton)} - onPress={onPress} - defaultIcon={() => <AspectRatioSvg className={clsx(defaultIconClassName)} />} - imgIcon={() => <img src={config.icon} alt={uiReference} className={clsx(imgIconClassName)} />} - defaultIconName={defaultConfig.icon} - configIconName={config.icon} - /> + <Button className={styles.aspectRatioButton} data-qa-anchor={accessibilityId} onPress={onPress}> + <IconComponent + defaultIcon={() => <AspectRatioSvg className={defaultIconClassName} />} + imgIcon={() => <img src={config.icon} alt={uiReference} className={imgIconClassName} />} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} + /> + </Button> ); } diff --git a/src/v4/social/elements/BackButton/BackButton.tsx b/src/v4/social/elements/BackButton/BackButton.tsx index f752063be..65ed1a69a 100644 --- a/src/v4/social/elements/BackButton/BackButton.tsx +++ b/src/v4/social/elements/BackButton/BackButton.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { IconComponent } from '~/v4/core/IconComponent'; import { useAmityElement } from '~/v4/core/hooks/uikit'; -import clsx from 'clsx'; +import { Button, ButtonProps } from '~/v4/core/natives/Button'; import styles from './BackButton.module.css'; @@ -27,7 +27,7 @@ interface BackButtonProps { componentId?: string; defaultClassName?: string; imgClassName?: string; - onPress?: () => void; + onPress?: ButtonProps['onPress']; } export const BackButton = ({ @@ -35,7 +35,7 @@ export const BackButton = ({ componentId = '*', defaultClassName, imgClassName, - onPress = () => {}, + onPress, }: BackButtonProps) => { const elementId = 'back_button'; const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = @@ -48,15 +48,14 @@ export const BackButton = ({ if (isExcluded) return null; return ( - <IconComponent - data-qa-anchor={accessibilityId} - className={clsx(styles.backButton, defaultClassName)} - defaultIcon={() => <BackButtonSvg />} - imgIcon={() => <img src={config.icon} alt={uiReference} className={clsx(imgClassName)} />} - defaultIconName={defaultConfig.icon} - configIconName={config.icon} - onPress={onPress} - style={themeStyles} - /> + <Button className={styles.backButton} style={themeStyles} onPress={onPress}> + <IconComponent + data-qa-anchor={accessibilityId} + defaultIcon={() => <BackButtonSvg className={defaultClassName} />} + imgIcon={() => <img src={config.icon} alt={uiReference} className={imgClassName} />} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} + /> + </Button> ); }; diff --git a/src/v4/social/elements/CancelButton/CancelButton.module.css b/src/v4/social/elements/CancelButton/CancelButton.module.css index aca2bba95..108d73af5 100644 --- a/src/v4/social/elements/CancelButton/CancelButton.module.css +++ b/src/v4/social/elements/CancelButton/CancelButton.module.css @@ -1,17 +1,5 @@ -button { - appearance: none; - border-radius: 0; - text-align: inherit; - background: none; - box-shadow: none; - padding: 0; +.cancelButton { cursor: pointer; - border: none; - color: inherit; - font: inherit; -} - -.clearButton { - color: var(--asc-color-base-default); + color: var(--asc-color-primary-default); background-color: var(--asc-color-base-background); } diff --git a/src/v4/social/elements/CancelButton/CancelButton.tsx b/src/v4/social/elements/CancelButton/CancelButton.tsx index 7cd6d74c2..edcd19bf7 100644 --- a/src/v4/social/elements/CancelButton/CancelButton.tsx +++ b/src/v4/social/elements/CancelButton/CancelButton.tsx @@ -1,21 +1,19 @@ -import { config } from 'process'; import React from 'react'; import { Typography } from '~/v4/core/components'; import { useAmityElement } from '~/v4/core/hooks/uikit'; -import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; -import { useGenerateStylesShadeColors } from '~/v4/core/providers/ThemeProvider'; +import { Button, ButtonProps } from '~/v4/core/natives/Button'; import styles from './CancelButton.module.css'; interface CancelButtonProps { pageId?: string; componentId?: string; - onClick?: (e: React.MouseEvent) => void; + onPress?: ButtonProps['onPress']; } export const CancelButton = ({ pageId = '*', componentId = '*', - onClick = () => {}, + onPress = () => {}, }: CancelButtonProps) => { const elementId = 'cancel_button'; const { accessibilityId, config, isExcluded, themeStyles } = useAmityElement({ @@ -27,8 +25,13 @@ export const CancelButton = ({ if (isExcluded) return null; return ( - <button data-qa-anchor={accessibilityId} style={themeStyles} onClick={onClick}> - <Typography.Body className={styles.clearButton}>{config.cancel_button_text}</Typography.Body> - </button> + <Button + data-qa-anchor={accessibilityId} + className={styles.cancelButton} + style={themeStyles} + onPress={onPress} + > + <Typography.Body>{config.text}</Typography.Body> + </Button> ); }; diff --git a/src/v4/social/elements/ClearButton/ClearButton.tsx b/src/v4/social/elements/ClearButton/ClearButton.tsx index 736bc6ff9..02b6a5310 100644 --- a/src/v4/social/elements/ClearButton/ClearButton.tsx +++ b/src/v4/social/elements/ClearButton/ClearButton.tsx @@ -3,6 +3,7 @@ import { IconComponent } from '~/v4/core/IconComponent'; import { useAmityElement } from '~/v4/core/hooks/uikit'; import styles from './ClearButton.module.css'; +import { Button, ButtonProps } from '~/v4/core/natives/Button'; function ClearButtonSvg(props: React.SVGProps<SVGSVGElement>) { return ( @@ -24,7 +25,7 @@ interface ClearButtonProps { componentId?: string; defaultClassName?: string; imgClassName?: string; - onPress?: () => void; + onPress?: ButtonProps['onPress']; } export const ClearButton = ({ @@ -32,7 +33,7 @@ export const ClearButton = ({ componentId = '*', defaultClassName, imgClassName, - onPress = () => {}, + onPress, }: ClearButtonProps) => { const elementId = 'clear_button'; const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = @@ -45,14 +46,18 @@ export const ClearButton = ({ if (isExcluded) return null; return ( - <button data-qa-anchor={accessibilityId} className={styles.clearButton} style={themeStyles}> + <Button + data-qa-anchor={accessibilityId} + className={styles.clearButton} + style={themeStyles} + onPress={onPress} + > <IconComponent - onPress={onPress} defaultIcon={() => <ClearButtonSvg className={defaultClassName} />} imgIcon={() => <img src={config.icon} alt={uiReference} className={imgClassName} />} defaultIconName={defaultConfig.icon} configIconName={config.icon} /> - </button> + </Button> ); }; diff --git a/src/v4/social/elements/CloseButton/CloseButton.module.css b/src/v4/social/elements/CloseButton/CloseButton.module.css index b2cfb2de1..65eddf849 100644 --- a/src/v4/social/elements/CloseButton/CloseButton.module.css +++ b/src/v4/social/elements/CloseButton/CloseButton.module.css @@ -20,9 +20,12 @@ } } +.closeButton__icon { + fill: var(--asc-color-base-default); +} + .closeButton { cursor: pointer; - fill: var(--asc-color-base-default); width: 1.5rem; height: 1.25rem; } diff --git a/src/v4/social/elements/CloseButton/CloseButton.tsx b/src/v4/social/elements/CloseButton/CloseButton.tsx index ce4d1492e..abb54d952 100644 --- a/src/v4/social/elements/CloseButton/CloseButton.tsx +++ b/src/v4/social/elements/CloseButton/CloseButton.tsx @@ -3,6 +3,7 @@ import styles from './CloseButton.module.css'; import { useAmityElement } from '~/v4/core/hooks/uikit'; import clsx from 'clsx'; import { IconComponent } from '~/v4/core/IconComponent'; +import { Button, ButtonProps } from '~/v4/core/natives/Button'; const CloseIconSVG = (props: React.SVGProps<SVGSVGElement>) => ( <svg @@ -27,8 +28,7 @@ const CloseIconSVG = (props: React.SVGProps<SVGSVGElement>) => ( interface CloseButtonProps { pageId?: string; componentId?: string; - onPress?: () => void; - 'data-qa-anchor'?: string; + onPress?: ButtonProps['onPress']; defaultClassName?: string; imgClassName?: string; } @@ -41,7 +41,7 @@ export const CloseButton = ({ imgClassName, }: CloseButtonProps) => { const elementId = 'close_button'; - const { isExcluded, config, uiReference } = useAmityElement({ + const { isExcluded, config, uiReference, accessibilityId } = useAmityElement({ pageId, componentId, elementId, @@ -50,12 +50,14 @@ export const CloseButton = ({ if (isExcluded) return null; return ( - <IconComponent - onPress={onPress} - data-qa-anchor="close_button" - defaultIcon={() => <CloseIconSVG className={clsx(styles.closeButton, defaultClassName)} />} - imgIcon={() => <img src={config.icon} alt={uiReference} className={imgClassName} />} - configIconName={config.icon} - /> + <Button className={styles.closeButton} data-qa-anchor={accessibilityId} onPress={onPress}> + <IconComponent + defaultIcon={() => ( + <CloseIconSVG className={clsx(styles.closeButton__icon, defaultClassName)} /> + )} + imgIcon={() => <img src={config.icon} alt={uiReference} className={imgClassName} />} + configIconName={config.icon} + /> + </Button> ); }; diff --git a/src/v4/social/elements/CommentBubbleDeleted/CommentBubbleDeleted.tsx b/src/v4/social/elements/CommentBubbleDeleted/CommentBubbleDeleted.tsx index 0feb50794..9185f705c 100644 --- a/src/v4/social/elements/CommentBubbleDeleted/CommentBubbleDeleted.tsx +++ b/src/v4/social/elements/CommentBubbleDeleted/CommentBubbleDeleted.tsx @@ -33,31 +33,35 @@ export function CommentBubbleDeleted({ imgIconClassName, }: CommentBubbleDeletedProps) { const elementId = 'comment_bubble_deleted_view'; - const { accessibilityId, config, defaultConfig, isExcluded, uiReference } = useAmityElement({ - pageId, - componentId, - elementId, - }); + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityElement({ + pageId, + componentId, + elementId, + }); if (isExcluded) return null; return ( - <IconComponent - className={clsx(styles.commentBubbleDeletedBlock)} - defaultIcon={() => ( - <div - className={clsx(styles.commentBubbleDeleted, defaultIconClassName)} - data-qa-anchor={accessibilityId} - > - <CommentBubbleDeletedSvg - className={clsx(styles.commentBubbleDeletedIcon, defaultIconClassName)} - /> - <Typography.Caption>{config.text}</Typography.Caption> - </div> - )} - imgIcon={() => <img src={config.icon} alt={uiReference} className={clsx(imgIconClassName)} />} - defaultIconName={defaultConfig.icon} - configIconName={config.icon} - /> + <div style={themeStyles} data-qa-anchor={accessibilityId}> + <IconComponent + defaultIcon={() => ( + <div + className={clsx(styles.commentBubbleDeleted, defaultIconClassName)} + data-qa-anchor={accessibilityId} + > + <CommentBubbleDeletedSvg + className={clsx(styles.commentBubbleDeletedIcon, defaultIconClassName)} + /> + <Typography.Caption>{config.text}</Typography.Caption> + </div> + )} + imgIcon={() => ( + <img src={config.icon} alt={uiReference} className={clsx(imgIconClassName)} /> + )} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} + /> + </div> ); } diff --git a/src/v4/social/elements/CommentButton/CommentButton.tsx b/src/v4/social/elements/CommentButton/CommentButton.tsx index e8f3d873e..e51885c99 100644 --- a/src/v4/social/elements/CommentButton/CommentButton.tsx +++ b/src/v4/social/elements/CommentButton/CommentButton.tsx @@ -5,6 +5,7 @@ import { IconComponent } from '~/v4/core/IconComponent'; import { useAmityElement } from '~/v4/core/hooks/uikit'; import styles from './CommentButton.module.css'; +import { Button, ButtonProps } from '~/v4/core/natives/Button'; const CommentSvg = (props: React.SVGProps<SVGSVGElement>) => ( <svg @@ -26,14 +27,13 @@ interface CommentButtonProps { className?: string; defaultIconClassName?: string; imgIconClassName?: string; - onPress?: () => void; + onPress?: ButtonProps['onPress']; } export function CommentButton({ pageId = '*', componentId = '*', commentsCount, - className = '', defaultIconClassName, imgIconClassName, onPress = () => {}, @@ -48,21 +48,21 @@ export function CommentButton({ if (isExcluded) return null; return ( - <IconComponent - className={clsx(className)} - data-qa-anchor={accessibilityId} - onPress={onPress} - defaultIcon={() => ( - <div className={clsx(styles.commentButton)}> - <CommentSvg className={clsx(styles.commentButton__icon, defaultIconClassName)} /> - <Typography.BodyBold className={styles.commentButton__text}> - {typeof commentsCount === 'number' ? commentsCount : config.text} - </Typography.BodyBold> - </div> - )} - imgIcon={() => <img src={config.icon} alt={uiReference} className={imgIconClassName} />} - defaultIconName={defaultConfig.icon} - configIconName={config.icon} - /> + <Button onPress={onPress}> + <IconComponent + data-qa-anchor={accessibilityId} + defaultIcon={() => ( + <div className={clsx(styles.commentButton)}> + <CommentSvg className={clsx(styles.commentButton__icon, defaultIconClassName)} /> + <Typography.BodyBold className={styles.commentButton__text}> + {typeof commentsCount === 'number' ? commentsCount : config.text} + </Typography.BodyBold> + </div> + )} + imgIcon={() => <img src={config.icon} alt={uiReference} className={imgIconClassName} />} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} + /> + </Button> ); } diff --git a/src/v4/social/elements/CreateNewStoryButton/CreateNewStoryButton.tsx b/src/v4/social/elements/CreateNewStoryButton/CreateNewStoryButton.tsx index 4ffb62af4..ac1a4d370 100644 --- a/src/v4/social/elements/CreateNewStoryButton/CreateNewStoryButton.tsx +++ b/src/v4/social/elements/CreateNewStoryButton/CreateNewStoryButton.tsx @@ -4,6 +4,7 @@ import { useAmityElement } from '~/v4/core/hooks/uikit'; import clsx from 'clsx'; import styles from './CreateNewStoryButton.module.css'; +import { Button } from '~/v4/core/natives/Button'; const CreateNewStoryButtonSvg = (props: React.SVGProps<SVGSVGElement>) => ( <svg @@ -48,15 +49,18 @@ export const CreateNewStoryButton = ({ if (isExcluded) return null; return ( - <IconComponent - data-qa-anchor={accessibilityId} + <Button + style={themeStyles} className={clsx(styles.createNewStoryIcon, defaultClassName)} - defaultIcon={() => <CreateNewStoryButtonSvg />} - imgIcon={() => <img src={config.icon} alt={uiReference} className={clsx(imgClassName)} />} - defaultIconName={defaultConfig.icon} - configIconName={config.icon} + data-qa-anchor={accessibilityId} onPress={onPress} - style={themeStyles} - /> + > + <IconComponent + defaultIcon={() => <CreateNewStoryButtonSvg />} + imgIcon={() => <img src={config.icon} alt={uiReference} className={clsx(imgClassName)} />} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} + /> + </Button> ); }; diff --git a/src/v4/social/elements/EditCancelButton/EditCancelButton.module.css b/src/v4/social/elements/EditCancelButton/EditCancelButton.module.css new file mode 100644 index 000000000..c1012f9dd --- /dev/null +++ b/src/v4/social/elements/EditCancelButton/EditCancelButton.module.css @@ -0,0 +1,5 @@ +.editCancelButton { + cursor: pointer; + color: var(--asc-color-base-default); + background-color: var(--asc-color-base-background); +} diff --git a/src/v4/social/elements/EditCancelButton/EditCancelButton.stories.tsx b/src/v4/social/elements/EditCancelButton/EditCancelButton.stories.tsx new file mode 100644 index 000000000..23c0c7943 --- /dev/null +++ b/src/v4/social/elements/EditCancelButton/EditCancelButton.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { EditCancelButton } from './EditCancelButton'; + +export default { + title: 'v4-social/elements/EditCancelButton', +}; + +export const EditCancelButtonStory = { + render: () => { + return <EditCancelButton />; + }, + + name: 'EditCancelButton', +}; diff --git a/src/v4/social/elements/EditCancelButton/EditCancelButton.tsx b/src/v4/social/elements/EditCancelButton/EditCancelButton.tsx new file mode 100644 index 000000000..2350e2156 --- /dev/null +++ b/src/v4/social/elements/EditCancelButton/EditCancelButton.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { Typography } from '~/v4/core/components'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { Button, ButtonProps } from '~/v4/core/natives/Button'; +import styles from './EditCancelButton.module.css'; + +interface EditCancelButtonProps { + pageId?: string; + componentId?: string; + onPress?: ButtonProps['onPress']; +} + +export const EditCancelButton = ({ + pageId = '*', + componentId = '*', + onPress = () => {}, +}: EditCancelButtonProps) => { + const elementId = 'edit_cancel_button'; + const { accessibilityId, config, isExcluded, themeStyles } = useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + return ( + <Button + data-qa-anchor={accessibilityId} + className={styles.editCancelButton} + style={{ + ...themeStyles, + backgroundColor: config.background_color as string | undefined, + }} + onPress={onPress} + > + <Typography.Body>{config.cancel_button_text}</Typography.Body> + </Button> + ); +}; diff --git a/src/v4/social/elements/EditCancelButton/index.tsx b/src/v4/social/elements/EditCancelButton/index.tsx new file mode 100644 index 000000000..91243b5d2 --- /dev/null +++ b/src/v4/social/elements/EditCancelButton/index.tsx @@ -0,0 +1 @@ +export { EditCancelButton } from './EditCancelButton'; diff --git a/src/v4/social/elements/GlobalSearchButton/GlobalSearchButton.module.css b/src/v4/social/elements/GlobalSearchButton/GlobalSearchButton.module.css index 9f2ba0aa0..86f6c3511 100644 --- a/src/v4/social/elements/GlobalSearchButton/GlobalSearchButton.module.css +++ b/src/v4/social/elements/GlobalSearchButton/GlobalSearchButton.module.css @@ -1,4 +1,8 @@ .globalSearchButton { + cursor: pointer; +} + +.globalSearchButton__icon { background-color: var(--asc-color-secondary-shade4); display: flex; justify-content: center; @@ -8,7 +12,7 @@ height: 2rem; } -.globalSearchButton__icon { +.globalSearchButton__svg { fill: var(--asc-color-secondary-default); width: 1rem; height: 1rem; diff --git a/src/v4/social/elements/GlobalSearchButton/GlobalSearchButton.tsx b/src/v4/social/elements/GlobalSearchButton/GlobalSearchButton.tsx index 1f65d87a0..36c32d393 100644 --- a/src/v4/social/elements/GlobalSearchButton/GlobalSearchButton.tsx +++ b/src/v4/social/elements/GlobalSearchButton/GlobalSearchButton.tsx @@ -1,9 +1,8 @@ -import clsx from 'clsx'; import React from 'react'; +import clsx from 'clsx'; import { useAmityElement } from '~/v4/core/hooks/uikit'; import { IconComponent } from '~/v4/core/IconComponent'; -import { getDefaultConfig, useCustomization } from '~/v4/core/providers/CustomizationProvider'; -import { useGenerateStylesShadeColors } from '~/v4/core/providers/ThemeProvider'; +import { Button, ButtonProps } from '~/v4/core/natives/Button'; import styles from './GlobalSearchButton.module.css'; const GlobalSearchSvg = (props: React.SVGProps<SVGSVGElement>) => ( @@ -24,7 +23,7 @@ export interface GlobalSearchButtonProps { componentId?: string; defaultClassName?: string; imgClassName?: string; - onClick?: (e: React.MouseEvent) => void; + onPress: ButtonProps['onPress']; } export function GlobalSearchButton({ @@ -32,7 +31,7 @@ export function GlobalSearchButton({ componentId = '*', defaultClassName, imgClassName, - onClick, + onPress, }: GlobalSearchButtonProps) { const elementId = 'global_search_button'; const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = @@ -45,29 +44,30 @@ export function GlobalSearchButton({ if (isExcluded) return null; return ( - <IconComponent - defaultIcon={() => ( - <div - style={themeStyles} - className={styles.globalSearchButton} - onClick={onClick} - data-qa-anchor={accessibilityId} - > - <GlobalSearchSvg className={clsx(styles.globalSearchButton__icon, defaultClassName)} /> - </div> - )} - imgIcon={() => ( - <img - style={themeStyles} - src={config.icon} - alt={uiReference} - className={clsx(styles.globalSearchButton__icon, imgClassName)} - onClick={onClick} - data-qa-anchor={accessibilityId} - /> - )} - defaultIconName={defaultConfig.icon} - configIconName={config.icon} - /> + <Button + style={themeStyles} + data-qa-anchor={accessibilityId} + className={styles.globalSearchButton} + onPress={onPress} + > + <IconComponent + defaultIcon={() => ( + <div className={styles.globalSearchButton__icon}> + <GlobalSearchSvg className={clsx(styles.globalSearchButton__svg, defaultClassName)} /> + </div> + )} + imgIcon={() => ( + <img + style={themeStyles} + src={config.icon} + alt={uiReference} + className={clsx(styles.globalSearchButton__icon, imgClassName)} + data-qa-anchor={accessibilityId} + /> + )} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} + /> + </Button> ); } diff --git a/src/v4/social/elements/HyperLinkButton/HyperLinkButton.tsx b/src/v4/social/elements/HyperLinkButton/HyperLinkButton.tsx index d99fb8841..e67c6b166 100644 --- a/src/v4/social/elements/HyperLinkButton/HyperLinkButton.tsx +++ b/src/v4/social/elements/HyperLinkButton/HyperLinkButton.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { useAmityElement } from '~/v4/core/hooks/uikit'; import { IconComponent } from '~/v4/core/IconComponent'; import clsx from 'clsx'; +import { Button, ButtonProps } from '~/v4/core/natives/Button'; import styles from './HyperLinkButton.module.css'; @@ -29,7 +30,7 @@ interface HyperLinkButtonProps { componentId?: string; defaultClassName?: string; imgClassName?: string; - onPress: (e: React.MouseEvent) => void; + onPress: ButtonProps['onPress']; } export const HyperLinkButton = ({ @@ -50,15 +51,15 @@ export const HyperLinkButton = ({ if (isExcluded) return null; return ( - <IconComponent - data-qa-anchor={accessibilityId} - className={clsx(styles.hyperLinkButton, defaultClassName)} - defaultIcon={() => <HyperLinkButtonSvg />} - imgIcon={() => <img src={config.icon} alt={uiReference} className={clsx(imgClassName)} />} - defaultIconName={defaultConfig.icon} - configIconName={config.icon} - onPress={onPress} - style={themeStyles} - /> + <Button onPress={onPress} style={themeStyles} data-qa-anchor={accessibilityId}> + <IconComponent + defaultIcon={() => ( + <HyperLinkButtonSvg className={clsx(styles.hyperLinkButton, defaultClassName)} /> + )} + imgIcon={() => <img src={config.icon} alt={uiReference} className={imgClassName} />} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} + /> + </Button> ); }; diff --git a/src/v4/social/elements/OverflowMenuButton/OverflowMenuButton.tsx b/src/v4/social/elements/OverflowMenuButton/OverflowMenuButton.tsx index 51f12498a..64ddb5ac8 100644 --- a/src/v4/social/elements/OverflowMenuButton/OverflowMenuButton.tsx +++ b/src/v4/social/elements/OverflowMenuButton/OverflowMenuButton.tsx @@ -1,7 +1,8 @@ -import clsx from 'clsx'; import React from 'react'; import { useAmityElement } from '~/v4/core/hooks/uikit'; import { IconComponent } from '~/v4/core/IconComponent'; +import { Button, ButtonProps } from '~/v4/core/natives/Button'; + import styles from './OverflowMenuButton.module.css'; const OverflowMenuSvg = (props: React.SVGProps<SVGSVGElement>) => { @@ -25,7 +26,7 @@ const OverflowMenuSvg = (props: React.SVGProps<SVGSVGElement>) => { interface OverflowMenuButtonProps { pageId?: string; componentId?: string; - onPress?: () => void; + onPress?: ButtonProps['onPress']; defaultClassName?: string; imgClassName?: string; 'data-qa-anchor'?: string; @@ -39,21 +40,25 @@ export const OverflowMenuButton = ({ imgClassName, }: OverflowMenuButtonProps) => { const elementId = 'overflow_menu'; - const { config, defaultConfig, uiReference } = useAmityElement({ + const { config, defaultConfig, uiReference, themeStyles, accessibilityId } = useAmityElement({ pageId, componentId, elementId, }); return ( - <IconComponent + <Button + style={themeStyles} + data-qa-anchor={accessibilityId} + className={styles.overflowMenuIcon} onPress={onPress} - defaultIcon={() => ( - <OverflowMenuSvg className={clsx(styles.overflowMenuIcon, defaultClassName)} /> - )} - imgIcon={() => <img src={config.icon} alt={uiReference} className={imgClassName} />} - defaultIconName={defaultConfig.icon} - configIconName={config.icon} - /> + > + <IconComponent + defaultIcon={() => <OverflowMenuSvg className={defaultClassName} />} + imgIcon={() => <img src={config.icon} alt={uiReference} className={imgClassName} />} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} + /> + </Button> ); }; diff --git a/src/v4/social/elements/SearchIcon/SearchIcon.tsx b/src/v4/social/elements/SearchIcon/SearchIcon.tsx index fa89fc0fa..12d2b60ca 100644 --- a/src/v4/social/elements/SearchIcon/SearchIcon.tsx +++ b/src/v4/social/elements/SearchIcon/SearchIcon.tsx @@ -29,12 +29,11 @@ export function SearchIcon({ imgClassName, }: SearchIconProps) { const elementId = 'search_icon'; - const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = - useAmityElement({ - pageId, - componentId, - elementId, - }); + const { config, defaultConfig, isExcluded, uiReference, themeStyles } = useAmityElement({ + pageId, + componentId, + elementId, + }); if (isExcluded) return null; diff --git a/src/v4/social/elements/SpeakerButton/SpeakerButton.module.css b/src/v4/social/elements/SpeakerButton/SpeakerButton.module.css index 0bb2463b9..8e3218f31 100644 --- a/src/v4/social/elements/SpeakerButton/SpeakerButton.module.css +++ b/src/v4/social/elements/SpeakerButton/SpeakerButton.module.css @@ -1,5 +1,4 @@ .speakerButton { - fill: var(--asc-color-white); position: absolute; top: 96px; left: 24px; @@ -7,6 +6,7 @@ } .speakerButton__icon { + fill: var(--asc-color-white); width: 1.5rem; height: 1.5rem; } diff --git a/src/v4/social/elements/SpeakerButton/SpeakerButton.tsx b/src/v4/social/elements/SpeakerButton/SpeakerButton.tsx index 28bbf5296..2d31bf835 100644 --- a/src/v4/social/elements/SpeakerButton/SpeakerButton.tsx +++ b/src/v4/social/elements/SpeakerButton/SpeakerButton.tsx @@ -2,6 +2,7 @@ import React from 'react'; import clsx from 'clsx'; import { IconComponent } from '~/v4/core/IconComponent'; import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { Button, ButtonProps } from '~/v4/core/natives/Button'; import styles from './SpeakerButton.module.css'; @@ -47,7 +48,7 @@ interface SpeakerButtonProps { componentId?: string; defaultIconClassName?: string; imgIconClassName?: string; - onPress: () => void; + onPress: ButtonProps['onPress']; } export function SpeakerButton({ @@ -68,14 +69,22 @@ export function SpeakerButton({ if (isExcluded) return null; return ( - <IconComponent - data-qa-anchor={accessibilityId} - className={clsx(styles.speakerButton)} - onPress={onPress} - defaultIcon={() => (isMuted ? <SpeakerUnmuteSvg /> : <SpeakerMuteSvg />)} - imgIcon={() => <img src={config.icon} alt={uiReference} className={clsx(imgIconClassName)} />} - defaultIconName={defaultConfig.icon} - configIconName={config.icon} - /> + <Button className={clsx(styles.speakerButton)} onPress={onPress}> + <IconComponent + data-qa-anchor={accessibilityId} + defaultIcon={() => + isMuted ? ( + <SpeakerUnmuteSvg className={styles.speakerButton__icon} /> + ) : ( + <SpeakerMuteSvg className={styles.speakerButton__icon} /> + ) + } + imgIcon={() => ( + <img src={config.icon} alt={uiReference} className={clsx(imgIconClassName)} /> + )} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} + /> + </Button> ); } diff --git a/src/v4/social/elements/StoryCommentButton/StoryCommentButton.tsx b/src/v4/social/elements/StoryCommentButton/StoryCommentButton.tsx index 8448a095e..0b387d9b8 100644 --- a/src/v4/social/elements/StoryCommentButton/StoryCommentButton.tsx +++ b/src/v4/social/elements/StoryCommentButton/StoryCommentButton.tsx @@ -5,6 +5,7 @@ import { PressEvent } from 'react-aria'; import { Typography } from '~/v4/core/components/index'; import { useAmityElement } from '~/v4/core/hooks/uikit/index'; import { IconComponent } from '~/v4/core/IconComponent'; +import { Button, ButtonProps } from '~/v4/core/natives/Button'; import styles from './StoryCommentButton.module.css'; @@ -28,10 +29,10 @@ const StoryCommentSvg = (props: React.SVGProps<SVGSVGElement>) => { interface StoryCommentButtonProps { commentsCount: number; - onPress: (e: PressEvent) => void; pageId?: string; componentId?: string; imgClassName?: string; + onPress: ButtonProps['onPress']; } export const StoryCommentButton = ({ @@ -52,26 +53,30 @@ export const StoryCommentButton = ({ if (isExcluded) return null; return ( - <IconComponent + <Button data-qa-anchor={accessibilityId} - defaultIconName={defaultConfig.icon} - configIconName={config.icon} - className={clsx(styles.storyCommentButton)} - imgIcon={() => ( - <img - src={config.icon} - alt={uiReference} - className={clsx(imgClassName)} - style={themeStyles} - /> - )} - defaultIcon={() => ( - <div className={clsx(styles.storyCommentIcon)}> - <StoryCommentSvg /> - <Typography.BodyBold>{millify(commentsCount)}</Typography.BodyBold> - </div> - )} onPress={onPress} - /> + style={themeStyles} + className={clsx(styles.storyCommentButton)} + > + <IconComponent + defaultIconName={defaultConfig.icon} + configIconName={config.icon} + imgIcon={() => ( + <img + src={config.icon} + alt={uiReference} + className={clsx(imgClassName)} + style={themeStyles} + /> + )} + defaultIcon={() => ( + <div className={clsx(styles.storyCommentIcon)}> + <StoryCommentSvg /> + <Typography.BodyBold>{millify(commentsCount)}</Typography.BodyBold> + </div> + )} + /> + </Button> ); }; diff --git a/src/v4/social/elements/StoryReactionButton/StoryReactionButton.tsx b/src/v4/social/elements/StoryReactionButton/StoryReactionButton.tsx index cec901262..b4dcd831f 100644 --- a/src/v4/social/elements/StoryReactionButton/StoryReactionButton.tsx +++ b/src/v4/social/elements/StoryReactionButton/StoryReactionButton.tsx @@ -2,6 +2,7 @@ import React from 'react'; import clsx from 'clsx'; import { IconComponent } from '~/v4/core/IconComponent'; import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { Button, ButtonProps } from '~/v4/core/natives/Button'; import styles from './StoryReactionButton.module.css'; import { Typography } from '~/v4/core/components/index'; @@ -77,7 +78,7 @@ export const StoryReactionButton = ({ onPress, }: StoryReactionButtonProps) => { const elementId = 'story_reaction_button'; - const { isExcluded, accessibilityId, config, uiReference } = useAmityElement({ + const { isExcluded, accessibilityId, config, uiReference, themeStyles } = useAmityElement({ pageId, componentId, elementId, @@ -88,17 +89,23 @@ export const StoryReactionButton = ({ if (isExcluded) return null; return ( - <IconComponent + <Button data-qa-anchor={accessibilityId} className={clsx(styles.storyReactionButton)} - defaultIcon={() => ( - <div className={clsx(styles.storyReactionIcon, defaultIconClassName)}> - {hasMyReactions ? <StoryMyReactionSvg /> : <StoryReactionSvg />} - <Typography.BodyBold>{millify(reactionsCount)}</Typography.BodyBold> - </div> - )} - imgIcon={() => <img src={config.icon} alt={uiReference} className={clsx(imgIconClassName)} />} onPress={onPress} - /> + style={themeStyles} + > + <IconComponent + defaultIcon={() => ( + <div className={clsx(styles.storyReactionIcon, defaultIconClassName)}> + {hasMyReactions ? <StoryMyReactionSvg /> : <StoryReactionSvg />} + <Typography.BodyBold>{millify(reactionsCount)}</Typography.BodyBold> + </div> + )} + imgIcon={() => ( + <img src={config.icon} alt={uiReference} className={clsx(imgIconClassName)} /> + )} + /> + </Button> ); }; diff --git a/src/v4/social/hooks/collections/useCommunitiesCollection.ts b/src/v4/social/hooks/collections/useCommunitiesCollection.ts index 210bdc16d..dcb66f8fe 100644 --- a/src/v4/social/hooks/collections/useCommunitiesCollection.ts +++ b/src/v4/social/hooks/collections/useCommunitiesCollection.ts @@ -1,14 +1,17 @@ import { CommunityRepository } from '@amityco/ts-sdk'; import useLiveCollection from '~/v4/core/hooks/useLiveCollection'; -export default function useCommunitiesCollection( - queryParams?: Parameters<typeof CommunityRepository.getCommunities>[0], - shouldCall?: () => boolean, -) { +export default function useCommunitiesCollection({ + queryParams, + shouldCall = true, +}: { + queryParams?: Parameters<typeof CommunityRepository.getCommunities>[0]; + shouldCall?: boolean; +}) { const { items, ...rest } = useLiveCollection({ fetcher: CommunityRepository.getCommunities, params: queryParams as Parameters<typeof CommunityRepository.getCommunities>[0], - shouldCall: () => !!queryParams && (shouldCall ? shouldCall?.() : true), + shouldCall: !!queryParams && shouldCall, }); return { diff --git a/src/v4/social/hooks/collections/useCommunityModeratorsCollection.ts b/src/v4/social/hooks/collections/useCommunityModeratorsCollection.ts index e6a7ae273..416fe4f5f 100644 --- a/src/v4/social/hooks/collections/useCommunityModeratorsCollection.ts +++ b/src/v4/social/hooks/collections/useCommunityModeratorsCollection.ts @@ -8,7 +8,7 @@ export default function useCommunityModeratorsCollection(communityId?: string) { const { items, ...rest } = useLiveCollection({ fetcher: CommunityRepository.Membership.getMembers, params: { communityId: communityId as string, roles: [COMMUNITY_MODERATOR] }, - shouldCall: () => !!communityId, + shouldCall: !!communityId, }); return { diff --git a/src/v4/social/hooks/collections/useGlobalStoryTargets.ts b/src/v4/social/hooks/collections/useGlobalStoryTargets.ts index 959cd327b..5c3fdf584 100644 --- a/src/v4/social/hooks/collections/useGlobalStoryTargets.ts +++ b/src/v4/social/hooks/collections/useGlobalStoryTargets.ts @@ -7,7 +7,7 @@ export const useGlobalStoryTargets = ( const { items, hasMore, loadMore, ...rest } = useLiveCollection({ fetcher: StoryRepository.getGlobalStoryTargets, params, - shouldCall: () => true, + shouldCall: true, }); const loadMoreStories = () => { @@ -16,7 +16,6 @@ export const useGlobalStoryTargets = ( } }; - return { stories: items, hasMore, diff --git a/src/v4/social/hooks/useCommunityInfo.ts b/src/v4/social/hooks/useCommunityInfo.ts index 10696952e..327d69903 100644 --- a/src/v4/social/hooks/useCommunityInfo.ts +++ b/src/v4/social/hooks/useCommunityInfo.ts @@ -11,7 +11,7 @@ export const useCommunityInfo = (communityId?: string) => { const { onEditCommunity } = useNavigation(); const { community } = useCommunity({ communityId, - shouldCall: () => !!communityId, + shouldCall: !!communityId, }); const avatarFileUrl = useImage({ fileId: community?.avatarFileId, imageSize: 'medium' }); diff --git a/src/v4/social/hooks/useCommunityStoriesSubscription.ts b/src/v4/social/hooks/useCommunityStoriesSubscription.ts index b5e26e928..d60987258 100644 --- a/src/v4/social/hooks/useCommunityStoriesSubscription.ts +++ b/src/v4/social/hooks/useCommunityStoriesSubscription.ts @@ -4,12 +4,12 @@ import useSubscription from '~/v4/core/hooks/subscriptions/useSubscription'; export default function useCommunityStoriesSubscription({ targetId, targetType, - shouldSubscribe = () => true, + shouldSubscribe = true, callback, }: { targetId: string; targetType: Amity.StoryTargetType; - shouldSubscribe?: () => boolean; + shouldSubscribe?: boolean; callback?: Amity.Listener; }) { return useSubscription({ @@ -19,7 +19,7 @@ export default function useCommunityStoriesSubscription({ targetType, }, callback, - shouldSubscribe: () => !!targetId && shouldSubscribe(), + shouldSubscribe: !!targetId && shouldSubscribe, getSubscribedTopic: () => getCommunityStoriesTopic({ targetId, diff --git a/src/v4/social/hooks/useGetActiveStories.ts b/src/v4/social/hooks/useGetActiveStories.ts index 11ee4037e..f75c35804 100644 --- a/src/v4/social/hooks/useGetActiveStories.ts +++ b/src/v4/social/hooks/useGetActiveStories.ts @@ -5,7 +5,7 @@ export const useGetActiveStoriesByTarget = (params: Amity.GetStoriesByTargetPara const { items, ...rest } = useLiveCollection({ fetcher: StoryRepository.getActiveStoriesByTarget, params, - shouldCall: () => true, + shouldCall: true, }); return { diff --git a/src/v4/social/internal-components/PostMenu/PostMenu.tsx b/src/v4/social/internal-components/PostMenu/PostMenu.tsx index 5b30f292b..3125723f6 100644 --- a/src/v4/social/internal-components/PostMenu/PostMenu.tsx +++ b/src/v4/social/internal-components/PostMenu/PostMenu.tsx @@ -65,9 +65,11 @@ export const PostMenu = ({ }: PostMenuProps) => { const { success, error } = useNotifications(); + const shouldCall = useMemo(() => post?.targetType === 'community', [post?.targetType]); + const { community } = useCommunity({ communityId: post?.targetId, - shouldCall: () => post?.targetType === 'community', + shouldCall, }); const { isCommunityModerator, isOwner } = usePostPermissions({ post, community }); From bad354685a92587420fe126ddb3ecf5eea5ca606 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Fri, 28 Jun 2024 17:05:06 +0700 Subject: [PATCH 174/300] feat: MyCommunitiesSearchPage (#459) --- .../core/providers/CustomizationProvider.tsx | 16 ++++++++ src/v4/core/providers/NavigationProvider.tsx | 18 ++++++++- src/v4/social/pages/Application/index.tsx | 12 +++++- .../MyCommunitiesSearchPage.module.css | 8 ++++ .../MyCommunitiesSearchPage.stories.tsx | 12 ++++++ .../MyCommunitiesSearchPage.tsx | 40 +++++++++++++++++++ .../pages/MyCommunitiesSearchPage/index.tsx | 1 + .../pages/SocialHomePage/SocialHomePage.tsx | 18 +++++++++ 8 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 src/v4/social/pages/MyCommunitiesSearchPage/MyCommunitiesSearchPage.module.css create mode 100644 src/v4/social/pages/MyCommunitiesSearchPage/MyCommunitiesSearchPage.stories.tsx create mode 100644 src/v4/social/pages/MyCommunitiesSearchPage/MyCommunitiesSearchPage.tsx create mode 100644 src/v4/social/pages/MyCommunitiesSearchPage/index.tsx diff --git a/src/v4/core/providers/CustomizationProvider.tsx b/src/v4/core/providers/CustomizationProvider.tsx index 6fde2c8ee..fc027417c 100644 --- a/src/v4/core/providers/CustomizationProvider.tsx +++ b/src/v4/core/providers/CustomizationProvider.tsx @@ -431,6 +431,22 @@ export const defaultConfig: DefaultConfig = { }, 'social_global_search_page/community_search_result/community_category_name': {}, 'social_global_search_page/community_search_result/community_members_count': {}, + 'my_communities_search_page/top_search_bar/*': { + text: 'Search my community', + }, + 'my_communities_search_page/*/community_avatar': {}, + 'my_communities_search_page/*/community_display_name': {}, + 'my_communities_search_page/*/community_private_badge': { + icon: 'lockIcon', + }, + 'my_communities_search_page/*/community_official_badge': { + icon: 'officialBadgeIcon', + }, + 'my_communities_search_page/*/community_category_name': {}, + 'my_communities_search_page/*/community_members_count': {}, + 'my_communities_search_page/top_search_bar/cancel_button': { + text: 'Cancel', + }, }, }; diff --git a/src/v4/core/providers/NavigationProvider.tsx b/src/v4/core/providers/NavigationProvider.tsx index 2c6b9ad99..04f4dfb67 100644 --- a/src/v4/core/providers/NavigationProvider.tsx +++ b/src/v4/core/providers/NavigationProvider.tsx @@ -19,6 +19,7 @@ export enum PageTypes { SelectPostTargetPage = 'SelectPostTargetPage', DraftPage = 'DraftPage', PostComposerPage = 'PostComposerPage', + MyCommunitiesSearchPage = 'MyCommunitiesSearchPage', } type Page = @@ -73,6 +74,7 @@ type Page = | { type: PageTypes.UserProfilePage; context: { userId: string; communityId?: string } } | { type: PageTypes.SocialHomePage; context: { communityId?: string } } | { type: PageTypes.SocialGlobalSearchPage; context: { tab?: string } } + | { type: PageTypes.MyCommunitiesSearchPage; context: { communityId?: string } } | { type: PageTypes.SelectPostTargetPage } | { type: PageTypes.DraftPage; @@ -108,6 +110,7 @@ type ContextValue = { goToPostDetailPage: (postId: string) => void; goToCommunityProfilePage: (communityId: string) => void; goToSocialGlobalSearchPage: (tab?: string) => void; + goToMyCommunitiesSearchPage: () => void; goToSelectPostTargetPage: () => void; goToDraftStoryPage: (context: { targetId: string; @@ -173,6 +176,7 @@ let defaultValue: ContextValue = { post?: Amity.Post, ) => {}, goToSocialHomePage: () => {}, + goToMyCommunitiesSearchPage: () => {}, setNavigationBlocker: () => {}, onBack: () => {}, }; @@ -210,6 +214,8 @@ if (process.env.NODE_ENV !== 'production') { `NavigationContext goToPostComposerPage(${mode} ${targetId}) ${targetType} ${community} ${post}`, ), goToSocialHomePage: () => console.log('NavigationContext goToSocialHomePage()'), + goToMyCommunitiesSearchPage: () => + console.log('NavigationContext goToMyCommunitiesSearchPage()'), }; } @@ -528,6 +534,15 @@ export default function NavigationProvider({ [onChangePage, pushPage], ); + const goToMyCommunitiesSearchPage = useCallback(() => { + const next = { + type: PageTypes.MyCommunitiesSearchPage, + context: {}, + }; + + pushPage(next); + }, [onChangePage, pushPage]); + return ( <NavigationContext.Provider value={{ @@ -550,10 +565,11 @@ export default function NavigationProvider({ goToDraftStoryPage, goToPostComposerPage, goToSocialHomePage, + goToMyCommunitiesSearchPage, setNavigationBlocker, }} > - {children} + <div style={{ position: 'relative' }}>{children}</div> </NavigationContext.Provider> ); } diff --git a/src/v4/social/pages/Application/index.tsx b/src/v4/social/pages/Application/index.tsx index 34554908f..dae6732bc 100644 --- a/src/v4/social/pages/Application/index.tsx +++ b/src/v4/social/pages/Application/index.tsx @@ -8,6 +8,7 @@ import { StoryProvider } from '~/v4/social/providers/StoryProvider'; import { SocialGlobalSearchPage } from '~/v4/social/pages/SocialGlobalSearchPage'; import { ViewStoryPage } from '~/v4/social/pages/StoryPage'; import { SelectPostTargetPage } from '../SelectPostTargetPage'; +import { MyCommunitiesSearchPage } from '../MyCommunitiesSearchPage/MyCommunitiesSearchPage'; import styles from './Application.module.css'; @@ -23,8 +24,17 @@ const Application = () => { {page.type === PageTypes.ViewStoryPage && ( <ViewStoryPage type="globalFeed" targetId={page.context.targetId} /> )} - {page.type === PageTypes.PostComposerPage && <PostComposerPage mode={page.context.mode} targetId={page.context.targetId} targetType={page.context.targetType} community={page.context.community} post={page.context.post} />} + {page.type === PageTypes.PostComposerPage && ( + <PostComposerPage + mode={page.context.mode} + targetId={page.context.targetId} + targetType={page.context.targetType} + community={page.context.community} + post={page.context.post} + /> + )} {page.type === PageTypes.SelectPostTargetPage && <SelectPostTargetPage />} + {page.type === PageTypes.MyCommunitiesSearchPage && <MyCommunitiesSearchPage />} </div> </StoryProvider> ); diff --git a/src/v4/social/pages/MyCommunitiesSearchPage/MyCommunitiesSearchPage.module.css b/src/v4/social/pages/MyCommunitiesSearchPage/MyCommunitiesSearchPage.module.css new file mode 100644 index 000000000..42ddf1e79 --- /dev/null +++ b/src/v4/social/pages/MyCommunitiesSearchPage/MyCommunitiesSearchPage.module.css @@ -0,0 +1,8 @@ +.myCommunitiesSearchPage { + display: flex; + flex-direction: column; + gap: 0.5rem; + width: 100%; + padding: 1rem; + background-color: var(--asc-color-base-background); +} diff --git a/src/v4/social/pages/MyCommunitiesSearchPage/MyCommunitiesSearchPage.stories.tsx b/src/v4/social/pages/MyCommunitiesSearchPage/MyCommunitiesSearchPage.stories.tsx new file mode 100644 index 000000000..dc929be00 --- /dev/null +++ b/src/v4/social/pages/MyCommunitiesSearchPage/MyCommunitiesSearchPage.stories.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { MyCommunitiesSearchPage } from './MyCommunitiesSearchPage'; + +export default { + title: 'v4-social/pages/MyCommunitiesSearchPage', +}; + +export const MyCommunitiesSearchPageStories = { + render: () => { + return <MyCommunitiesSearchPage />; + }, +}; diff --git a/src/v4/social/pages/MyCommunitiesSearchPage/MyCommunitiesSearchPage.tsx b/src/v4/social/pages/MyCommunitiesSearchPage/MyCommunitiesSearchPage.tsx new file mode 100644 index 000000000..ba03cab98 --- /dev/null +++ b/src/v4/social/pages/MyCommunitiesSearchPage/MyCommunitiesSearchPage.tsx @@ -0,0 +1,40 @@ +import React, { useState } from 'react'; +import styles from './MyCommunitiesSearchPage.module.css'; + +import { TopSearchBar } from '~/v4/social/components/TopSearchBar'; +import { CommunitySearchResult } from '~/v4/social/components/CommunitySearchResult'; +import useCommunitiesCollection from '~/v4/core/hooks/collections/useCommunitiesCollection'; +import { useAmityPage } from '~/v4/core/hooks/uikit'; + +export function MyCommunitiesSearchPage() { + const pageId = 'my_communities_search_page'; + const { themeStyles } = useAmityPage({ + pageId, + }); + + const [searchValue, setSearchValue] = useState<string>(''); + + const { communities, isLoading, hasMore, loadMore } = useCommunitiesCollection({ + displayName: searchValue, + limit: 20, + membership: 'member', + }); + + return ( + <div className={styles.myCommunitiesSearchPage} style={themeStyles}> + <TopSearchBar pageId={pageId} search={(newSearchValue) => setSearchValue(newSearchValue)} /> + {searchValue.length > 0 && ( + <CommunitySearchResult + pageId={pageId} + communityCollection={communities} + isLoading={isLoading} + onLoadMore={() => { + if (hasMore && isLoading === false) { + loadMore(); + } + }} + /> + )} + </div> + ); +} diff --git a/src/v4/social/pages/MyCommunitiesSearchPage/index.tsx b/src/v4/social/pages/MyCommunitiesSearchPage/index.tsx new file mode 100644 index 000000000..63b1fd85b --- /dev/null +++ b/src/v4/social/pages/MyCommunitiesSearchPage/index.tsx @@ -0,0 +1 @@ +export { MyCommunitiesSearchPage } from './MyCommunitiesSearchPage'; diff --git a/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx b/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx index c65a10721..f08ad5cde 100644 --- a/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx +++ b/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx @@ -9,6 +9,8 @@ import { MyCommunitiesButton } from '~/v4/social/elements/MyCommunitiesButton'; import { Newsfeed } from '~/v4/social/components/Newsfeed'; import { useAmityPage } from '~/v4/core/hooks/uikit'; import { CreatePostMenu } from '~/v4/social/components/CreatePostMenu'; +import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; +import { useNavigation } from '~/v4/core/providers/NavigationProvider'; enum EnumTabNames { Newsfeed = 'Newsfeed', @@ -21,6 +23,7 @@ export function SocialHomePage() { const { themeStyles } = useAmityPage({ pageId, }); + const { goToSocialGlobalSearchPage, goToMyCommunitiesSearchPage } = useNavigation(); const [activeTab, setActiveTab] = useState(EnumTabNames.Newsfeed); @@ -33,6 +36,20 @@ export function SocialHomePage() { setIsShowCreatePostMenu((prev) => !prev); }; + const handleGlobalSearchClick = () => { + switch (activeTab) { + case EnumTabNames.Newsfeed: + goToSocialGlobalSearchPage(); + break; + case EnumTabNames.Explore: + break; + case EnumTabNames.MyCommunities: + goToMyCommunitiesSearchPage(); + + break; + } + }; + useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if ( @@ -56,6 +73,7 @@ export function SocialHomePage() { <TopNavigation pageId={pageId} onClickPostCreationButton={handleClickButton} + onGlobalSearchButtonClick={handleGlobalSearchClick} createPostButtonRef={createPostButtonRef} /> {isShowCreatePostMenu && ( From 57f8126549a7f23d3739fe4289cc57a79534256e Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Fri, 28 Jun 2024 17:23:41 +0700 Subject: [PATCH 175/300] fix: ASC-23544 - global search UI (#460) * fix: fix SocialGlobalSearchPage behavior * fix: fix post reaction --- .../core/providers/CustomizationProvider.tsx | 4 +- .../CommunitySearchResult.stories.tsx | 8 +- .../CommunitySearchResult.tsx | 13 +- .../components/PostContent/PostContent.tsx | 321 ++++++++++-------- .../TopNavigation/TopNavigation.tsx | 5 +- .../TopSearchBar/TopSearchBar.module.css | 1 - .../ReactionButton/ReactionButton.tsx | 309 +++++++++-------- .../SocialGlobalSearchPage.tsx | 57 +++- 8 files changed, 384 insertions(+), 334 deletions(-) diff --git a/src/v4/core/providers/CustomizationProvider.tsx b/src/v4/core/providers/CustomizationProvider.tsx index fc027417c..4ad6e746b 100644 --- a/src/v4/core/providers/CustomizationProvider.tsx +++ b/src/v4/core/providers/CustomizationProvider.tsx @@ -411,7 +411,9 @@ export const defaultConfig: DefaultConfig = { image: 'platformValue', }, 'social_global_search_page/*/*': {}, - 'social_global_search_page/top_search_bar/*': {}, + 'social_global_search_page/top_search_bar/*': { + text: 'Search community and user', + }, 'social_global_search_page/top_search_bar/search_icon': { icon: 'search', }, diff --git a/src/v4/social/components/CommunitySearchResult/CommunitySearchResult.stories.tsx b/src/v4/social/components/CommunitySearchResult/CommunitySearchResult.stories.tsx index 4d866816e..70c2bd3f9 100644 --- a/src/v4/social/components/CommunitySearchResult/CommunitySearchResult.stories.tsx +++ b/src/v4/social/components/CommunitySearchResult/CommunitySearchResult.stories.tsx @@ -13,7 +13,13 @@ export const CommunitySearchResultStory = { if (community == null) return null; - return <CommunitySearchResult communityCollection={[community]} onLoadMore={() => {}} />; + return ( + <CommunitySearchResult + isLoading={false} + communityCollection={[community]} + onLoadMore={() => {}} + /> + ); }, name: 'CommunitySearchResult', diff --git a/src/v4/social/components/CommunitySearchResult/CommunitySearchResult.tsx b/src/v4/social/components/CommunitySearchResult/CommunitySearchResult.tsx index f17490a88..7a85c146e 100644 --- a/src/v4/social/components/CommunitySearchResult/CommunitySearchResult.tsx +++ b/src/v4/social/components/CommunitySearchResult/CommunitySearchResult.tsx @@ -15,20 +15,17 @@ interface CommunitySearchResultProps { export const CommunitySearchResult = ({ pageId = '*', communityCollection = [], - // isLoading, + isLoading, onLoadMore, }: CommunitySearchResultProps) => { const componentId = 'community_search_result'; - const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = - useAmityComponent({ - pageId, - componentId, - }); + const { themeStyles } = useAmityComponent({ + pageId, + componentId, + }); const intersectionRef = useRef<HTMLDivElement>(null); - const isLoading = true; - useIntersectionObserver({ onIntersect: () => onLoadMore(), ref: intersectionRef }); return ( diff --git a/src/v4/social/components/PostContent/PostContent.tsx b/src/v4/social/components/PostContent/PostContent.tsx index 38e712054..9a6db0c51 100644 --- a/src/v4/social/components/PostContent/PostContent.tsx +++ b/src/v4/social/components/PostContent/PostContent.tsx @@ -11,10 +11,9 @@ import { Typography } from '~/v4/core/components'; import AngleRight from '~/v4/icons/AngleRight'; import { UserAvatar } from '~/v4/social/internal-components/UserAvatar'; import { CommentButton } from '~/v4/social/elements/CommentButton'; -import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; -import { useGenerateStylesShadeColors } from '~/v4/core/providers/ThemeProvider'; +import { useDrawer } from '~/v4/core/providers/DrawerProvider'; import { useMutation } from '@tanstack/react-query'; -import { ReactionRepository } from '@amityco/ts-sdk'; +import { ReactionRepository, SubscriptionLevels } from '@amityco/ts-sdk'; import styles from './PostContent.module.css'; @@ -30,10 +29,10 @@ import { useAmityComponent } from '~/v4/core/hooks/uikit'; import { ImageViewer } from '~/v4/social/internal-components/ImageViewer/ImageViewer'; import { VideoViewer } from '~/v4/social/internal-components/VideoViewer/VideoViewer'; import usePost from '~/v4/core/hooks/objects/usePost'; -import { ReactionList } from '~/v4/social/components/ReactionList/'; import { usePostPermissions } from '~/v4/core/hooks/usePostPermissions'; import { PostMenu } from '~/v4/social/internal-components/PostMenu/PostMenu'; -import { useDrawer } from '~/v4/core/providers/DrawerProvider'; +import usePostSubscription from '~/v4/core/hooks/subscriptions/usePostSubscription'; +import { ReactionList } from '../index'; interface PostTitleProps { post: Amity.Post; @@ -41,22 +40,18 @@ interface PostTitleProps { } const PostTitle = ({ pageId, post }: PostTitleProps) => { - const componentId = 'post_content'; - const { getConfig } = useCustomization(); - const uiReference = `${pageId}/${componentId}/*`; - const config = getConfig(uiReference); - const themeStyles = useGenerateStylesShadeColors(config); + const shouldCall = useMemo(() => post?.targetType === 'community', [post?.targetType]); const { community: targetCommunity } = useCommunity({ communityId: post.targetId, - shouldCall: () => post.targetType === 'community', + shouldCall, }); const { user: postedUser } = useUser(post.postedUserId); if (targetCommunity) { return ( - <div style={themeStyles} className={styles.postTitle}> + <div className={styles.postTitle}> <Typography.BodyBold className={styles.postTitle__text}> {postedUser?.displayName} </Typography.BodyBold> @@ -141,6 +136,7 @@ interface PostContentProps { pageId?: string; post: Amity.Post; type: 'feed' | 'detail'; + drawerRef?: React.RefObject<HTMLDivElement>; onClick?: () => void; } @@ -148,6 +144,7 @@ export const PostContent = ({ pageId = '*', post: initialPost, type, + drawerRef, onClick, }: PostContentProps) => { const componentId = 'post_content'; @@ -156,25 +153,37 @@ export const PostContent = ({ componentId, }); - const { post: postData } = usePost(initialPost.postId); + const { post: postData } = usePost(initialPost?.postId); const { setDrawerData, removeDrawerData } = useDrawer(); const post = postData || initialPost; + const [shouldSubscribe, setShouldSubscribe] = useState(false); const [isImageViewerOpen, setIsImageViewerOpen] = useState(false); const [isVideoViewerOpen, setIsVideoViewerOpen] = useState(false); const [clickedImageIndex, setClickedImageIndex] = useState<number | null>(null); const [clickedVideoIndex, setClickedVideoIndex] = useState<number | null>(null); + const [reactionByMe, setReactionByMe] = useState<string | null>(null); + const [reactionsCount, setReactionsCount] = useState<number>(0); + + usePostSubscription({ + postId: post?.postId, + level: SubscriptionLevels.POST, + shouldSubscribe: shouldSubscribe, + }); + useEffect(() => { if (post) { post.analytics?.markAsViewed(); } }, [post]); + const shouldCall = useMemo(() => post?.targetType === 'community', [post?.targetType]); + const { community: targetCommunity } = useCommunity({ communityId: post?.targetId, - shouldCall: () => post?.targetType === 'community', + shouldCall, }); const { isCommunityModerator } = usePostPermissions({ @@ -182,24 +191,48 @@ export const PostContent = ({ community: targetCommunity, }); - const reactionByMe = useMemo(() => { - if (post == null || post.myReactions?.length === 0) return null; - return post.myReactions[0]; + useEffect(() => { + if (post == null) return; + setReactionByMe(post.myReactions[0] || null); }, [post.myReactions]); - const { mutateAsync: mutateAddReactionAsync } = useMutateAddReaction({ - postId: post.postId, - reactionByMe, + useEffect(() => { + if (post == null) return; + setReactionsCount(post?.reactionsCount || 0); + }, [post?.reactionsCount]); + + const { mutateAsync: mutateAddReactionAsync } = useMutation({ + mutationFn: async (reactionKey: string) => { + if (reactionByMe && reactionByMe !== reactionKey) { + try { + await ReactionRepository.removeReaction('post', post?.postId, reactionByMe); + } catch { + console.log(); + } + } + return ReactionRepository.addReaction('post', post?.postId, reactionKey); + }, + onMutate: (reactionKey) => { + setShouldSubscribe(true); + setReactionsCount(reactionsCount + 1); + setReactionByMe(reactionKey); + }, }); - const { mutateAsync: mutateRemoveReactionAsync } = useMutateRemoveReaction({ - postId: post.postId, - reactionsByMe: post.myReactions, + const { mutateAsync: mutateRemoveReactionAsync } = useMutation({ + mutationFn: async (reactionKey: string) => { + return ReactionRepository.removeReaction('post', post?.postId, reactionKey); + }, + onMutate: () => { + setShouldSubscribe(true); + setReactionsCount(Math.max(0, reactionsCount - 1)); + setReactionByMe(null); + }, }); const handleReactionClick = (reactionKey: string) => { - if (post.myReactions?.length > 0) { - mutateRemoveReactionAsync(); + if (reactionByMe) { + mutateRemoveReactionAsync(reactionByMe); } else { mutateAddReactionAsync(reactionKey); } @@ -234,135 +267,127 @@ export const PostContent = ({ const hasReaction = hasLike || hasLove || hasFire || hasHappy || hasCrying; return ( - <> - <div className={styles.postContent} style={themeStyles}> - <div className={styles.postContent__bar}> - <div className={styles.postContent__bar__userAvatar}> - <UserAvatar userId={post.postedUserId} /> - </div> + <div className={styles.postContent} style={themeStyles}> + <div className={styles.postContent__bar} data-type={type}> + <div className={styles.postContent__bar__userAvatar}> + <UserAvatar userId={post?.postedUserId} /> + </div> + <div> <div> - <div> - <PostTitle post={post} /> - </div> - <div className={styles.postContent__bar__information__subtitle}> - {!isCommunityModerator ? ( - <div className={styles.postContent__bar__information__subtitle__moderator}> - <ModeratorBadge pageId={pageId} componentId={componentId} /> - <span className={styles.postContent__bar__information__subtitle__separator}> - • - </span> - </div> - ) : null} - <span - className={styles.postContent__bar__information__subtitle__timestamp} - onClick={() => onClick?.()} - > - <Timestamp timestamp={post.createdAt} /> - </span> - </div> + <PostTitle post={post} /> </div> - <div className={styles.postContent__bar__actionButton}> - {type === 'feed' ? ( - <MenuButton - pageId={pageId} - componentId={componentId} - onClick={() => - setDrawerData({ - content: ( - <PostMenu - post={post} - onCloseMenu={() => removeDrawerData()} - pageId={pageId} - componentId={componentId} - /> - ), - }) - } - /> + <div className={styles.postContent__bar__information__subtitle}> + {!isCommunityModerator ? ( + <div className={styles.postContent__bar__information__subtitle__moderator}> + <ModeratorBadge pageId={pageId} componentId={componentId} /> + <span className={styles.postContent__bar__information__subtitle__separator}>•</span> + </div> ) : null} + <span className={styles.postContent__bar__information__subtitle__timestamp}> + <Timestamp timestamp={post.createdAt} /> + </span> </div> </div> - <div className={styles.postContent__content_and_reactions}> - <div className={styles.postContent__content}> - <TextContent text={post.data.text} mentionees={post?.metadata?.mentioned} /> - {post.children.length > 0 ? ( - <ChildrenPostContent - post={post} - onImageClick={openImageViewer} - onVideoClick={openVideoViewer} - /> - ) : null} - </div> - {type === 'detail' ? ( - <div className={styles.postContent__reactions_and_comments}> - <div - className={styles.postContent__reactionsBar} - onClick={() => - setDrawerData({ - content: ( - <ReactionList - pageId={pageId} - referenceId={post.postId} - referenceType={'post'} - /> - ), - }) - } - > - {hasReaction ? ( - <div className={styles.postContent__reactionsBar__reactions}> - {hasCrying && ( - <Crying className={styles.postContent__reactionsBar__reactions__icon} /> - )} - {hasHappy && ( - <Happy className={styles.postContent__reactionsBar__reactions__icon} /> - )} - {hasFire && ( - <Fire className={styles.postContent__reactionsBar__reactions__icon} /> - )} - {hasLove && ( - <Love className={styles.postContent__reactionsBar__reactions__icon} /> - )} - {hasLike && ( - <Like className={styles.postContent__reactionsBar__reactions__icon} /> - )} - </div> - ) : null} - <Typography.Caption className={styles.postContent__reactionsBar__reactions__count}> - {`${post?.reactionsCount || 0} ${post?.reactionsCount === 1 ? 'like' : 'likes'}`} - </Typography.Caption> - </div> - - <Typography.Caption className={styles.postContent__commentsCount}> - {`${post?.commentsCount || 0} ${ - post?.commentsCount === 1 ? 'comment' : 'comments' - }`} - </Typography.Caption> - </div> + <div className={styles.postContent__bar__actionButton}> + {type === 'feed' ? ( + <MenuButton + pageId={pageId} + componentId={componentId} + onClick={() => + setDrawerData({ + content: ( + <PostMenu + post={post} + onCloseMenu={() => removeDrawerData()} + pageId={pageId} + componentId={componentId} + /> + ), + }) + } + /> ) : null} - <div className={styles.postContent__divider} /> - <div className={styles.postContent__reactionBar}> - <div className={styles.postContent__reactionBar__leftPane}> - <ReactionButton - pageId={pageId} - componentId={componentId} - reactionsCount={type === 'feed' ? post.reactionsCount : undefined} - myReactions={post.myReactions} - defaultIconClassName={styles.postContent__reactionBar__leftPane__icon} - imgIconClassName={styles.postContent__reactionBar__leftPane__iconImg} - onReactionClick={handleReactionClick} - /> - <CommentButton - pageId={pageId} - componentId={componentId} - commentsCount={type === 'feed' ? post.commentsCount : undefined} - defaultIconClassName={styles.postContent__reactionBar__leftPane__icon} - imgIconClassName={styles.postContent__reactionBar__leftPane__iconImg} - /> - </div> - <div className={styles.postContent__reactionBar__rightPane}> - <ShareButton pageId={pageId} componentId={componentId} /> + </div> + </div> + <div className={styles.postContent__content_and_reactions}> + <div className={styles.postContent__content}> + <TextContent text={post.data.text} mentionees={post?.metadata?.mentioned} /> + {post.children.length > 0 ? ( + <ChildrenPostContent + post={post} + onImageClick={openImageViewer} + onVideoClick={openVideoViewer} + /> + ) : null} + </div> + {type === 'detail' ? ( + <div className={styles.postContent__reactions_and_comments}> + <div + className={styles.postContent__reactionsBar} + onClick={() => + setDrawerData({ + content: ( + <ReactionList + pageId={pageId} + referenceId={post.postId} + referenceType={'post'} + /> + ), + }) + } + > + {hasReaction ? ( + <div className={styles.postContent__reactionsBar__reactions}> + {hasCrying && ( + <Crying className={styles.postContent__reactionsBar__reactions__icon} /> + )} + {hasHappy && ( + <Happy className={styles.postContent__reactionsBar__reactions__icon} /> + )} + {hasFire && ( + <Fire className={styles.postContent__reactionsBar__reactions__icon} /> + )} + {hasLove && ( + <Love className={styles.postContent__reactionsBar__reactions__icon} /> + )} + {hasLike && ( + <Like className={styles.postContent__reactionsBar__reactions__icon} /> + )} + </div> + ) : null} + <Typography.Caption className={styles.postContent__reactionsBar__reactions__count}> + {`${post?.reactionsCount || 0} ${post?.reactionsCount === 1 ? 'like' : 'likes'}`} + </Typography.Caption> </div> + + <Typography.Caption className={styles.postContent__commentsCount}> + {`${post?.commentsCount || 0} ${post?.commentsCount === 1 ? 'comment' : 'comments'}`} + </Typography.Caption> + </div> + ) : null} + <div className={styles.postContent__divider} /> + <div className={styles.postContent__reactionBar}> + <div className={styles.postContent__reactionBar__leftPane}> + <ReactionButton + pageId={pageId} + componentId={componentId} + reactionsCount={type === 'feed' ? reactionsCount : undefined} + myReaction={reactionByMe} + defaultIconClassName={styles.postContent__reactionBar__leftPane__icon} + imgIconClassName={styles.postContent__reactionBar__leftPane__iconImg} + onReactionClick={handleReactionClick} + /> + <CommentButton + pageId={pageId} + componentId={componentId} + commentsCount={type === 'feed' ? post.commentsCount : undefined} + defaultIconClassName={styles.postContent__reactionBar__leftPane__icon} + imgIconClassName={styles.postContent__reactionBar__leftPane__iconImg} + onPress={() => onClick?.()} + /> + </div> + <div className={styles.postContent__reactionBar__rightPane}> + <ShareButton pageId={pageId} componentId={componentId} /> </div> </div> </div> @@ -372,6 +397,6 @@ export const PostContent = ({ {isVideoViewerOpen && typeof clickedVideoIndex === 'number' ? ( <VideoViewer post={post} onClose={closeVideoViewer} initialVideoIndex={clickedVideoIndex} /> ) : null} - </> + </div> ); }; diff --git a/src/v4/social/components/TopNavigation/TopNavigation.tsx b/src/v4/social/components/TopNavigation/TopNavigation.tsx index 9f2360b96..163178227 100644 --- a/src/v4/social/components/TopNavigation/TopNavigation.tsx +++ b/src/v4/social/components/TopNavigation/TopNavigation.tsx @@ -4,16 +4,17 @@ import { GlobalSearchButton } from '~/v4/social/elements/GlobalSearchButton'; import { HeaderLabel } from '~/v4/social/elements/HeaderLabel'; import styles from './TopNavigation.module.css'; import { useAmityComponent } from '~/v4/core/hooks/uikit'; -import { useNavigation } from '~/v4/core/providers/NavigationProvider'; export interface TopNavigationProps { pageId?: string; + onGlobalSearchButtonClick: () => void; onClickPostCreationButton: (event: React.MouseEvent) => void; createPostButtonRef: React.RefObject<HTMLDivElement>; } export function TopNavigation({ pageId = '*', + onGlobalSearchButtonClick, onClickPostCreationButton, createPostButtonRef, }: TopNavigationProps) { @@ -23,8 +24,6 @@ export function TopNavigation({ componentId, }); - const { goToSocialGlobalSearchPage } = useNavigation(); - if (isExcluded) return null; return ( diff --git a/src/v4/social/components/TopSearchBar/TopSearchBar.module.css b/src/v4/social/components/TopSearchBar/TopSearchBar.module.css index 9c3bc3c35..bee5bc93d 100644 --- a/src/v4/social/components/TopSearchBar/TopSearchBar.module.css +++ b/src/v4/social/components/TopSearchBar/TopSearchBar.module.css @@ -26,7 +26,6 @@ fill: var(--asc-color-base-default); width: 1.25rem; height: 1.25rem; - cursor: pointer; } .topSearchBar__searchIcon_img { diff --git a/src/v4/social/elements/ReactionButton/ReactionButton.tsx b/src/v4/social/elements/ReactionButton/ReactionButton.tsx index 8416f47cf..2a61c593e 100644 --- a/src/v4/social/elements/ReactionButton/ReactionButton.tsx +++ b/src/v4/social/elements/ReactionButton/ReactionButton.tsx @@ -26,7 +26,7 @@ const LikeSvg = (props: React.SVGProps<SVGSVGElement>) => ( interface ReactionButtonProps { pageId?: string; componentId?: string; - myReactions: string[]; + myReaction?: string | null; reactionsCount?: number; defaultIconClassName?: string; imgIconClassName?: string; @@ -38,7 +38,7 @@ const MOUSE_DURATION = 250; export function ReactionButton({ pageId = '*', componentId = '*', - myReactions, + myReaction, reactionsCount, defaultIconClassName, imgIconClassName, @@ -52,44 +52,43 @@ export function ReactionButton({ elementId, }); - const clickTimerRef = useRef(0); - const touchTimerRef = useRef<NodeJS.Timeout | null>(null); - const [isShowReactionPanel, setIsShowReactionPanel] = useState(false); + // const clickTimerRef = useRef(0); + // const touchTimerRef = useRef<NodeJS.Timeout | null>(null); + // const [isShowReactionPanel, setIsShowReactionPanel] = useState(false); - const likeRef = useRef<HTMLDivElement>(null); - const loveRef = useRef<HTMLDivElement>(null); - const fireRef = useRef<HTMLDivElement>(null); - const happyRef = useRef<HTMLDivElement>(null); - const cryingRef = useRef<HTMLDivElement>(null); + // const likeRef = useRef<HTMLDivElement>(null); + // const loveRef = useRef<HTMLDivElement>(null); + // const fireRef = useRef<HTMLDivElement>(null); + // const happyRef = useRef<HTMLDivElement>(null); + // const cryingRef = useRef<HTMLDivElement>(null); - const myReaction = myReactions && myReactions.length > 0 ? myReactions[0] : null; const hasMyReaction = myReaction != null; - const [selectedReaction, setSelectedReaction] = useState<string | null>(null); - const [activeReaction, setActiveReaction] = useState<string | null>(null); + // const [selectedReaction, setSelectedReaction] = useState<string | null>(null); + // const [activeReaction, setActiveReaction] = useState<string | null>(null); - useEffect(() => { - if (selectedReaction) { - setTimeout(() => { - setSelectedReaction(null); - setIsShowReactionPanel(false); - }, 250); - } - }, [selectedReaction]); + // useEffect(() => { + // if (selectedReaction) { + // setTimeout(() => { + // setSelectedReaction(null); + // setIsShowReactionPanel(false); + // }, 250); + // } + // }, [selectedReaction]); - const hideReactionPanel = (ev: MouseEvent) => { - ev.preventDefault(); - ev.stopPropagation(); - setIsShowReactionPanel(false); - }; + // const hideReactionPanel = (ev: MouseEvent) => { + // ev.preventDefault(); + // ev.stopPropagation(); + // setIsShowReactionPanel(false); + // }; - useEffect(() => { - if (isShowReactionPanel) { - window.addEventListener('click', hideReactionPanel); - } else { - window.removeEventListener('click', hideReactionPanel); - } - }, [isShowReactionPanel]); + // useEffect(() => { + // if (isShowReactionPanel) { + // window.addEventListener('click', hideReactionPanel); + // } else { + // window.removeEventListener('click', hideReactionPanel); + // } + // }, [isShowReactionPanel]); if (isExcluded) return null; @@ -119,141 +118,141 @@ export function ReactionButton({ ev.preventDefault(); ev.stopPropagation(); - clickTimerRef.current = Date.now(); + // clickTimerRef.current = Date.now(); - touchTimerRef.current = setTimeout(() => { - setIsShowReactionPanel(true); - }, MOUSE_DURATION); + // touchTimerRef.current = setTimeout(() => { + // setIsShowReactionPanel(true); + // }, MOUSE_DURATION); }} onTouchStart={(ev) => { ev.preventDefault(); ev.stopPropagation(); - clickTimerRef.current = Date.now(); + // clickTimerRef.current = Date.now(); - touchTimerRef.current = setTimeout(() => { - setIsShowReactionPanel(true); - }, MOUSE_DURATION); + // touchTimerRef.current = setTimeout(() => { + // setIsShowReactionPanel(true); + // }, MOUSE_DURATION); }} - onMouseMove={(ev) => { - ev.preventDefault(); - ev.stopPropagation(); + // onMouseMove={(ev) => { + // ev.preventDefault(); + // ev.stopPropagation(); - if ( - likeRef.current && - likeRef.current.offsetLeft < ev.clientX && - ev.clientX < likeRef.current.offsetLeft + likeRef.current.clientWidth - ) { - setActiveReaction('like'); - return; - } - if ( - loveRef.current && - loveRef.current.offsetLeft < ev.clientX && - ev.clientX < loveRef.current.offsetLeft + loveRef.current.clientWidth - ) { - setActiveReaction('love'); - return; - } - if ( - fireRef.current && - fireRef.current.offsetLeft < ev.clientX && - ev.clientX < fireRef.current.offsetLeft + fireRef.current.clientWidth - ) { - setActiveReaction('fire'); - return; - } - if ( - happyRef.current && - happyRef.current.offsetLeft < ev.clientX && - ev.clientX < happyRef.current.offsetLeft + happyRef.current.clientWidth - ) { - setActiveReaction('happy'); - return; - } - if ( - cryingRef.current && - cryingRef.current.offsetLeft < ev.clientX && - ev.clientX < cryingRef.current.offsetLeft + cryingRef.current.clientWidth - ) { - setActiveReaction('crying'); - return; - } + // if ( + // likeRef.current && + // likeRef.current.offsetLeft < ev.clientX && + // ev.clientX < likeRef.current.offsetLeft + likeRef.current.clientWidth + // ) { + // setActiveReaction('like'); + // return; + // } + // if ( + // loveRef.current && + // loveRef.current.offsetLeft < ev.clientX && + // ev.clientX < loveRef.current.offsetLeft + loveRef.current.clientWidth + // ) { + // setActiveReaction('love'); + // return; + // } + // if ( + // fireRef.current && + // fireRef.current.offsetLeft < ev.clientX && + // ev.clientX < fireRef.current.offsetLeft + fireRef.current.clientWidth + // ) { + // setActiveReaction('fire'); + // return; + // } + // if ( + // happyRef.current && + // happyRef.current.offsetLeft < ev.clientX && + // ev.clientX < happyRef.current.offsetLeft + happyRef.current.clientWidth + // ) { + // setActiveReaction('happy'); + // return; + // } + // if ( + // cryingRef.current && + // cryingRef.current.offsetLeft < ev.clientX && + // ev.clientX < cryingRef.current.offsetLeft + cryingRef.current.clientWidth + // ) { + // setActiveReaction('crying'); + // return; + // } - setActiveReaction(null); - }} - onTouchMove={(ev) => { - ev.preventDefault(); - ev.stopPropagation(); + // setActiveReaction(null); + // }} + // onTouchMove={(ev) => { + // ev.preventDefault(); + // ev.stopPropagation(); - if ( - likeRef.current && - likeRef.current.offsetLeft < ev.touches[0].clientX && - ev.touches[0].clientX < likeRef.current.offsetLeft + likeRef.current.clientWidth - ) { - setActiveReaction('like'); - return; - } - if ( - loveRef.current && - loveRef.current.offsetLeft < ev.touches[0].clientX && - ev.touches[0].clientX < loveRef.current.offsetLeft + loveRef.current.clientWidth - ) { - setActiveReaction('love'); - return; - } - if ( - fireRef.current && - fireRef.current.offsetLeft < ev.touches[0].clientX && - ev.touches[0].clientX < fireRef.current.offsetLeft + fireRef.current.clientWidth - ) { - setActiveReaction('fire'); - return; - } - if ( - happyRef.current && - happyRef.current.offsetLeft < ev.touches[0].clientX && - ev.touches[0].clientX < happyRef.current.offsetLeft + happyRef.current.clientWidth - ) { - setActiveReaction('happy'); - return; - } - if ( - cryingRef.current && - cryingRef.current.offsetLeft < ev.touches[0].clientX && - ev.touches[0].clientX < cryingRef.current.offsetLeft + cryingRef.current.clientWidth - ) { - setActiveReaction('crying'); - return; - } + // if ( + // likeRef.current && + // likeRef.current.offsetLeft < ev.touches[0].clientX && + // ev.touches[0].clientX < likeRef.current.offsetLeft + likeRef.current.clientWidth + // ) { + // setActiveReaction('like'); + // return; + // } + // if ( + // loveRef.current && + // loveRef.current.offsetLeft < ev.touches[0].clientX && + // ev.touches[0].clientX < loveRef.current.offsetLeft + loveRef.current.clientWidth + // ) { + // setActiveReaction('love'); + // return; + // } + // if ( + // fireRef.current && + // fireRef.current.offsetLeft < ev.touches[0].clientX && + // ev.touches[0].clientX < fireRef.current.offsetLeft + fireRef.current.clientWidth + // ) { + // setActiveReaction('fire'); + // return; + // } + // if ( + // happyRef.current && + // happyRef.current.offsetLeft < ev.touches[0].clientX && + // ev.touches[0].clientX < happyRef.current.offsetLeft + happyRef.current.clientWidth + // ) { + // setActiveReaction('happy'); + // return; + // } + // if ( + // cryingRef.current && + // cryingRef.current.offsetLeft < ev.touches[0].clientX && + // ev.touches[0].clientX < cryingRef.current.offsetLeft + cryingRef.current.clientWidth + // ) { + // setActiveReaction('crying'); + // return; + // } - setActiveReaction(null); - }} + // setActiveReaction(null); + // }} onTouchEnd={(ev) => { - touchTimerRef.current && clearTimeout(touchTimerRef.current); - touchTimerRef.current = null; - setIsShowReactionPanel(false); - if (activeReaction) { - setSelectedReaction(activeReaction); - onReactionClick(activeReaction); - setActiveReaction(null); - } else { - setSelectedReaction('like'); - onReactionClick('like'); - } + // touchTimerRef.current && clearTimeout(touchTimerRef.current); + // touchTimerRef.current = null; + // setIsShowReactionPanel(false); + // if (activeReaction) { + // setSelectedReaction(activeReaction); + // onReactionClick(activeReaction); + // setActiveReaction(null); + // } else { + // setSelectedReaction('like'); + onReactionClick('like'); + // } }} onMouseUp={(ev) => { - touchTimerRef.current && clearTimeout(touchTimerRef.current); - touchTimerRef.current = null; - setIsShowReactionPanel(false); - if (activeReaction) { - setSelectedReaction(activeReaction); - onReactionClick(activeReaction); - setActiveReaction(null); - } else { - setSelectedReaction('like'); - onReactionClick('like'); - } + // touchTimerRef.current && clearTimeout(touchTimerRef.current); + // touchTimerRef.current = null; + // setIsShowReactionPanel(false); + // if (activeReaction) { + // setSelectedReaction(activeReaction); + // onReactionClick(activeReaction); + // setActiveReaction(null); + // } else { + // setSelectedReaction('like'); + onReactionClick('like'); + // } }} > {myReaction ? ( @@ -277,7 +276,7 @@ export function ReactionButton({ > {typeof reactionsCount === 'number' ? reactionsCount : myReaction || config.text} </Typography.BodyBold> - {isShowReactionPanel ? ( + {/* {isShowReactionPanel ? ( <div className={styles.reactButton__panel}> <div className={styles.reactButton__panel__reaction} @@ -325,7 +324,7 @@ export function ReactionButton({ <Crying /> </div> </div> - ) : null} + ) : null} */} </div> ); } diff --git a/src/v4/social/pages/SocialGlobalSearchPage/SocialGlobalSearchPage.tsx b/src/v4/social/pages/SocialGlobalSearchPage/SocialGlobalSearchPage.tsx index bf9e8bb74..c228e7e13 100644 --- a/src/v4/social/pages/SocialGlobalSearchPage/SocialGlobalSearchPage.tsx +++ b/src/v4/social/pages/SocialGlobalSearchPage/SocialGlobalSearchPage.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import styles from './SocialGlobalSearchPage.module.css'; import { TopSearchBar } from '~/v4/social/components/TopSearchBar'; @@ -20,35 +20,49 @@ const useGlobalSearchViewModel = () => { const [searchType, setSearchType] = useState<AmityGlobalSearchType>( AmityGlobalSearchType.Community, ); + + const enabledUserSearch = useMemo( + () => searchType === AmityGlobalSearchType.User && searchKeyword.length > 0, + [searchType, searchKeyword], + ); + const communityCollection = useCommunitiesCollection( { displayName: searchKeyword, limit: 20 }, - () => searchType === AmityGlobalSearchType.Community, + () => searchType === AmityGlobalSearchType.Community && searchKeyword.length > 0, ); - const userCollection = useUserQueryByDisplayName({ displayName: searchKeyword, limit: 20 }); - const search = useCallback((keyword: string) => { - setSearchKeyword(keyword); - }, []); + const userCollection = useUserQueryByDisplayName({ + displayName: searchKeyword, + limit: 20, + enabled: enabledUserSearch, + }); + + const search = useCallback( + (keyword: string) => { + setSearchKeyword(keyword); + }, + [setSearchKeyword], + ); return { userCollection, communityCollection, searchType, search, + searchValue: searchKeyword, setSearchType, }; }; export function SocialGlobalSearchPage() { const pageId = 'social_global_search_page'; - const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = - useAmityPage({ - pageId, - }); + const { themeStyles } = useAmityPage({ + pageId, + }); const [activeTab, setActiveTab] = useState('communities'); - const { userCollection, communityCollection, searchType, search, setSearchType } = + const { userCollection, communityCollection, search, searchValue, setSearchType } = useGlobalSearchViewModel(); const tabs = [ @@ -89,12 +103,21 @@ export function SocialGlobalSearchPage() { return ( <div className={styles.socialGlobalSearchPage} style={themeStyles}> <TopSearchBar pageId={pageId} search={search} /> - <TabsBar - pageId={pageId} - tabs={tabs} - activeTab={activeTab} - onTabChange={(newTab) => setActiveTab(newTab)} - /> + {searchValue.length > 0 && ( + <TabsBar + pageId={pageId} + tabs={tabs} + activeTab={activeTab} + onTabChange={(newTab) => { + setActiveTab(newTab); + setSearchType( + newTab === 'communities' + ? AmityGlobalSearchType.Community + : AmityGlobalSearchType.User, + ); + }} + /> + )} </div> ); } From 93a2681b6e461bdf16fc15b80967c657f5c40797 Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Fri, 28 Jun 2024 17:39:51 +0700 Subject: [PATCH 176/300] feat: ASC-22903 - media attachment (#449) * feat: swipe bottom menu * feat. bottom menu position * feat: hide file button * style: overflow auto * style: hide scroll bar form * feat: add asc-uikit class * fix: show text condition --- src/v4/core/providers/AmityUIKitProvider.tsx | 100 +++++++++--------- .../DetailedMediaAttachment.module.css | 4 +- .../DetailedMediaAttachment.tsx | 1 - .../MediaAttachment.module.css | 2 +- .../MediaAttachment/MediaAttachment.tsx | 1 - .../CameraButton/CameraButton.module.css | 5 +- .../elements/CameraButton/CameraButton.tsx | 2 +- .../elements/FileButton/FileButton.module.css | 5 +- .../social/elements/FileButton/FileButton.tsx | 3 +- .../ImageButton/ImageButton.module.css | 5 +- .../elements/ImageButton/ImageButton.tsx | 2 +- .../VideoButton/VideoButton.module.css | 5 +- .../elements/VideoButton/VideoButton.tsx | 2 +- .../PostComposerPage.module.css | 38 ++++++- .../PostComposerPage/PostComposerPage.tsx | 83 +++++++++++---- src/v4/styles/global.css | 7 ++ 16 files changed, 168 insertions(+), 97 deletions(-) diff --git a/src/v4/core/providers/AmityUIKitProvider.tsx b/src/v4/core/providers/AmityUIKitProvider.tsx index 1d6f7f9c7..f10b5e4a1 100644 --- a/src/v4/core/providers/AmityUIKitProvider.tsx +++ b/src/v4/core/providers/AmityUIKitProvider.tsx @@ -135,55 +135,57 @@ const AmityUIKitProvider: React.FC<AmityUIKitProviderProps> = ({ if (!client) return null; return ( - <QueryClientProvider client={queryClient}> - <Localization locale="en"> - <CustomizationProvider initialConfig={configs || defaultConfig}> - <StyledThemeProvider theme={buildGlobalTheme(theme)}> - <ThemeProvider> - <CustomReactionProvider> - <SDKContextV3.Provider value={sdkContextValue}> - <SDKContext.Provider value={sdkContextValue}> - <SDKConnectorProviderV3> - <SDKConnectorProvider> - <NotificationProvider> - <DrawerProvider> - <LegacyNotificationProvider> - <ConfirmProvider> - <LegacyConfirmProvider> - <ConfigProvider - config={{ - socialCommunityCreationButtonVisible: - socialCommunityCreationButtonVisible || true, - }} - > - <PostRendererProvider config={postRendererConfig}> - <NavigationProvider> - <PageBehaviorProvider pageBehavior={pageBehavior}> - {children} - </PageBehaviorProvider> - </NavigationProvider> - </PostRendererProvider> - </ConfigProvider> - <NotificationsContainer /> - <LegacyNotificationsContainer /> - <ConfirmComponent /> - <DrawerContainer /> - <LegacyConfirmComponent /> - </LegacyConfirmProvider> - </ConfirmProvider> - </LegacyNotificationProvider> - </DrawerProvider> - </NotificationProvider> - </SDKConnectorProvider> - </SDKConnectorProviderV3> - </SDKContext.Provider> - </SDKContextV3.Provider> - </CustomReactionProvider> - </ThemeProvider> - </StyledThemeProvider> - </CustomizationProvider> - </Localization> - </QueryClientProvider> + <div className="asc-uikit"> + <QueryClientProvider client={queryClient}> + <Localization locale="en"> + <CustomizationProvider initialConfig={configs || defaultConfig}> + <StyledThemeProvider theme={buildGlobalTheme(theme)}> + <ThemeProvider> + <CustomReactionProvider> + <SDKContextV3.Provider value={sdkContextValue}> + <SDKContext.Provider value={sdkContextValue}> + <SDKConnectorProviderV3> + <SDKConnectorProvider> + <NotificationProvider> + <DrawerProvider> + <LegacyNotificationProvider> + <ConfirmProvider> + <LegacyConfirmProvider> + <ConfigProvider + config={{ + socialCommunityCreationButtonVisible: + socialCommunityCreationButtonVisible || true, + }} + > + <PostRendererProvider config={postRendererConfig}> + <NavigationProvider> + <PageBehaviorProvider pageBehavior={pageBehavior}> + {children} + </PageBehaviorProvider> + </NavigationProvider> + </PostRendererProvider> + </ConfigProvider> + <NotificationsContainer /> + <LegacyNotificationsContainer /> + <ConfirmComponent /> + <DrawerContainer /> + <LegacyConfirmComponent /> + </LegacyConfirmProvider> + </ConfirmProvider> + </LegacyNotificationProvider> + </DrawerProvider> + </NotificationProvider> + </SDKConnectorProvider> + </SDKConnectorProviderV3> + </SDKContext.Provider> + </SDKContextV3.Provider> + </CustomReactionProvider> + </ThemeProvider> + </StyledThemeProvider> + </CustomizationProvider> + </Localization> + </QueryClientProvider> + </div> ); }; diff --git a/src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.module.css b/src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.module.css index f89a03314..8c038d48a 100644 --- a/src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.module.css +++ b/src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.module.css @@ -1,11 +1,12 @@ .detailedMediaAttachment { display: block; width: 100%; - background-color: var(--asc-color-white); + background-color: var(--asc-color-base-background); border-top-left-radius: 1.25rem; border-top-right-radius: 1.25rem; box-shadow: var(--asc-box-shadow-04); padding-bottom: 2rem; + padding-top: 0.75rem; } .detailedMediaAttachment__swipeDown { @@ -17,5 +18,4 @@ background-color: var(--asc-color-base-shade3); margin-top: 0.75rem; margin-bottom: 1.25rem; - bottom: 0; } diff --git a/src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.tsx b/src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.tsx index 1ec0da750..9163482f9 100644 --- a/src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.tsx +++ b/src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.tsx @@ -26,7 +26,6 @@ export function DetailedMediaAttachment({ pageId }: DetailedMediaAttachmentProps <CameraButton pageId={pageId} componentId={componentId} /> <ImageButton pageId={pageId} componentId={componentId} /> <VideoButton pageId={pageId} componentId={componentId} /> - <FileButton pageId={pageId} componentId={componentId} /> </div> ); } diff --git a/src/v4/social/components/MediaAttachment/MediaAttachment.module.css b/src/v4/social/components/MediaAttachment/MediaAttachment.module.css index eb505cea7..9a69e2307 100644 --- a/src/v4/social/components/MediaAttachment/MediaAttachment.module.css +++ b/src/v4/social/components/MediaAttachment/MediaAttachment.module.css @@ -4,7 +4,7 @@ justify-content: space-between; width: 100%; padding: 0.5rem 1rem; - background-color: var(--asc-color-white); + background-color: var(--asc-color-base-background); border-top-left-radius: 1.25rem; border-top-right-radius: 1.25rem; box-shadow: var(--asc-box-shadow-04); diff --git a/src/v4/social/components/MediaAttachment/MediaAttachment.tsx b/src/v4/social/components/MediaAttachment/MediaAttachment.tsx index 1187ab845..f249b9a58 100644 --- a/src/v4/social/components/MediaAttachment/MediaAttachment.tsx +++ b/src/v4/social/components/MediaAttachment/MediaAttachment.tsx @@ -23,7 +23,6 @@ export function MediaAttachment({ pageId }: MediaAttachmentProps) { <CameraButton pageId={pageId} componentId={componentId} /> <ImageButton pageId={pageId} componentId={componentId} /> <VideoButton pageId={pageId} componentId={componentId} /> - <FileButton pageId={pageId} componentId={componentId} /> </div> </div> ); diff --git a/src/v4/social/elements/CameraButton/CameraButton.module.css b/src/v4/social/elements/CameraButton/CameraButton.module.css index 6ab4a859b..e70fb016c 100644 --- a/src/v4/social/elements/CameraButton/CameraButton.module.css +++ b/src/v4/social/elements/CameraButton/CameraButton.module.css @@ -2,6 +2,7 @@ display: flex; align-items: center; padding: 0.75rem 1rem; + gap: 0.75rem; } .cameraButton__icon { @@ -12,7 +13,3 @@ border-radius: 50%; padding: 0.25rem; } - -.cameraButton__text { - margin-left: 0.75rem; -} diff --git a/src/v4/social/elements/CameraButton/CameraButton.tsx b/src/v4/social/elements/CameraButton/CameraButton.tsx index c42e6a2e8..04bd86048 100644 --- a/src/v4/social/elements/CameraButton/CameraButton.tsx +++ b/src/v4/social/elements/CameraButton/CameraButton.tsx @@ -69,7 +69,7 @@ export function CameraButton({ defaultIconName={defaultConfig.icon} configIconName={config.icon} /> - <Typography.BodyBold className={styles.cameraButton__text}>{config.text}</Typography.BodyBold> + {config.text && <Typography.BodyBold>{config.text}</Typography.BodyBold>} </div> ); } diff --git a/src/v4/social/elements/FileButton/FileButton.module.css b/src/v4/social/elements/FileButton/FileButton.module.css index a9be121ad..bfb98b2b4 100644 --- a/src/v4/social/elements/FileButton/FileButton.module.css +++ b/src/v4/social/elements/FileButton/FileButton.module.css @@ -2,6 +2,7 @@ display: flex; align-items: center; padding: 0.75rem 1rem; + gap: 0.75rem; } .fileButton__icon { @@ -12,7 +13,3 @@ border-radius: 50%; padding: 0.25rem; } - -.fileButton__text { - margin-left: 0.75rem; -} diff --git a/src/v4/social/elements/FileButton/FileButton.tsx b/src/v4/social/elements/FileButton/FileButton.tsx index caa4d4e70..4267e0e86 100644 --- a/src/v4/social/elements/FileButton/FileButton.tsx +++ b/src/v4/social/elements/FileButton/FileButton.tsx @@ -62,8 +62,7 @@ export function FileButton({ defaultIconName={defaultConfig.icon} configIconName={config.icon} /> - - <Typography.BodyBold className={styles.fileButton__text}>{config.text}</Typography.BodyBold> + {config.text && <Typography.BodyBold>{config.text}</Typography.BodyBold>} </div> ); } diff --git a/src/v4/social/elements/ImageButton/ImageButton.module.css b/src/v4/social/elements/ImageButton/ImageButton.module.css index c8c874bd7..d37774386 100644 --- a/src/v4/social/elements/ImageButton/ImageButton.module.css +++ b/src/v4/social/elements/ImageButton/ImageButton.module.css @@ -2,6 +2,7 @@ display: flex; align-items: center; padding: 0.75rem 1rem; + gap: 0.75rem; } .imageButton__icon { @@ -12,7 +13,3 @@ border-radius: 50%; padding: 0.25rem; } - -.imageButton__text { - margin-left: 0.75rem; -} diff --git a/src/v4/social/elements/ImageButton/ImageButton.tsx b/src/v4/social/elements/ImageButton/ImageButton.tsx index 7aeb86483..04bd0f1c7 100644 --- a/src/v4/social/elements/ImageButton/ImageButton.tsx +++ b/src/v4/social/elements/ImageButton/ImageButton.tsx @@ -70,7 +70,7 @@ export function ImageButton({ defaultIconName={defaultConfig.icon} configIconName={config.icon} /> - <Typography.BodyBold className={styles.imageButton__text}>{config.text}</Typography.BodyBold> + {config.text && <Typography.BodyBold>{config.text}</Typography.BodyBold>} </div> ); } diff --git a/src/v4/social/elements/VideoButton/VideoButton.module.css b/src/v4/social/elements/VideoButton/VideoButton.module.css index 90da13076..d33d1a1f3 100644 --- a/src/v4/social/elements/VideoButton/VideoButton.module.css +++ b/src/v4/social/elements/VideoButton/VideoButton.module.css @@ -2,6 +2,7 @@ display: flex; align-items: center; padding: 0.75rem 1rem; + gap: 0.75rem; } .videoButton__icon { @@ -12,7 +13,3 @@ border-radius: 50%; padding: 0.25rem; } - -.videoButton__text { - margin-left: 0.75rem; -} diff --git a/src/v4/social/elements/VideoButton/VideoButton.tsx b/src/v4/social/elements/VideoButton/VideoButton.tsx index bef031691..ad2588017 100644 --- a/src/v4/social/elements/VideoButton/VideoButton.tsx +++ b/src/v4/social/elements/VideoButton/VideoButton.tsx @@ -68,7 +68,7 @@ export function VideoButton({ defaultIconName={defaultConfig.icon} configIconName={config.icon} /> - <Typography.BodyBold className={styles.videoButton__text}>{config.text}</Typography.BodyBold> + {config.text && <Typography.BodyBold>{config.text}</Typography.BodyBold>} </div> ); } diff --git a/src/v4/social/pages/PostComposerPage/PostComposerPage.module.css b/src/v4/social/pages/PostComposerPage/PostComposerPage.module.css index 685f37be9..bcde8bfe5 100644 --- a/src/v4/social/pages/PostComposerPage/PostComposerPage.module.css +++ b/src/v4/social/pages/PostComposerPage/PostComposerPage.module.css @@ -5,7 +5,7 @@ height: 100%; } -.selectPostTargetPage__topBar { +.postComposerPage__topBar { width: 100%; display: flex; justify-content: space-between; @@ -13,11 +13,11 @@ padding: var(--asc-spacing-m1); } -.selectPostTargetPage__status { +.postComposerPage__status { + width: calc(100% - 2rem); position: absolute; - left: 1rem; bottom: 0; - width: calc(100% - 2rem); + left: 1rem; display: flex; justify-content: start; padding: unset; @@ -35,3 +35,33 @@ height: 1.5rem; fill: var(--asc-color-base-background); } + +.postComposerPage__notiWrap { + position: relative; + top: 0; +} + +.postComposerPage__formLong { + max-height: calc(100% - 92px); + overflow: auto; +} + +.postComposerPage__formShort { + max-height: calc(100% - 236px); /* 290px including file button */ + overflow: auto; +} + +.postComposerPage__formLong::-webkit-scrollbar, +.postComposerPage__formShort::-webkit-scrollbar { + display: none; +} + +/* vaul */ + +.drawer__content { + position: absolute; + width: 100%; + top: 0; + left: 0; + outline: none; +} diff --git a/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx b/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx index 643387e68..9bc5facb0 100644 --- a/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx +++ b/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx @@ -1,4 +1,4 @@ -import React, { useRef, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import styles from './PostComposerPage.module.css'; import { useAmityPage } from '~/v4/core/hooks/uikit'; import { CloseButton } from '~/v4/social/elements/CloseButton/CloseButton'; @@ -15,6 +15,10 @@ import { Spinner } from '../../internal-components/Spinner'; import ExclamationCircle from '~/v4/icons/ExclamationCircle'; import { useForm } from 'react-hook-form'; import { Mentioned } from '~/v4/helpers/utils'; +import { Drawer } from 'vaul'; +import { MediaAttachment } from '../../components/MediaAttachment'; +import { DetailedMediaAttachment } from '../../components/DetailedMediaAttachment'; +import ReactDOM from 'react-dom'; export enum Mode { CREATE = 'create', @@ -75,9 +79,17 @@ const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCr const { themeStyles } = useAmityPage({ pageId, }); + + // const HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU = '290px'; //Including file button + const HEIGHT_MEDIA_ATTACHMENT_MENU = '92px'; + const HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU = '236px'; //Not including file button + const { onBack } = useNavigation(); const editorRef = useRef<LexicalEditor | null>(null); const { AmityPostComposerPageBehavior } = usePageBehavior(); + const [snap, setSnap] = useState<number | string | null>(HEIGHT_MEDIA_ATTACHMENT_MENU); + const [isShowBottomMenu, setIsShowBottomMenu] = useState<boolean>(true); + const drawerRef = useRef<HTMLDivElement>(null); const [textValue, setTextValue] = useState<createPostParams>({ text: '', @@ -122,10 +134,24 @@ const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCr setTextValue(val); }; + const handleSnapChange = (newSnap: string | number | null) => { + if (snap === HEIGHT_MEDIA_ATTACHMENT_MENU && newSnap === '0px') { + return; + } + setSnap(newSnap); + }; + return ( <div className={styles.postComposerPage} style={themeStyles}> - <form onSubmit={handleSubmit(onSubmit)}> - <div className={styles.selectPostTargetPage__topBar}> + <form + onSubmit={handleSubmit(onSubmit)} + className={ + snap == HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU + ? styles.postComposerPage__formShort + : styles.postComposerPage__formLong + } + > + <div className={styles.postComposerPage__topBar}> <CloseButton pageId={pageId} onPress={onBack} /> <CommunityDisplayName pageId={pageId} community={community} /> <CreateNewPostButton @@ -134,23 +160,44 @@ const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCr isValid={textValue.text.length > 0} /> </div> - <PostTextField ref={editorRef} onChange={onChange} /> - {isPending && ( - <Notification - content="Posting..." - icon={<Spinner />} - className={styles.selectPostTargetPage__status} - /> - )} - {isError && ( - <Notification - content="Failed to create post" - icon={<ExclamationCircle className={styles.selectPostTargetPag_infoIcon} />} - className={styles.selectPostTargetPage__status} - /> - )} </form> + <div ref={drawerRef}></div> + {drawerRef.current ? ReactDOM.createPortal( + <Drawer.Root + snapPoints={[HEIGHT_MEDIA_ATTACHMENT_MENU, HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU]} + activeSnapPoint={snap} + setActiveSnapPoint={handleSnapChange} + open={isShowBottomMenu} + modal={false} + > + <Drawer.Portal container={drawerRef.current}> + <Drawer.Content className={styles.drawer__content}> + <div className={styles.postComposerPage__notiWrap}> + {isPending && ( + <Notification + content="Posting..." + icon={<Spinner />} + className={styles.postComposerPage__status} + /> + )} + {isError && ( + <Notification + content="Failed to create post" + icon={<ExclamationCircle className={styles.selectPostTargetPag_infoIcon} />} + className={styles.postComposerPage__status} + /> + )} + </div> + {snap == HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU ? ( + <DetailedMediaAttachment pageId={pageId} /> + ) : ( + <MediaAttachment pageId={pageId} /> + )} + </Drawer.Content> + </Drawer.Portal> + </Drawer.Root>, drawerRef.current + ) : null } </div> ); }; diff --git a/src/v4/styles/global.css b/src/v4/styles/global.css index a09bb11a1..214774cc5 100644 --- a/src/v4/styles/global.css +++ b/src/v4/styles/global.css @@ -169,3 +169,10 @@ --asc-color-base-background: #191919; } } + +/* vaul */ +.asc-uikit { + [vaul-drawer][vaul-drawer-direction='bottom']::after { + height: unset; + } +} From 41f09516bf13c088d96664d7122ebbc3d8aedd74 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Fri, 28 Jun 2024 17:48:08 +0700 Subject: [PATCH 177/300] fix: ASC-23543 - fix PostDetailPage navigation and layout (#461) * fix: fix PostDetailPage navigation and layout * fix: style * fix: post container style --- src/v4/core/providers/NavigationProvider.tsx | 2 +- .../pages/PostDetailPage/PostDetailPage.module.css | 2 ++ src/v4/social/pages/PostDetailPage/PostDetailPage.tsx | 9 ++++----- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/v4/core/providers/NavigationProvider.tsx b/src/v4/core/providers/NavigationProvider.tsx index 04f4dfb67..dc31674b7 100644 --- a/src/v4/core/providers/NavigationProvider.tsx +++ b/src/v4/core/providers/NavigationProvider.tsx @@ -569,7 +569,7 @@ export default function NavigationProvider({ setNavigationBlocker, }} > - <div style={{ position: 'relative' }}>{children}</div> + {children} </NavigationContext.Provider> ); } diff --git a/src/v4/social/pages/PostDetailPage/PostDetailPage.module.css b/src/v4/social/pages/PostDetailPage/PostDetailPage.module.css index 8d0815c06..3818cf16c 100644 --- a/src/v4/social/pages/PostDetailPage/PostDetailPage.module.css +++ b/src/v4/social/pages/PostDetailPage/PostDetailPage.module.css @@ -7,6 +7,8 @@ } .postDetailPage__container { + flex: 1; + margin-top: 3.625rem; overflow-y: scroll; scrollbar-width: none; -ms-overflow-style: none; diff --git a/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx b/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx index c15c9a65a..23d1427c0 100644 --- a/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx +++ b/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx @@ -24,10 +24,9 @@ interface PostDetailPageProps { export function PostDetailPage({ id }: PostDetailPageProps) { const pageId = 'post_detail_page'; const { post, isLoading: isPostLoading, error } = usePost(id); - const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = - useAmityPage({ - pageId, - }); + const { themeStyles } = useAmityPage({ + pageId, + }); const { onBack } = useNavigation(); const [replyComment, setReplyComment] = useState<Amity.Comment | null>(null); @@ -83,7 +82,7 @@ export function PostDetailPage({ id }: PostDetailPageProps) { <BackButton pageId={pageId} defaultClassName={styles.postDetailPage__backIcon} - onClick={() => onBack()} + onPress={() => onBack()} /> <Typography.Title className={styles.postDetailPage__topBar__title}>Post</Typography.Title> <div className={styles.postDetailPage__topBar__menuBar}> From d9330befee821fa2f5e0f2cf85e8ee6c061304d6 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Fri, 28 Jun 2024 18:03:41 +0700 Subject: [PATCH 178/300] fix: shouldCall useEffect logic (#462) --- src/v4/core/hooks/useLiveCollection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v4/core/hooks/useLiveCollection.ts b/src/v4/core/hooks/useLiveCollection.ts index e53b58f4f..4c1a38429 100644 --- a/src/v4/core/hooks/useLiveCollection.ts +++ b/src/v4/core/hooks/useLiveCollection.ts @@ -54,7 +54,7 @@ function useLiveCollection<TCallback, TParams>({ ); useEffect(() => { - if (shouldCall) return; + if (!shouldCall) return; const { unsubscribe } = subscribe({ fetcher, params, From ec692a0bd0fc5620cbca692f4d19fd25b40fb2d7 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Fri, 28 Jun 2024 19:11:18 +0700 Subject: [PATCH 179/300] fix: ASC-22949 - view story navigate logic (#458) * fix: select file to pause story progress bar * fix: story navigate * fix: remove unused * fix: remove unused * fix: add ref to image * fix: remove unused * fix: package --- package.json | 2 +- pnpm-lock.yaml | 358 +++++++++++++++++- src/global.d.ts | 13 + .../StoryViewer/Renderers/Image.tsx | 45 ++- src/v4/social/pages/DraftsPage/DraftsPage.tsx | 67 ++-- .../pages/StoryPage/CommunityFeedStory.tsx | 221 ++++------- .../pages/StoryPage/GlobalFeedStory.tsx | 86 +---- .../pages/StoryPage/StoryPage.module.css | 4 + 8 files changed, 535 insertions(+), 261 deletions(-) diff --git a/package.json b/package.json index fd8a96b3c..657007bc6 100644 --- a/package.json +++ b/package.json @@ -111,8 +111,8 @@ "@radix-ui/react-tabs": "^1.0.4", "@tanstack/react-query": "^5.28.14", "clsx": "^2.1.0", + "colorthief": "^2.4.0", "dayjs": "^1.11.11", - "extract-colors": "^4.0.2", "filesize": "^9.0.11", "framer-motion": "^11.1.7", "hls.js": "^1.4.14", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c57a17b7b..f4472ea75 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,12 +35,12 @@ importers: clsx: specifier: ^2.1.0 version: 2.1.1 + colorthief: + specifier: ^2.4.0 + version: 2.4.0 dayjs: specifier: ^1.11.11 version: 1.11.11 - extract-colors: - specifier: ^4.0.2 - version: 4.0.6 filesize: specifier: ^9.0.11 version: 9.0.11 @@ -1595,6 +1595,9 @@ packages: peerDependencies: yjs: '>=13.5.22' + '@lokesh.dhakar/quantize@1.3.0': + resolution: {integrity: sha512-4KBSyaMj65d8A+2vnzLxtHFu4OmBU4IKO0yLxZ171Itdf9jGV4w+WbG7VsKts2jUdRkFSzsZqpZOz6hTB3qGAw==} + '@mdx-js/react@2.3.0': resolution: {integrity: sha512-zQH//gdOmuu7nt2oJR29vFhDv88oGPmVw6BggmrHeMI+xgEkp1B2dX9/bMBSYtK0dyLX/aOmesKS09g222K1/g==} peerDependencies: @@ -3353,6 +3356,13 @@ packages: resolution: {integrity: sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==} engines: {node: '>=8'} + asn1@0.2.6: + resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} + + assert-plus@1.0.0: + resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} + engines: {node: '>=0.8'} + assert@2.1.0: resolution: {integrity: sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==} @@ -3384,6 +3394,12 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} + aws-sign2@0.7.0: + resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==} + + aws4@1.13.0: + resolution: {integrity: sha512-3AungXC4I8kKsS9PuS4JH2nc+0bVY/mjgrephHTIi8fpEeGsTHBUJeosp0Wc1myYMElmD0B3Oc4XL/HVJ4PV2g==} + axe-core@4.9.1: resolution: {integrity: sha512-QbUdXJVTpvUTHU7871ppZkdOLBeGUKBQWHkHrvN2V9IQWGMt61zf3B45BtzjxEJzYuj0JBjBZP/hmYS/R9pmAw==} engines: {node: '>=4'} @@ -3455,6 +3471,9 @@ packages: batch-processor@1.0.0: resolution: {integrity: sha512-xoLQD8gmmR32MeuBHgH0Tzd5PuSZx71ZsbhVxOCRbgktZEPe4SQy7s9Z50uPp0F/f7iw2XmkHN2xkgbMfckMDA==} + bcrypt-pbkdf@1.0.2: + resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} + better-assert@1.0.2: resolution: {integrity: sha512-bYeph2DFlpK1XmGs6fvlLRUN29QISM3GBuUwSFsMY2XRx4AvC0WNCS57j4c/xGrK2RS24C1w3YoBOsw9fT46tQ==} @@ -3571,6 +3590,9 @@ packages: caniuse-lite@1.0.30001636: resolution: {integrity: sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg==} + caseless@0.12.0: + resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} + chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -3688,6 +3710,9 @@ packages: colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + colorthief@2.4.0: + resolution: {integrity: sha512-0U48RGNRo5fVO+yusBwgp+d3augWSorXabnqXUu9SabEhCpCgZJEUjUTTI41OOBBYuMMxawa3177POT6qLfLeQ==} + combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -3851,6 +3876,9 @@ packages: core-js-compat@3.37.1: resolution: {integrity: sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==} + core-util-is@1.0.2: + resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} + core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -3909,10 +3937,20 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + cwise-compiler@1.1.3: + resolution: {integrity: sha512-WXlK/m+Di8DMMcCjcWr4i+XzcQra9eCdXIJrgh4TUgh0pIS/yJduLxS9JgefsHJ/YVLdgPtXm9r62W92MvanEQ==} + dargs@7.0.0: resolution: {integrity: sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==} engines: {node: '>=8'} + dashdash@1.14.1: + resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==} + engines: {node: '>=0.10'} + + data-uri-to-buffer@0.0.3: + resolution: {integrity: sha512-Cp+jOa8QJef5nXS5hU7M1DWzXPEIoVR3kbV0dQuVGwROZg8bGf1DcCnkmajBTnvghTtSNMUdRrPjgaT6ZQucbw==} + data-view-buffer@1.0.1: resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} engines: {node: '>= 0.4'} @@ -4104,6 +4142,9 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + ecc-jsbn@0.1.2: + resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} + ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -4393,13 +4434,14 @@ packages: extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - extract-colors@4.0.6: - resolution: {integrity: sha512-U+pYyQKXCSHOmtZPIEJBGLJjLDiqS+oOub2ILA3a7UGt9+IvZvwAN3hOPFjUgT+gX/apSBwP5vBgnKMlV0fy8Q==} - extract-zip@1.7.0: resolution: {integrity: sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==} hasBin: true + extsprintf@1.3.0: + resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==} + engines: {'0': node >=0.6.0} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -4529,6 +4571,13 @@ packages: resolution: {integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==} engines: {node: '>=14'} + forever-agent@0.6.1: + resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==} + + form-data@2.3.3: + resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==} + engines: {node: '>= 0.12'} + form-data@4.0.0: resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} engines: {node: '>= 6'} @@ -4615,6 +4664,9 @@ packages: resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} engines: {node: '>=8.0.0'} + get-pixels@3.3.3: + resolution: {integrity: sha512-5kyGBn90i9tSMUVHTqkgCHsoWoR+/lGbl4yC83Gefyr0HLIhgSWEx/2F/3YgsZ7UpYNuM6pDhDK7zebrUJ5nXg==} + get-pkg-repo@4.2.1: resolution: {integrity: sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA==} engines: {node: '>=6.9.0'} @@ -4639,6 +4691,9 @@ packages: get-tsconfig@4.7.5: resolution: {integrity: sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==} + getpass@0.1.7: + resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} + giget@1.2.3: resolution: {integrity: sha512-8EHPljDvs7qKykr6uw8b+lqLiUc/vUg+KVTI0uND4s63TdsZM2Xus3mflvF0DDG9SiM4RlCkFGL+7aAjRmV7KA==} hasBin: true @@ -4737,6 +4792,15 @@ packages: engines: {node: '>=0.4.7'} hasBin: true + har-schema@2.0.0: + resolution: {integrity: sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==} + engines: {node: '>=4'} + + har-validator@5.1.5: + resolution: {integrity: sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==} + engines: {node: '>=6'} + deprecated: this library is no longer supported + hard-rejection@2.1.0: resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} engines: {node: '>=6'} @@ -4804,6 +4868,10 @@ packages: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} + http-signature@1.2.0: + resolution: {integrity: sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==} + engines: {node: '>=0.8', npm: '>=1.3.7'} + https-proxy-agent@4.0.0: resolution: {integrity: sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==} engines: {node: '>= 6.0.0'} @@ -4903,6 +4971,9 @@ packages: invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} + iota-array@1.0.0: + resolution: {integrity: sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA==} + ip@2.0.1: resolution: {integrity: sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==} @@ -4936,6 +5007,9 @@ packages: resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} engines: {node: '>= 0.4'} + is-buffer@1.1.6: + resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} + is-callable@1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} @@ -5063,6 +5137,9 @@ packages: resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} engines: {node: '>= 0.4'} + is-typedarray@1.0.0: + resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} + is-unicode-supported@0.1.0: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} @@ -5099,6 +5176,9 @@ packages: isomorphic.js@0.2.5: resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==} + isstream@0.1.2: + resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} + istanbul-lib-coverage@3.2.2: resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} engines: {node: '>=8'} @@ -5269,6 +5349,9 @@ packages: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} + jpeg-js@0.4.4: + resolution: {integrity: sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==} + js-base64@3.7.7: resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==} @@ -5289,6 +5372,9 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true + jsbn@0.1.1: + resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} + jscodeshift@0.15.2: resolution: {integrity: sha512-FquR7Okgmc4Sd0aEDwqho3rEiKR3BdvuG9jfdHjLJ6JQoWSMpavug3AoIfnfWhxFlf+5pzQh8qjqz0DWFrNQzA==} hasBin: true @@ -5322,6 +5408,9 @@ packages: json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-schema@0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -5344,6 +5433,10 @@ packages: resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} engines: {'0': node >= 0.2.0} + jsprim@1.4.2: + resolution: {integrity: sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==} + engines: {node: '>=0.6.0'} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -5740,6 +5833,12 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + ndarray-pack@1.2.1: + resolution: {integrity: sha512-51cECUJMT0rUZNQa09EoKsnFeDL4x2dHRT0VR5U2H5ZgEcm95ZDWcMA5JShroXjHOejmAD/fg8+H+OvUnVXz2g==} + + ndarray@1.0.19: + resolution: {integrity: sha512-B4JHA4vdyZU30ELBw3g7/p9bZupyew5a7tX1Y/gGeF2hafrPaQZhgrGQfsvgfYbgdFZjYwuEcnaobeM/WMW+HQ==} + needle@3.3.1: resolution: {integrity: sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==} engines: {node: '>= 4.4.x'} @@ -5752,6 +5851,10 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + node-bitmap@0.0.1: + resolution: {integrity: sha512-Jx5lPaaLdIaOsj2mVLWMWulXF6GQVdyLvNSxmiYCvZ8Ma2hfKX0POoR2kgKOqz+oFsRreq0yYZjQ2wjE9VNzCA==} + engines: {node: '>=v0.6.5'} + node-dir@0.1.17: resolution: {integrity: sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==} engines: {node: '>= 0.10.5'} @@ -5805,6 +5908,9 @@ packages: engines: {node: ^14.16.0 || >=16.10.0} hasBin: true + oauth-sign@0.9.0: + resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -5846,6 +5952,9 @@ packages: ohash@1.1.3: resolution: {integrity: sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw==} + omggif@1.0.10: + resolution: {integrity: sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==} + on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} @@ -5924,6 +6033,9 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse-data-uri@0.2.0: + resolution: {integrity: sha512-uOtts8NqDcaCt1rIsO3VFDRsAfgE4c6osG4d9z3l4dCBlxYFzni6Di/oNU270SDrjkfZuUvLZx1rxMyqh46Y9w==} + parse-json@4.0.0: resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} engines: {node: '>=4'} @@ -5993,6 +6105,9 @@ packages: pend@1.2.0: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + performance-now@2.1.0: + resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} + picocolors@1.0.1: resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} @@ -6033,6 +6148,10 @@ packages: resolution: {integrity: sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==} engines: {node: '>=10'} + pngjs@3.4.0: + resolution: {integrity: sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==} + engines: {node: '>=4.0.0'} + polished@4.3.1: resolution: {integrity: sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==} engines: {node: '>=10'} @@ -6157,6 +6276,9 @@ packages: prr@1.0.1: resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} + psl@1.9.0: + resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} + pump@2.0.1: resolution: {integrity: sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==} @@ -6193,6 +6315,10 @@ packages: resolution: {integrity: sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==} engines: {node: '>=0.6'} + qs@6.5.3: + resolution: {integrity: sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==} + engines: {node: '>=0.6'} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -6485,6 +6611,11 @@ packages: remark-slug@6.1.0: resolution: {integrity: sha512-oGCxDF9deA8phWvxFuyr3oSJsdyUAxMFbA0mZ7Y1Sas+emILtO+e5WutF9564gDsEN4IXaQXm5pFo6MLH+YmwQ==} + request@2.88.2: + resolution: {integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==} + engines: {node: '>= 6'} + deprecated: request has been deprecated, see https://github.com/request/request/issues/3142 + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -6750,6 +6881,11 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + sshpk@1.18.0: + resolution: {integrity: sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==} + engines: {node: '>=0.10.0'} + hasBin: true + stack-generator@2.0.10: resolution: {integrity: sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==} @@ -7060,6 +7196,10 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + tough-cookie@2.5.0: + resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==} + engines: {node: '>=0.8'} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -7163,6 +7303,12 @@ packages: peerDependencies: typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + + tweetnacl@0.14.5: + resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -7266,6 +7412,9 @@ packages: resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==} engines: {node: '>=4'} + uniq@1.0.1: + resolution: {integrity: sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA==} + unique-string@2.0.0: resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} engines: {node: '>=8'} @@ -7368,6 +7517,11 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} + uuid@3.4.0: + resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} + deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. + hasBin: true + uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true @@ -7393,6 +7547,10 @@ packages: react: ^16.8 || ^17.0 || ^18.0 react-dom: ^16.8 || ^17.0 || ^18.0 + verror@1.10.0: + resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} + engines: {'0': node >=0.6.0} + vite-tsconfig-paths@4.3.2: resolution: {integrity: sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==} peerDependencies: @@ -9180,6 +9338,8 @@ snapshots: lexical: 0.16.0 yjs: 13.6.17 + '@lokesh.dhakar/quantize@1.3.0': {} + '@mdx-js/react@2.3.0(react@18.3.1)': dependencies: '@types/mdx': 2.0.13 @@ -11843,6 +12003,12 @@ snapshots: arrify@2.0.1: {} + asn1@0.2.6: + dependencies: + safer-buffer: 2.1.2 + + assert-plus@1.0.0: {} + assert@2.1.0: dependencies: call-bind: 1.0.7 @@ -11877,6 +12043,10 @@ snapshots: dependencies: possible-typed-array-names: 1.0.0 + aws-sign2@0.7.0: {} + + aws4@1.13.0: {} + axe-core@4.9.1: {} axios@1.7.2(debug@4.3.5): @@ -11979,6 +12149,10 @@ snapshots: batch-processor@1.0.0: {} + bcrypt-pbkdf@1.0.2: + dependencies: + tweetnacl: 0.14.5 + better-assert@1.0.2: dependencies: callsite: 1.0.0 @@ -12102,6 +12276,8 @@ snapshots: caniuse-lite@1.0.30001636: {} + caseless@0.12.0: {} + chalk@2.4.2: dependencies: ansi-styles: 3.2.1 @@ -12212,6 +12388,11 @@ snapshots: colorette@2.0.20: {} + colorthief@2.4.0: + dependencies: + '@lokesh.dhakar/quantize': 1.3.0 + get-pixels: 3.3.3 + combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 @@ -12416,6 +12597,8 @@ snapshots: dependencies: browserslist: 4.23.1 + core-util-is@1.0.2: {} + core-util-is@1.0.3: {} cosmiconfig@9.0.0(typescript@4.9.5): @@ -12483,8 +12666,18 @@ snapshots: csstype@3.1.3: {} + cwise-compiler@1.1.3: + dependencies: + uniq: 1.0.1 + dargs@7.0.0: {} + dashdash@1.14.1: + dependencies: + assert-plus: 1.0.0 + + data-uri-to-buffer@0.0.3: {} + data-view-buffer@1.0.1: dependencies: call-bind: 1.0.7 @@ -12656,6 +12849,11 @@ snapshots: eastasianwidth@0.2.0: {} + ecc-jsbn@0.1.2: + dependencies: + jsbn: 0.1.1 + safer-buffer: 2.1.2 + ee-first@1.1.1: {} ejs@3.1.10: @@ -13138,8 +13336,6 @@ snapshots: extend@3.0.2: {} - extract-colors@4.0.6: {} - extract-zip@1.7.0: dependencies: concat-stream: 1.6.2 @@ -13149,6 +13345,8 @@ snapshots: transitivePeerDependencies: - supports-color + extsprintf@1.3.0: {} + fast-deep-equal@3.1.3: {} fast-glob@3.3.2: @@ -13288,6 +13486,14 @@ snapshots: cross-spawn: 7.0.3 signal-exit: 4.1.0 + forever-agent@0.6.1: {} + + form-data@2.3.3: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + form-data@4.0.0: dependencies: asynckit: 0.4.0 @@ -13360,6 +13566,20 @@ snapshots: get-package-type@0.1.0: {} + get-pixels@3.3.3: + dependencies: + data-uri-to-buffer: 0.0.3 + jpeg-js: 0.4.4 + mime-types: 2.1.35 + ndarray: 1.0.19 + ndarray-pack: 1.2.1 + node-bitmap: 0.0.1 + omggif: 1.0.10 + parse-data-uri: 0.2.0 + pngjs: 3.4.0 + request: 2.88.2 + through: 2.3.8 + get-pkg-repo@4.2.1: dependencies: '@hutson/parse-repository-url': 3.0.2 @@ -13383,6 +13603,10 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + getpass@0.1.7: + dependencies: + assert-plus: 1.0.0 + giget@1.2.3: dependencies: citty: 0.1.6 @@ -13510,6 +13734,13 @@ snapshots: optionalDependencies: uglify-js: 3.18.0 + har-schema@2.0.0: {} + + har-validator@5.1.5: + dependencies: + ajv: 6.12.6 + har-schema: 2.0.0 + hard-rejection@2.1.0: {} has-bigints@1.0.2: {} @@ -13569,6 +13800,12 @@ snapshots: statuses: 2.0.1 toidentifier: 1.0.1 + http-signature@1.2.0: + dependencies: + assert-plus: 1.0.0 + jsprim: 1.4.2 + sshpk: 1.18.0 + https-proxy-agent@4.0.0: dependencies: agent-base: 5.1.1 @@ -13659,6 +13896,8 @@ snapshots: dependencies: loose-envify: 1.4.0 + iota-array@1.0.0: {} + ip@2.0.1: {} ipaddr.js@1.9.1: {} @@ -13690,6 +13929,8 @@ snapshots: call-bind: 1.0.7 has-tostringtag: 1.0.2 + is-buffer@1.1.6: {} + is-callable@1.2.7: {} is-core-module@2.13.1: @@ -13784,6 +14025,8 @@ snapshots: dependencies: which-typed-array: 1.1.15 + is-typedarray@1.0.0: {} + is-unicode-supported@0.1.0: {} is-weakref@1.0.2: @@ -13810,6 +14053,8 @@ snapshots: isomorphic.js@0.2.5: {} + isstream@0.1.2: {} + istanbul-lib-coverage@3.2.2: {} istanbul-lib-instrument@5.2.1: @@ -14180,6 +14425,8 @@ snapshots: joycon@3.1.1: {} + jpeg-js@0.4.4: {} + js-base64@3.7.7: {} js-cookie@2.2.1: {} @@ -14197,6 +14444,8 @@ snapshots: dependencies: argparse: 2.0.1 + jsbn@0.1.1: {} + jscodeshift@0.15.2(@babel/preset-env@7.24.7(@babel/core@7.24.7)): dependencies: '@babel/core': 7.24.7 @@ -14238,6 +14487,8 @@ snapshots: json-schema-traverse@1.0.0: {} + json-schema@0.4.0: {} + json-stable-stringify-without-jsonify@1.0.1: {} json-stringify-safe@5.0.1: {} @@ -14256,6 +14507,13 @@ snapshots: jsonparse@1.3.1: {} + jsprim@1.4.2: + dependencies: + assert-plus: 1.0.0 + extsprintf: 1.3.0 + json-schema: 0.4.0 + verror: 1.10.0 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -14661,6 +14919,16 @@ snapshots: natural-compare@1.4.0: {} + ndarray-pack@1.2.1: + dependencies: + cwise-compiler: 1.1.3 + ndarray: 1.0.19 + + ndarray@1.0.19: + dependencies: + iota-array: 1.0.0 + is-buffer: 1.1.6 + needle@3.3.1: dependencies: iconv-lite: 0.6.3 @@ -14671,6 +14939,8 @@ snapshots: neo-async@2.6.2: {} + node-bitmap@0.0.1: {} + node-dir@0.1.17: dependencies: minimatch: 3.1.2 @@ -14726,6 +14996,8 @@ snapshots: pathe: 1.1.2 ufo: 1.5.3 + oauth-sign@0.9.0: {} + object-assign@4.1.1: {} object-component@0.0.3: {} @@ -14769,6 +15041,8 @@ snapshots: ohash@1.1.3: {} + omggif@1.0.10: {} + on-finished@2.4.1: dependencies: ee-first: 1.1.1 @@ -14856,6 +15130,10 @@ snapshots: dependencies: callsites: 3.1.0 + parse-data-uri@0.2.0: + dependencies: + data-uri-to-buffer: 0.0.3 + parse-json@4.0.0: dependencies: error-ex: 1.3.2 @@ -14915,6 +15193,8 @@ snapshots: pend@1.2.0: {} + performance-now@2.1.0: {} + picocolors@1.0.1: {} picomatch@2.3.1: {} @@ -14941,6 +15221,8 @@ snapshots: dependencies: find-up: 5.0.0 + pngjs@3.4.0: {} + polished@4.3.1: dependencies: '@babel/runtime': 7.24.7 @@ -15039,6 +15321,8 @@ snapshots: prr@1.0.1: optional: true + psl@1.9.0: {} + pump@2.0.1: dependencies: end-of-stream: 1.4.4 @@ -15086,6 +15370,8 @@ snapshots: dependencies: side-channel: 1.0.6 + qs@6.5.3: {} + queue-microtask@1.2.3: {} quick-lru@4.0.1: {} @@ -15525,6 +15811,29 @@ snapshots: mdast-util-to-string: 1.1.0 unist-util-visit: 2.0.3 + request@2.88.2: + dependencies: + aws-sign2: 0.7.0 + aws4: 1.13.0 + caseless: 0.12.0 + combined-stream: 1.0.8 + extend: 3.0.2 + forever-agent: 0.6.1 + form-data: 2.3.3 + har-validator: 5.1.5 + http-signature: 1.2.0 + is-typedarray: 1.0.0 + isstream: 0.1.2 + json-stringify-safe: 5.0.1 + mime-types: 2.1.35 + oauth-sign: 0.9.0 + performance-now: 2.1.0 + qs: 6.5.3 + safe-buffer: 5.2.1 + tough-cookie: 2.5.0 + tunnel-agent: 0.6.0 + uuid: 3.4.0 + require-directory@2.1.1: {} require-from-string@2.0.2: {} @@ -15831,6 +16140,18 @@ snapshots: sprintf-js@1.0.3: {} + sshpk@1.18.0: + dependencies: + asn1: 0.2.6 + assert-plus: 1.0.0 + bcrypt-pbkdf: 1.0.2 + dashdash: 1.14.1 + ecc-jsbn: 0.1.2 + getpass: 0.1.7 + jsbn: 0.1.1 + safer-buffer: 2.1.2 + tweetnacl: 0.14.5 + stack-generator@2.0.10: dependencies: stackframe: 1.3.4 @@ -16217,6 +16538,11 @@ snapshots: toidentifier@1.0.1: {} + tough-cookie@2.5.0: + dependencies: + psl: 1.9.0 + punycode: 2.3.1 + tr46@0.0.3: {} tr46@1.0.1: @@ -16307,6 +16633,12 @@ snapshots: tslib: 1.14.1 typescript: 4.9.5 + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + + tweetnacl@0.14.5: {} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -16418,6 +16750,8 @@ snapshots: unicode-property-aliases-ecmascript@2.1.0: {} + uniq@1.0.1: {} + unique-string@2.0.0: dependencies: crypto-random-string: 2.0.0 @@ -16512,6 +16846,8 @@ snapshots: utils-merge@1.0.1: {} + uuid@3.4.0: {} + uuid@8.3.2: {} uuid@9.0.1: {} @@ -16538,6 +16874,12 @@ snapshots: - '@types/react' - '@types/react-dom' + verror@1.10.0: + dependencies: + assert-plus: 1.0.0 + core-util-is: 1.0.2 + extsprintf: 1.3.0 + vite-tsconfig-paths@4.3.2(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1)): dependencies: debug: 4.3.5 diff --git a/src/global.d.ts b/src/global.d.ts index c968121df..a52b35ee9 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -18,3 +18,16 @@ declare module '*.module.css' { const classes: { [key: string]: string }; export default classes; } + +declare module 'colorthief' { + export type RGBColor = [number, number, number]; + export default class ColorThief { + getColor: (img: HTMLImageElement | null, quality: number = 10) => RGBColor | null; + + getPalette: ( + img: HTMLImageElement | null, + colorCount: number = 10, + quality: number = 10, + ) => RGBColor[] | null; + } +} diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index b9b8a21f4..e844358ce 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -1,10 +1,11 @@ -import React, { useState, useEffect, useCallback, useMemo } from 'react'; +import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; import { useIntl } from 'react-intl'; import Truncate from 'react-truncate-markup'; import { CustomRenderer, Tester, } from '~/v4/social/internal-components/StoryViewer/Renderers/types'; +import ColorThief from 'colorthief'; import { CommentTray } from '~/v4/social/components'; import { HyperLink } from '~/v4/social/elements/HyperLink'; import Footer from '~/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer'; @@ -23,7 +24,6 @@ import styles from './Renderers.module.css'; import clsx from 'clsx'; import { StoryProgressBar } from '~/v4/social/elements/StoryProgressBar/StoryProgressBar'; -import useCommunityStoriesSubscription from '~/v4/social/hooks/useCommunityStoriesSubscription'; export const renderer: CustomRenderer = ({ story, @@ -40,6 +40,8 @@ export const renderer: CustomRenderer = ({ const [isPaused, setIsPaused] = useState(false); const { loader } = config; const { client } = useSDK(); + const [backgroundGradient, setBackgroundGradient] = useState(''); + const imageRef = useRef<HTMLImageElement>(null); const isLiked = !!(story && story.myReactions && story.myReactions.includes(LIKE_REACTION_KEY)); const reactionsCount = story.reactionsCount || 0; @@ -55,7 +57,6 @@ export const renderer: CustomRenderer = ({ actions, addStoryButton, fileInputRef, - storyStyles, myReactions, currentIndex, storiesCount, @@ -95,13 +96,27 @@ export const renderer: CustomRenderer = ({ ); const targetRootId = 'asc-uikit-stories-viewer'; - const imageLoaded = () => { + const extractColors = useCallback(() => { + if (imageRef.current && imageRef.current.complete) { + const colorThief = new ColorThief(); + const palette = colorThief.getPalette(imageRef.current, 2); + if (palette) { + const gradient = `linear-gradient(to top, rgb(${palette[0].join( + ',', + )}), rgb(${palette[1].join(',')})`; + setBackgroundGradient(gradient); + } + } + }, []); + + const imageLoaded = useCallback(() => { setLoaded(true); if (isPaused) { setIsPaused(false); } action('play', true); - }; + extractColors(); + }, [action, isPaused, extractColors]); const play = () => setIsPaused(false); const pause = () => setIsPaused(true); @@ -144,6 +159,12 @@ export const renderer: CustomRenderer = ({ increaseIndex(); }; + useEffect(() => { + if (imageRef.current && imageRef.current.complete) { + extractColors(); + } + }, [extractColors]); + useEffect(() => { if (fileInputRef.current) { const handleClick = () => { @@ -177,6 +198,9 @@ export const renderer: CustomRenderer = ({ onDragStart={handleDragStart} onDragEnd={handleDragEnd} whileDrag={{ scale: 0.95, borderRadius: '8px', cursor: 'grabbing' }} + style={{ + background: backgroundGradient, + }} > <StoryProgressBar pageId={pageId} @@ -202,20 +226,15 @@ export const renderer: CustomRenderer = ({ addStoryButton={addStoryButton} /> - <div - className={clsx(styles.storyImageContainer)} - style={ - { - '--asc-story-image-background': storyStyles?.background, - } as React.CSSProperties - } - > + <div className={clsx(styles.storyImageContainer)}> <img + ref={imageRef} className={styles.storyImage} data-qa-anchor="image_view" src={story.url} onLoad={imageLoaded} alt="Story Image" + crossOrigin="anonymous" /> </div> diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index 70e4629d5..9b4eb922c 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -1,6 +1,4 @@ -import React, { useEffect, useState } from 'react'; -import { extractColors } from 'extract-colors'; -import { readFileAsync } from '~/helpers'; +import React, { useCallback, useEffect, useState } from 'react'; import { SubmitHandler } from 'react-hook-form'; import { AspectRatioButton, @@ -19,6 +17,7 @@ import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; import { useNavigation } from '~/v4/core/providers/NavigationProvider'; import { VideoPreview } from '~/v4/social/internal-components/VideoPreview'; import { useAmityPage } from '~/v4/core/hooks/uikit'; +import ColorThief from 'colorthief'; import styles from './DraftsPage.module.css'; @@ -83,7 +82,7 @@ export const PlainDraftStoryPage = ({ }; const [imageMode, setImageMode] = useState<'fit' | 'fill'>('fit'); - const [colors, setColors] = useState<Awaited<ReturnType<typeof extractColors>>>([]); + const [colors, setColors] = useState<string[]>([]); const onClickImageMode = () => { setImageMode(imageMode === 'fit' ? 'fill' : 'fit'); @@ -182,35 +181,34 @@ export const PlainDraftStoryPage = ({ setIsHyperLinkBottomSheetOpen(true); }; - useEffect(() => { - const extractColorsFromImage = async (fileTarget: File) => { - const img = await readFileAsync(fileTarget); - - if (fileTarget?.type.includes('image')) { - const image = new Image(); - image.src = img as string; + const extractColorsFromImage = useCallback(async (imageUrl: string) => { + const img = new Image(); + img.crossOrigin = 'Anonymous'; + img.src = imageUrl; - const colorsFromImage = await extractColors(image, { - crossOrigin: 'anonymous', - }); - - setColors(colorsFromImage); + img.onload = () => { + const colorThief = new ColorThief(); + const palette = colorThief.getPalette(img, 2); + if (palette) { + setColors(palette.map((color) => `rgb(${color.join(',')})`)); } }; + }, []); - if (mediaType?.type === 'image') { - if (file) { - extractColorsFromImage(file); - } else if (mediaType.url) { - fetch(mediaType.url) - .then((response) => response.blob()) - .then((blob) => { - const fileFromUrl = new File([blob], 'image', { type: 'image/jpeg' }); - extractColorsFromImage(fileFromUrl); - }); + useEffect(() => { + const extractColors = async () => { + if (mediaType?.type === 'image') { + if (file) { + const imageUrl = URL.createObjectURL(file); + await extractColorsFromImage(imageUrl); + } else if (mediaType.url) { + await extractColorsFromImage(mediaType.url); + } } - } - }, [file, imageMode, mediaType]); + }; + + extractColors(); + }, [file, mediaType, extractColorsFromImage]); return ( <div data-qa-anchor={accessibilityId} style={themeStyles} className={styles.storyWrapper}> @@ -232,10 +230,10 @@ export const PlainDraftStoryPage = ({ className={styles.mainContainer} style={{ background: `linear-gradient( - 180deg, - ${colors?.length > 0 ? colors[0].hex : 'var(--asc-color-black)'} 0%, - ${colors?.length > 0 ? colors[colors?.length - 1].hex : 'var(--asc-color-black)'} 100% - )`, + 180deg, + ${colors[0] || 'var(--asc-color-black)'} 0%, + ${colors[1] || 'var(--asc-color-black)'} 100% + )`, }} > <img @@ -247,6 +245,11 @@ export const PlainDraftStoryPage = ({ objectFit: imageMode === 'fit' ? 'contain' : 'cover', }} alt="Draft" + onLoad={(e) => { + if (imageMode === 'fill') { + extractColorsFromImage((e.target as HTMLImageElement).src); + } + }} /> </div> ) : mediaType?.type === 'video' ? ( diff --git a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx index 77bd35ef7..455071948 100644 --- a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx @@ -1,12 +1,11 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import useSDK from '~/core/hooks/useSDK'; -import { FinalColor } from 'extract-colors/lib/types/Color'; + import { StoryRepository } from '@amityco/ts-sdk'; import { CreateNewStoryButton } from '~/v4/social/elements/CreateNewStoryButton'; import { Trash2Icon } from '~/icons'; import { isNonNullable } from '~/v4/helpers/utils'; -import { extractColors } from 'extract-colors'; import Stories from 'react-insta-stories'; import { renderers } from '~/v4/social/internal-components/StoryViewer/Renderers'; @@ -75,24 +74,20 @@ export const CommunityFeedStory = ({ }, }); - const communityFeedRenderers = useMemo( - () => - renderers.map(({ renderer, tester }) => { - const newRenderer = (props: CustomRendererProps) => - renderer({ - ...props, - onClose: () => onClose(communityId), - onSwipeDown: () => onSwipeDown(communityId), - onClickCommunity: () => onClickCommunity(communityId), - }); + const communityFeedRenderers = renderers.map(({ renderer, tester }) => { + const newRenderer = (props: CustomRendererProps) => + renderer({ + ...props, + onClose: () => onClose(communityId), + onSwipeDown: () => onSwipeDown(communityId), + onClickCommunity: () => onClickCommunity(communityId), + }); - return { - renderer: newRenderer, - tester, - }; - }), - [renderers, onClose, onSwipeDown, onClickCommunity, communityId], - ); + return { + renderer: newRenderer, + tester, + }; + }); const fileInputRef = useRef<HTMLInputElement>(null); @@ -100,35 +95,40 @@ export const CommunityFeedStory = ({ const [currentIndex, setCurrentIndex] = useState(0); const { file, setFile } = useStoryContext(); - const [colors, setColors] = useState<FinalColor[]>([]); const isStoryCreator = stories[currentIndex]?.creator?.userId === currentUserId; const isModerator = checkStoryPermission(client, communityId); + const nextStory = () => { + if (currentIndex === stories.length - 1) { + onBack(); + return; + } + setCurrentIndex(currentIndex + 1); + }; + + const previousStory = () => { + if (currentIndex === 0) return; + setCurrentIndex(currentIndex - 1); + }; + const confirmDeleteStory = (storyId: string) => { - const isLastStory = currentIndex === stories.length - 1; confirm({ pageId, title: 'Delete this story?', content: - 'This story will be permanently deleted. You’ll no longer to see and find this story.', + "This story will be permanently deleted. You'll no longer to see and find this story.", okText: 'Delete', onOk: async () => { await StoryRepository.softDeleteStory(storyId); - notification.success({ - content: 'Story deleted', - }); - if (stories.length === 1) { - // If it's the only story, close the ViewStory screen + if (currentIndex === 0) { onClose(communityId); - } else if (isLastStory) { - // If it's the last story, move to the previous one - setCurrentIndex((prevIndex) => prevIndex - 1); } else { - // For any other case (including first story), stay on the same index - // The next story will automatically take its place - setCurrentIndex((prevIndex) => prevIndex); + setCurrentIndex(currentIndex - 1); } + notification.success({ + content: 'Story deleted', + }); }, }); }; @@ -200,89 +200,44 @@ export const CommunityFeedStory = ({ [pageId, setFile], ); - const storyStyles = useMemo( - () => ({ - width: '100%', - height: '100%', - objectFit: - stories[currentIndex]?.dataType === 'image' && - stories[currentIndex]?.data?.imageDisplayMode === 'fill' - ? 'cover' - : 'contain', - background: `linear-gradient( - 180deg, - ${colors?.length > 0 ? colors[0].hex : '#000'} 0%, - ${colors?.length > 0 ? colors[colors?.length - 1].hex : '#000'} 100% - )`, - }), - [stories, currentIndex, colors], - ); - const increaseIndex = () => { setCurrentIndex((prevIndex) => prevIndex + 1); }; - const formattedStories = useMemo( - () => - stories?.map((story) => { - const isImage = story?.dataType === 'image'; - const url = isImage ? story?.imageData?.fileUrl : story?.videoData?.videoUrl?.['720p']; - - return { - ...story, - url, - type: isImage ? 'image' : 'video', - actions: [ - isStoryCreator || isModerator - ? { - name: 'delete', - action: () => deleteStory(story?.storyId as string), - icon: ( - <Trash2Icon - fill={getComputedStyle(document.documentElement).getPropertyValue( - '--asc-color-base-default', - )} - /> - ), - } - : null, - ].filter(isNonNullable), - onCreateStory, - discardStory, - addStoryButton, - fileInputRef, - storyStyles, - currentIndex, - storiesCount: stories?.length, - increaseIndex, - pageId, - }; - }), - [ - stories, - deleteStory, + const formattedStories = stories?.map((story) => { + const isImage = story?.dataType === 'image'; + const url = isImage ? story?.imageData?.fileUrl : story?.videoData?.videoUrl?.['720p']; + + return { + ...story, + url, + type: isImage ? 'image' : 'video', + actions: [ + isStoryCreator || isModerator + ? { + name: 'delete', + action: () => deleteStory(story?.storyId as string), + icon: ( + <Trash2Icon + fill={getComputedStyle(document.documentElement).getPropertyValue( + '--asc-color-base-default', + )} + /> + ), + } + : null, + ].filter(isNonNullable), onCreateStory, discardStory, addStoryButton, fileInputRef, - storyStyles, + // storyStyles, currentIndex, + storiesCount: stories?.length, increaseIndex, - ], - ); - - const nextStory = () => { - if (currentIndex === stories.length - 1) { - onBack(); - return; - } - setCurrentIndex(currentIndex + 1); - }; - - const previousStory = () => { - if (currentIndex === 0) return; - setCurrentIndex(currentIndex - 1); - }; + pageId, + }; + }); const targetRootId = 'asc-uikit-stories-viewer'; @@ -304,23 +259,6 @@ export const CommunityFeedStory = ({ } }, [stories]); - useEffect(() => { - if (!stories) return; - const extractColorsFromImage = async (url: string) => { - const colorsFromImage = await extractColors(url, { - crossOrigin: 'anonymous', - }); - - setColors(colorsFromImage); - }; - - if (file?.type.includes('image') || stories[currentIndex]?.dataType === 'image') { - extractColorsFromImage((stories[currentIndex]?.imageData?.fileUrl as string) ?? file); - } else { - setColors([]); - } - }, [stories, currentIndex]); - if (file) { goToDraftStoryPage({ targetId: communityId, @@ -332,6 +270,8 @@ export const CommunityFeedStory = ({ }); } + if (!stories || stories.length === 0) return null; + return ( <div className={clsx(styles.storyWrapper)} data-qa-anchor={accessibilityId}> <ArrowLeftButton onClick={previousStory} /> @@ -340,24 +280,23 @@ export const CommunityFeedStory = ({ <div className={clsx(styles.overlayLeft)} onClick={previousStory} /> <div className={clsx(styles.overlayRight)} onClick={nextStory} /> <div className={clsx(styles.viewStoryOverlay)} /> - {formattedStories?.length > 0 ? ( - // NOTE: Do not use isPaused prop, it will cause the first video story skipped - <Stories - progressWrapperStyles={{ - display: 'none', - }} - preventDefault - currentIndex={currentIndex} - stories={formattedStories} - renderers={communityFeedRenderers as RendererObject[]} - defaultInterval={DURATION} - onStoryStart={() => stories[currentIndex]?.analytics.markAsSeen()} - onStoryEnd={increaseIndex} - onNext={nextStory} - onPrevious={previousStory} - onAllStoriesEnd={nextStory} - /> - ) : null} + {/* NOTE: Do not use isPaused prop, it will cause the first video story skipped */} + <Stories + key={stories?.length} + progressWrapperStyles={{ + display: 'none', + }} + preventDefault + currentIndex={currentIndex} + stories={formattedStories} + renderers={communityFeedRenderers as RendererObject[]} + defaultInterval={DURATION} + onStoryStart={() => stories[currentIndex]?.analytics.markAsSeen()} + onStoryEnd={nextStory} + onNext={nextStory} + onPrevious={previousStory} + onAllStoriesEnd={nextStory} + /> </div> </div> <ArrowRightButton onClick={nextStory} /> diff --git a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx index 663b6663e..5250ad7c9 100644 --- a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx @@ -1,8 +1,6 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { FinalColor } from 'extract-colors/lib/types/Color'; import { StoryRepository } from '@amityco/ts-sdk'; import { isNonNullable } from '~/v4/helpers/utils'; -import { extractColors } from 'extract-colors'; import Stories from 'react-insta-stories'; import { renderers } from '~/v4/social/internal-components/StoryViewer/Renderers'; import { checkStoryPermission } from '~/utils'; @@ -62,7 +60,6 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ const { file, setFile } = useStoryContext(); const [currentIndex, setCurrentIndex] = useState(0); - const [colors, setColors] = useState<FinalColor[]>([]); const fileInputRef = useRef<HTMLInputElement>(null); @@ -198,24 +195,6 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ [pageId, setFile], ); - const storyStyles = useMemo( - () => ({ - width: '100%', - height: '100%', - objectFit: - stories[currentIndex]?.dataType === 'image' && - stories[currentIndex]?.data?.imageDisplayMode === 'fill' - ? 'cover' - : 'contain', - background: `linear-gradient( - 180deg, - ${colors?.length > 0 ? colors[0].hex : '#000'} 0%, - ${colors?.length > 0 ? colors[colors?.length - 1].hex : '#000'} 100% - )`, - }), - [stories, currentIndex, colors], - ); - const increaseIndex = () => { setCurrentIndex((prevIndex) => prevIndex + 1); }; @@ -235,13 +214,7 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ ? { name: 'delete', action: () => deleteStory(story?.storyId as string), - icon: ( - <TrashIcon - fill={getComputedStyle(document.documentElement).getPropertyValue( - '--asc-color-base-default', - )} - /> - ), + icon: <TrashIcon className={styles.deleteIcon} />, } : null, ].filter(isNonNullable), @@ -249,7 +222,6 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ discardStory, addStoryButton, fileInputRef, - storyStyles, currentIndex, storiesCount: stories?.length, increaseIndex, @@ -263,7 +235,6 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ discardStory, addStoryButton, fileInputRef, - storyStyles, currentIndex, increaseIndex, ], @@ -325,23 +296,6 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ } }, [stories]); - useEffect(() => { - if (!stories) return; - const extractColorsFromImage = async (url: string) => { - const colorsFromImage = await extractColors(url, { - crossOrigin: 'anonymous', - }); - - setColors(colorsFromImage); - }; - - if (stories[currentIndex]?.dataType === 'image') { - extractColorsFromImage(stories[currentIndex]?.imageData?.fileUrl as string); - } else { - setColors([]); - } - }, [stories, currentIndex]); - if (file) { goToDraftStoryPage({ targetId, @@ -353,6 +307,8 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ }); } + if (!stories || stories.length === 0) return null; + return ( <div className={clsx(styles.storyWrapper)} data-qa-anchor={accessibilityId}> <ArrowLeftButton onClick={previousStory} /> @@ -361,25 +317,23 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ <div className={clsx(styles.overlayLeft)} onClick={previousStory} /> <div className={clsx(styles.overlayRight)} onClick={nextStory} /> <div className={clsx(styles.viewStoryOverlay)} /> - {formattedStories?.length > 0 ? ( - // NOTE: Do not use isPaused prop, it will cause the first video story skipped - <Stories - // hide default progress bar - progressWrapperStyles={{ - display: 'none', - }} - preventDefault - currentIndex={currentIndex} - stories={formattedStories} - renderers={globalFeedRenderers as RendererObject[]} - defaultInterval={DURATION} - onStoryStart={() => stories[currentIndex]?.analytics.markAsSeen()} - onStoryEnd={increaseIndex} - onNext={nextStory} - onPrevious={previousStory} - onAllStoriesEnd={nextStory} - /> - ) : null} + {/* NOTE: Do not use isPaused prop, it will cause the first video story skipped */} + <Stories + // hide default progress bar + progressWrapperStyles={{ + display: 'none', + }} + preventDefault + currentIndex={currentIndex} + stories={formattedStories} + renderers={globalFeedRenderers as RendererObject[]} + defaultInterval={DURATION} + onStoryStart={() => stories[currentIndex]?.analytics.markAsSeen()} + onStoryEnd={increaseIndex} + onNext={nextStory} + onPrevious={previousStory} + onAllStoriesEnd={nextStory} + /> </div> </div> <ArrowRightButton onClick={nextStory} /> diff --git a/src/v4/social/pages/StoryPage/StoryPage.module.css b/src/v4/social/pages/StoryPage/StoryPage.module.css index f412db33c..58fcf19cd 100644 --- a/src/v4/social/pages/StoryPage/StoryPage.module.css +++ b/src/v4/social/pages/StoryPage/StoryPage.module.css @@ -56,3 +56,7 @@ gap: 1rem; overflow: hidden; } + +.deleteIcon { + fill: var(--asc-color-base-default); +} From ee68c13e36f6c6b66852470870043dedea65b3f4 Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Mon, 1 Jul 2024 11:24:42 +0700 Subject: [PATCH 180/300] fix: member query (#466) --- .../pages/SelectPostTargetPage/SelectPostTargetPage.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.tsx b/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.tsx index 8c6d8aa62..17ecd0fae 100644 --- a/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.tsx +++ b/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.tsx @@ -24,11 +24,9 @@ export function SelectPostTargetPage() { }); const { onBack } = useNavigation(); const { communities, hasMore, loadMore, isLoading } = useCommunitiesCollection({ - sortBy: 'displayName', - limit: 20, + queryParams: { sortBy: 'displayName', limit: 20, membership: 'member' }, }); const { AmityPostTargetSelectionPage } = usePageBehavior(); - const intersectionRef = useRef<HTMLDivElement>(null); const { currentUserId } = useSDK(); const { user } = useUser(currentUserId); From 7be6be45b0843aca8a14f5bf1730e51b3e8e1481 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Mon, 1 Jul 2024 13:14:47 +0700 Subject: [PATCH 181/300] fix: moderator badge logic (#464) --- src/v4/core/hooks/usePostPermissions.ts | 17 ++++++++-- src/v4/core/hooks/usePostedUserInformation.ts | 34 +++++++++++++++++++ .../components/PostContent/PostContent.tsx | 6 ++-- .../useCommunityModeratorsCollection.ts | 10 ++++-- src/v4/social/hooks/useCommunityPermission.ts | 2 +- 5 files changed, 60 insertions(+), 9 deletions(-) create mode 100644 src/v4/core/hooks/usePostedUserInformation.ts diff --git a/src/v4/core/hooks/usePostPermissions.ts b/src/v4/core/hooks/usePostPermissions.ts index 3b7c4ce30..81b18e9f8 100644 --- a/src/v4/core/hooks/usePostPermissions.ts +++ b/src/v4/core/hooks/usePostPermissions.ts @@ -1,5 +1,6 @@ import { useMemo } from 'react'; import useSDK from '~/v4/core/hooks/useSDK'; +import useCommunityModeratorsCollection from '~/v4/social/hooks/collections/useCommunityModeratorsCollection'; export const usePostPermissions = ({ post, @@ -10,12 +11,22 @@ export const usePostPermissions = ({ }) => { const { currentUserId } = useSDK(); + const isCommunityPost = useMemo( + () => post.targetType === 'community' && post.targetId === community?.communityId, + [post.targetType, community?.communityId], + ); + + const { moderators } = useCommunityModeratorsCollection({ + communityId: community?.communityId, + shouldCall: isCommunityPost, + }); + const isCommunityModerator = useMemo(() => { - if (post && community) { - return post.targetType === 'community' && post.targetId === community.communityId; + if (isCommunityPost) { + return moderators.some((moderator) => moderator.userId === currentUserId); } return false; - }, []); + }, [moderators, isCommunityPost, currentUserId]); const isOwner = post.postedUserId === currentUserId; diff --git a/src/v4/core/hooks/usePostedUserInformation.ts b/src/v4/core/hooks/usePostedUserInformation.ts new file mode 100644 index 000000000..1aab50495 --- /dev/null +++ b/src/v4/core/hooks/usePostedUserInformation.ts @@ -0,0 +1,34 @@ +import { useMemo } from 'react'; +import useCommunityModeratorsCollection from '~/v4/social/hooks/collections/useCommunityModeratorsCollection'; + +export const usePostedUserInformation = ({ + post, + community, +}: { + post: Amity.Post; + community?: Amity.Community | null; +}) => { + const isCommunityPost = useMemo( + () => post.targetType === 'community' && post.targetId === community?.communityId, + [post.targetType, community?.communityId], + ); + + const { moderators } = useCommunityModeratorsCollection({ + communityId: community?.communityId, + shouldCall: isCommunityPost, + }); + + const isCommunityModerator = useMemo(() => { + if (isCommunityPost) { + return moderators.some((moderator) => moderator.userId === post?.postedUserId); + } + return false; + }, [moderators, isCommunityPost, post?.postedUserId]); + + const isOwner = post.postedUserId === post?.postedUserId; + + return { + isCommunityModerator, + isOwner, + }; +}; diff --git a/src/v4/social/components/PostContent/PostContent.tsx b/src/v4/social/components/PostContent/PostContent.tsx index 9a6db0c51..cc149eca4 100644 --- a/src/v4/social/components/PostContent/PostContent.tsx +++ b/src/v4/social/components/PostContent/PostContent.tsx @@ -29,10 +29,10 @@ import { useAmityComponent } from '~/v4/core/hooks/uikit'; import { ImageViewer } from '~/v4/social/internal-components/ImageViewer/ImageViewer'; import { VideoViewer } from '~/v4/social/internal-components/VideoViewer/VideoViewer'; import usePost from '~/v4/core/hooks/objects/usePost'; -import { usePostPermissions } from '~/v4/core/hooks/usePostPermissions'; import { PostMenu } from '~/v4/social/internal-components/PostMenu/PostMenu'; import usePostSubscription from '~/v4/core/hooks/subscriptions/usePostSubscription'; import { ReactionList } from '../index'; +import { usePostedUserInformation } from '~/v4/core/hooks/usePostedUserInformation'; interface PostTitleProps { post: Amity.Post; @@ -186,7 +186,7 @@ export const PostContent = ({ shouldCall, }); - const { isCommunityModerator } = usePostPermissions({ + const { isCommunityModerator } = usePostedUserInformation({ post, community: targetCommunity, }); @@ -277,7 +277,7 @@ export const PostContent = ({ <PostTitle post={post} /> </div> <div className={styles.postContent__bar__information__subtitle}> - {!isCommunityModerator ? ( + {isCommunityModerator ? ( <div className={styles.postContent__bar__information__subtitle__moderator}> <ModeratorBadge pageId={pageId} componentId={componentId} /> <span className={styles.postContent__bar__information__subtitle__separator}>•</span> diff --git a/src/v4/social/hooks/collections/useCommunityModeratorsCollection.ts b/src/v4/social/hooks/collections/useCommunityModeratorsCollection.ts index 416fe4f5f..dcf4001b5 100644 --- a/src/v4/social/hooks/collections/useCommunityModeratorsCollection.ts +++ b/src/v4/social/hooks/collections/useCommunityModeratorsCollection.ts @@ -4,11 +4,17 @@ import { MemberRoles } from '~/v4/social/constants/memberRoles'; const { COMMUNITY_MODERATOR } = MemberRoles; -export default function useCommunityModeratorsCollection(communityId?: string) { +export default function useCommunityModeratorsCollection({ + communityId, + shouldCall = true, +}: { + communityId?: string; + shouldCall?: boolean; +}) { const { items, ...rest } = useLiveCollection({ fetcher: CommunityRepository.Membership.getMembers, params: { communityId: communityId as string, roles: [COMMUNITY_MODERATOR] }, - shouldCall: !!communityId, + shouldCall: !!communityId && shouldCall, }); return { diff --git a/src/v4/social/hooks/useCommunityPermission.ts b/src/v4/social/hooks/useCommunityPermission.ts index 722357aad..42222414a 100644 --- a/src/v4/social/hooks/useCommunityPermission.ts +++ b/src/v4/social/hooks/useCommunityPermission.ts @@ -3,7 +3,7 @@ import useCommunityModeratorsCollection from '~/v4/social/hooks/collections/useC const useCommunityPermission = ({ community }: { community?: Amity.Community | null }) => { const { currentUserId, userRoles } = useSDK(); - const { moderators } = useCommunityModeratorsCollection(community?.communityId); + const { moderators } = useCommunityModeratorsCollection({ communityId: community?.communityId }); const moderator = moderators.find((moderator) => moderator.userId === currentUserId); From 0881bcb5b4fc07debad496cbbb78e363f4e1915f Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Mon, 1 Jul 2024 17:11:39 +0700 Subject: [PATCH 182/300] fix: ASC-00000 - tanstack query cache key (#465) * fix: tanstack query cache key * fix: image viewer, video viewer and image content --- .../ImageContent/ImageContent.module.css | 2 +- .../PostContent/LinkPreview/LinkPreview.tsx | 2 +- .../ImageViewer/ImageViewer.tsx | 4 +- .../VideoViewer/VideoViewer.tsx | 55 ++++++++++++++----- 4 files changed, 46 insertions(+), 17 deletions(-) diff --git a/src/v4/social/components/PostContent/ImageContent/ImageContent.module.css b/src/v4/social/components/PostContent/ImageContent/ImageContent.module.css index 89e1890a5..384bff5ae 100644 --- a/src/v4/social/components/PostContent/ImageContent/ImageContent.module.css +++ b/src/v4/social/components/PostContent/ImageContent/ImageContent.module.css @@ -29,7 +29,7 @@ .imageContent[data-images-amount='4'] { aspect-ratio: 1; - grid-template-areas: + grid-template: 'image1 image1 image1 image1 image1 image1' 66% 'image2 image2 image3 image3 image4 image4' 33% / minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr); diff --git a/src/v4/social/components/PostContent/LinkPreview/LinkPreview.tsx b/src/v4/social/components/PostContent/LinkPreview/LinkPreview.tsx index bf921f5d8..ff2ecbaef 100644 --- a/src/v4/social/components/PostContent/LinkPreview/LinkPreview.tsx +++ b/src/v4/social/components/PostContent/LinkPreview/LinkPreview.tsx @@ -31,7 +31,7 @@ const usePreviewLink = ({ url }: { url: string }) => { return useQuery({ enabled: !!client, - queryKey: ['asc-uikit', 'previewLink'], + queryKey: ['asc-uikit', 'previewLink', url], queryFn: async () => { const data = await client?.http.get<{ title: string; diff --git a/src/v4/social/internal-components/ImageViewer/ImageViewer.tsx b/src/v4/social/internal-components/ImageViewer/ImageViewer.tsx index 942b68ef0..d26bfcd21 100644 --- a/src/v4/social/internal-components/ImageViewer/ImageViewer.tsx +++ b/src/v4/social/internal-components/ImageViewer/ImageViewer.tsx @@ -3,6 +3,7 @@ import useImage from '~/core/hooks/useImage'; import usePostByIds from '~/social/hooks/usePostByIds'; import { useAmityElement } from '~/v4/core/hooks/uikit'; import { ClearButton } from '../../elements/ClearButton/ClearButton'; + import styles from './ImageViewer.module.css'; const AngleRight = (props: React.SVGProps<SVGSVGElement>) => ( @@ -81,12 +82,13 @@ export function ImageViewer({ </div> )} </div> - <span className={styles.closeButton} onClick={onClose}> + <span className={styles.closeButton}> <ClearButton pageId={pageId} componentId={componentId} defaultClassName={styles.imageViewer__clearButton} imgClassName={styles.imageViewer__clearButton__img} + onPress={onClose} /> </span> </div> diff --git a/src/v4/social/internal-components/VideoViewer/VideoViewer.tsx b/src/v4/social/internal-components/VideoViewer/VideoViewer.tsx index b61fccdd0..68bce7c93 100644 --- a/src/v4/social/internal-components/VideoViewer/VideoViewer.tsx +++ b/src/v4/social/internal-components/VideoViewer/VideoViewer.tsx @@ -1,4 +1,4 @@ -import React, { memo, useMemo, useState } from 'react'; +import React, { memo, useEffect, useMemo, useRef, useState } from 'react'; import useFile from '~/core/hooks/useFile'; import { VideoFileStatus } from '~/social/constants'; import usePostByIds from '~/social/hooks/usePostByIds'; @@ -7,8 +7,23 @@ import AngleRight from '~/v4/icons/AngleRight'; import { ClearButton } from '~/v4/social/elements/ClearButton/ClearButton'; import styles from './VideoViewer.module.css'; -const VideoPlayer = memo(({ videoFileId }: { videoFileId: string }) => { +import { VideoSize } from '@amityco/ts-sdk'; + +const VideoPlayer = memo(({ videoPost }: { videoPost?: Amity.Post<'video'> }) => { + const videoFileId = useMemo(() => { + return ( + videoPost?.data.videoFileId.high || + videoPost?.data.videoFileId.medium || + videoPost?.data.videoFileId.low || + videoPost?.data.videoFileId.original || + undefined + ); + }, [videoPost]); + const file: Amity.File<'video'> | undefined = useFile<Amity.File<'video'>>(videoFileId); + const posterUrl = useFile(videoPost?.data.thumbnailFileId); + + const videoRef = useRef<HTMLVideoElement>(null); /* * It's possible that certain video formats uploaded by the user are not @@ -35,10 +50,29 @@ const VideoPlayer = memo(({ videoFileId }: { videoFileId: string }) => { return file.fileUrl; }, [file]); + /* + The video initially doesn't change because in essence you're only modifying the <source> element + and React understands that <video> should remain unchanged, + so it does not update it on the DOM and doesn't trigger a new load event for that source. + A new load event should be triggered for <video>. + + ref: https://stackoverflow.com/a/47382850 + */ + useEffect(() => { + videoRef.current?.load(); + }, [url]); + if (url == null) return <></>; return ( - <video controls controlsList="nodownload" autoPlay className={styles.fullImage}> + <video + controls + controlsList="nodownload" + autoPlay + className={styles.fullImage} + ref={videoRef} + poster={posterUrl?.fileUrl} + > <source src={url} type="video/mp4" /> <p> Your browser does not support this format of video. Please try again later once the server @@ -73,15 +107,7 @@ export function VideoViewer({ const videoPosts = posts.filter((post) => post.dataType === 'video'); - const videoFileId = useMemo(() => { - return ( - videoPosts?.[selectedVideoIndex]?.data.videoFileId.high || - videoPosts?.[selectedVideoIndex]?.data.videoFileId.medium || - videoPosts?.[selectedVideoIndex]?.data.videoFileId.low || - videoPosts?.[selectedVideoIndex]?.data.videoFileId.original || - undefined - ); - }, [videoPosts, selectedVideoIndex]); + const videoPost = videoPosts[selectedVideoIndex]; const hasNext = selectedVideoIndex < videoPosts.length - 1; const hasPrev = selectedVideoIndex > 0; @@ -104,7 +130,7 @@ export function VideoViewer({ <div style={themeStyles}> <div className={styles.modal} onClick={onClose}> <div className={styles.modalContent} onClick={(e) => e.stopPropagation()}> - <VideoPlayer videoFileId={videoFileId} /> + <VideoPlayer videoPost={videoPost} /> <div className={styles.overlayPanel}> {hasPrev && ( <div className={styles.overlayPanel__prev} onClick={prev}> @@ -118,12 +144,13 @@ export function VideoViewer({ </div> )} </div> - <span className={styles.closeButton} onClick={onClose}> + <span className={styles.closeButton}> <ClearButton pageId={pageId} componentId={componentId} defaultClassName={styles.videoViewer__clearButton} imgClassName={styles.videoViewer__clearButton__img} + onPress={onClose} /> </span> </div> From 9c64f84a6443a041149e5c5c9f4f0162b3e20e0d Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Mon, 1 Jul 2024 18:20:55 +0700 Subject: [PATCH 183/300] fix: remove timestamp interaction (#463) --- .../components/PostContent/PostContent.module.css | 11 ----------- src/v4/social/components/PostContent/PostContent.tsx | 4 +--- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/src/v4/social/components/PostContent/PostContent.module.css b/src/v4/social/components/PostContent/PostContent.module.css index fb4c285db..6a7d0ee07 100644 --- a/src/v4/social/components/PostContent/PostContent.module.css +++ b/src/v4/social/components/PostContent/PostContent.module.css @@ -34,17 +34,6 @@ color: var(--asc-color-base-shade2); } -.postContent__bar__information__subtitle__timestamp { - cursor: pointer; -} - -.postContent__bar__information__subtitle__timestamp:hover { - text-decoration: underline; - text-underline-offset: 0.125rem; - text-decoration-thickness: 0.0625rem; - text-decoration-color: var(--asc-color-base-shade2); -} - .postContent__bar__actionButton { justify-self: flex-end; cursor: pointer; diff --git a/src/v4/social/components/PostContent/PostContent.tsx b/src/v4/social/components/PostContent/PostContent.tsx index cc149eca4..0f4fdd4f6 100644 --- a/src/v4/social/components/PostContent/PostContent.tsx +++ b/src/v4/social/components/PostContent/PostContent.tsx @@ -283,9 +283,7 @@ export const PostContent = ({ <span className={styles.postContent__bar__information__subtitle__separator}>•</span> </div> ) : null} - <span className={styles.postContent__bar__information__subtitle__timestamp}> - <Timestamp timestamp={post.createdAt} /> - </span> + <Timestamp timestamp={post.createdAt} /> </div> </div> <div className={styles.postContent__bar__actionButton}> From d6bb2a8100d684d8280036af19f7daff883f51ba Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Tue, 2 Jul 2024 10:29:26 +0700 Subject: [PATCH 184/300] fix: ASC-21809 - upload story video in android device (#467) * fix: upload story video in android device * fix: prop type * fix: remove console.log * fix: play pause function --- src/social/pages/Application/index.tsx | 1 - src/social/pages/DraftPage.tsx | 5 +- src/v4/core/providers/NavigationProvider.tsx | 32 +-- .../core/providers/PageBehaviorProvider.tsx | 7 +- .../StoryViewer/Renderers/Image.tsx | 32 ++- .../StoryViewer/Renderers/Video.tsx | 31 ++- src/v4/social/pages/Application/index.tsx | 8 + src/v4/social/pages/DraftsPage/DraftsPage.tsx | 10 +- .../pages/StoryPage/GlobalFeedStory.tsx | 188 ++++++++---------- .../social/pages/StoryPage/ViewStoryPage.tsx | 3 +- 10 files changed, 172 insertions(+), 145 deletions(-) diff --git a/src/social/pages/Application/index.tsx b/src/social/pages/Application/index.tsx index 699e7b8f8..3eb8f88b9 100644 --- a/src/social/pages/Application/index.tsx +++ b/src/social/pages/Application/index.tsx @@ -100,7 +100,6 @@ const Community = () => { mediaType={page.mediaType} targetId={page.targetId} targetType={page.targetType} - storyType={page.storyType} /> )} diff --git a/src/social/pages/DraftPage.tsx b/src/social/pages/DraftPage.tsx index 6fc187717..f83ad69d5 100644 --- a/src/social/pages/DraftPage.tsx +++ b/src/social/pages/DraftPage.tsx @@ -7,13 +7,13 @@ import { PageTypes } from '~/social/constants'; import { useNavigation } from '~/social/providers/NavigationProvider'; export const AmityDraftStoryPage = (props: AmityDraftStoryPageProps) => { - const { onChangePage, onClickCommunity } = useNavigation(); + const { page, onChangePage, onClickCommunity } = useNavigation(); return ( <PlainDraftStoryPage {...props} onDiscardCreateStory={() => { - if (props.storyType === 'communityFeed') { + if (page.type === PageTypes.DraftPage && page.storyType === 'communityFeed') { onClickCommunity(props.targetId); } else { onChangePage(PageTypes.NewsFeed); @@ -21,7 +21,6 @@ export const AmityDraftStoryPage = (props: AmityDraftStoryPageProps) => { }} goToCommunityPage={(communityId) => onClickCommunity(communityId)} goToGlobalFeedPage={() => onChangePage(PageTypes.NewsFeed)} - storyType={props.storyType} /> ); }; diff --git a/src/v4/core/providers/NavigationProvider.tsx b/src/v4/core/providers/NavigationProvider.tsx index dc31674b7..114a2d0d8 100644 --- a/src/v4/core/providers/NavigationProvider.tsx +++ b/src/v4/core/providers/NavigationProvider.tsx @@ -78,10 +78,13 @@ type Page = | { type: PageTypes.SelectPostTargetPage } | { type: PageTypes.DraftPage; - communityId?: string; - mediaType: AmityStoryMediaType; - targetId: string; - targetType: Amity.StoryTargetType; + context: { + communityId?: string; + mediaType: AmityStoryMediaType; + targetId: string; + targetType: Amity.StoryTargetType; + storyType: 'communityFeed' | 'globalFeed'; + }; } | { type: PageTypes.PostComposerPage; @@ -164,6 +167,7 @@ let defaultValue: ContextValue = { targetId: string; targetType: string; mediaType: AmityStoryMediaType; + storyType: 'communityFeed' | 'globalFeed'; }) => {}, goToCommunityProfilePage: (communityId: string) => {}, goToSocialGlobalSearchPage: (tab?: string) => {}, @@ -207,8 +211,10 @@ if (process.env.NODE_ENV !== 'production') { goToSocialGlobalSearchPage: (tab) => console.log(`NavigationContext goToSocialGlobalSearchPage(${tab})`), goToSelectPostTargetPage: () => console.log('NavigationContext goToTargetPage()'), - goToDraftStoryPage: ({ targetId, targetType, mediaType }) => - console.log(`NavigationContext goToDraftStoryPage(${targetId}, ${targetType}, ${mediaType})`), + goToDraftStoryPage: ({ targetId, targetType, mediaType, storyType }) => + console.log( + `NavigationContext goToDraftStoryPage(${targetId}, ${targetType}, ${mediaType}), ${storyType})`, + ), goToPostComposerPage: (mode, targetId, targetType, community, post) => console.log( `NavigationContext goToPostComposerPage(${mode} ${targetId}) ${targetType} ${community} ${post}`, @@ -245,6 +251,7 @@ interface NavigationProviderProps { targetId: string; targetType: string; mediaType: AmityStoryMediaType; + storyType: 'communityFeed' | 'globalFeed'; }) => void; onCommunityCreated?: (communityId: string) => void; onEditCommunity?: (communityId: string, options?: { tab?: string }) => void; @@ -493,16 +500,17 @@ export default function NavigationProvider({ }, [onChangePage, pushPage]); const goToDraftStoryPage = useCallback( - ({ targetId, targetType, mediaType }) => { + ({ targetId, targetType, mediaType, storyType }) => { const next = { type: PageTypes.DraftPage, - targetId, - targetType, - mediaType, + context: { + targetId, + targetType, + mediaType, + storyType, + }, }; - if (onChangePage) return onChangePage(next); - pushPage(next); }, [onChangePage, pushPage], diff --git a/src/v4/core/providers/PageBehaviorProvider.tsx b/src/v4/core/providers/PageBehaviorProvider.tsx index c1728b401..367985b3f 100644 --- a/src/v4/core/providers/PageBehaviorProvider.tsx +++ b/src/v4/core/providers/PageBehaviorProvider.tsx @@ -59,6 +59,7 @@ export const PageBehaviorProvider: React.FC<PageBehaviorProviderProps> = ({ pageBehavior = {}, }) => { const { + page, onBack, goToPostDetailPage, goToCommunityProfilePage, @@ -88,7 +89,11 @@ export const PageBehaviorProvider: React.FC<PageBehaviorProviderProps> = ({ if (pageBehavior?.AmityDraftStoryPageBehavior?.onCloseAction) { return pageBehavior.AmityDraftStoryPageBehavior.onCloseAction(); } - onBack(); + if (page.type === PageTypes.DraftPage && page.context.storyType === 'communityFeed') { + goToCommunityProfilePage(page.context.targetId); + } else { + goToSocialHomePage(); + } }, }, onClickHyperLink: () => {}, diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index e844358ce..97339de8b 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -118,13 +118,31 @@ export const renderer: CustomRenderer = ({ extractColors(); }, [action, isPaused, extractColors]); - const play = () => setIsPaused(false); - const pause = () => setIsPaused(true); + const play = () => { + action('play', true); + setIsPaused(false); + }; + const pause = () => { + action('pause', true); + setIsPaused(true); + }; - const openBottomSheet = () => setIsOpenBottomSheet(true); - const closeBottomSheet = () => setIsOpenBottomSheet(false); - const openCommentSheet = () => setIsOpenCommentSheet(true); - const closeCommentSheet = () => setIsOpenCommentSheet(false); + const openBottomSheet = () => { + action('pause', true); + setIsOpenBottomSheet(true); + }; + const closeBottomSheet = () => { + action('play', true); + setIsOpenBottomSheet(false); + }; + const openCommentSheet = () => { + action('pause', true); + setIsOpenCommentSheet(true); + }; + const closeCommentSheet = () => { + action('play', true); + setIsOpenCommentSheet(false); + }; const handleSwipeDown = () => { controls @@ -163,7 +181,7 @@ export const renderer: CustomRenderer = ({ if (imageRef.current && imageRef.current.complete) { extractColors(); } - }, [extractColors]); + }, []); useEffect(() => { if (fileInputRef.current) { diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx index 31906edc6..0f0ae4ef3 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx @@ -118,14 +118,31 @@ export const renderer: CustomRenderer = ({ const mute = () => setMuted(true); const unmute = () => setMuted(false); - const play = () => setIsPaused(false); - const pause = () => setIsPaused(true); - - const openBottomSheet = () => setIsOpenBottomSheet(true); - const closeBottomSheet = () => setIsOpenBottomSheet(false); + const play = () => { + action('play', true); + setIsPaused(false); + }; + const pause = () => { + action('pause', true); + setIsPaused(true); + }; - const openCommentSheet = () => setIsOpenCommentSheet(true); - const closeCommentSheet = () => setIsOpenCommentSheet(false); + const openBottomSheet = () => { + action('pause', true); + setIsOpenBottomSheet(true); + }; + const closeBottomSheet = () => { + action('play', true); + setIsOpenBottomSheet(false); + }; + const openCommentSheet = () => { + action('pause', true); + setIsOpenCommentSheet(true); + }; + const closeCommentSheet = () => { + action('play', true); + setIsOpenCommentSheet(false); + }; const targetRootId = 'asc-uikit-stories-viewer'; diff --git a/src/v4/social/pages/Application/index.tsx b/src/v4/social/pages/Application/index.tsx index dae6732bc..124c1c230 100644 --- a/src/v4/social/pages/Application/index.tsx +++ b/src/v4/social/pages/Application/index.tsx @@ -11,6 +11,7 @@ import { SelectPostTargetPage } from '../SelectPostTargetPage'; import { MyCommunitiesSearchPage } from '../MyCommunitiesSearchPage/MyCommunitiesSearchPage'; import styles from './Application.module.css'; +import { AmityDraftStoryPage } from '..'; const Application = () => { const { page } = useNavigation(); @@ -24,6 +25,13 @@ const Application = () => { {page.type === PageTypes.ViewStoryPage && ( <ViewStoryPage type="globalFeed" targetId={page.context.targetId} /> )} + {page.type === PageTypes.DraftPage && ( + <AmityDraftStoryPage + targetId={page.context.targetId} + targetType={page.context.targetType} + mediaType={page.context.mediaType} + /> + )} {page.type === PageTypes.PostComposerPage && ( <PostComposerPage mode={page.context.mode} diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index 9b4eb922c..267ec0b19 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -14,7 +14,7 @@ import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; import { useNotifications } from '~/v4/core/providers/NotificationProvider'; import { useCommunityInfo } from '~/social/components/CommunityInfo/hooks'; import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; -import { useNavigation } from '~/v4/core/providers/NavigationProvider'; +import { PageTypes, useNavigation } from '~/v4/core/providers/NavigationProvider'; import { VideoPreview } from '~/v4/social/internal-components/VideoPreview'; import { useAmityPage } from '~/v4/core/hooks/uikit'; import ColorThief from 'colorthief'; @@ -27,7 +27,6 @@ export type AmityDraftStoryPageProps = { targetId: string; targetType: Amity.StoryTargetType; mediaType?: AmityStoryMediaType; - storyType: 'communityFeed' | 'globalFeed'; }; export type HyperLinkFormInputs = { @@ -42,13 +41,12 @@ export const PlainDraftStoryPage = ({ goToCommunityPage, goToGlobalFeedPage, onDiscardCreateStory, - storyType, }: AmityDraftStoryPageProps & { goToCommunityPage: (communityId: string) => void; goToGlobalFeedPage: () => void; onDiscardCreateStory: () => void; - storyType: 'communityFeed' | 'globalFeed'; }) => { + const { page } = useNavigation(); const pageId = 'create_story_page'; const { accessibilityId, themeStyles, isExcluded } = useAmityPage({ pageId, @@ -102,7 +100,7 @@ export const PlainDraftStoryPage = ({ const formData = new FormData(); formData.append('files', file); setFile(null); - if (storyType === 'globalFeed') { + if (page.type === PageTypes.DraftPage && page.context.storyType === 'globalFeed') { goToGlobalFeedPage(); } else { goToCommunityPage(targetId); @@ -294,7 +292,6 @@ export const PlainDraftStoryPage = ({ }; export const AmityDraftStoryPage = (props: AmityDraftStoryPageProps) => { - const { page } = useNavigation(); const { AmityDraftStoryPageBehavior } = usePageBehavior(); return ( @@ -303,7 +300,6 @@ export const AmityDraftStoryPage = (props: AmityDraftStoryPageProps) => { onDiscardCreateStory={() => AmityDraftStoryPageBehavior.onCloseAction()} goToCommunityPage={(communityId) => AmityDraftStoryPageBehavior.onCloseAction()} goToGlobalFeedPage={() => AmityDraftStoryPageBehavior.onCloseAction()} - storyType={page.type === 'communityFeed' ? 'communityFeed' : 'globalFeed'} /> ); }; diff --git a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx index 5250ad7c9..0835c5b8e 100644 --- a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { StoryRepository } from '@amityco/ts-sdk'; import { isNonNullable } from '~/v4/helpers/utils'; import Stories from 'react-insta-stories'; @@ -124,121 +124,102 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ confirmDeleteStory(storyId); }; - const onCreateStory = useCallback( - async ( - file: File, - imageMode: 'fit' | 'fill', - metadata?: Amity.Metadata, - items?: Amity.StoryItem[], - ) => { - try { - const formData = new FormData(); - formData.append('files', file); - setFile(null); - if (file?.type.includes('image') && currentUserId) { - const { data: imageData } = await StoryRepository.createImageStory( + const onCreateStory = async ( + file: File, + imageMode: 'fit' | 'fill', + metadata?: Amity.Metadata, + items?: Amity.StoryItem[], + ) => { + try { + const formData = new FormData(); + formData.append('files', file); + setFile(null); + if (file?.type.includes('image') && currentUserId) { + const { data: imageData } = await StoryRepository.createImageStory( + 'user', + currentUserId, + formData, + metadata, + imageMode, + items, + ); + if (imageData) { + notification.success({ + content: 'Successfully shared story', + }); + } + } else { + if (currentUserId) { + const { data: videoData } = await StoryRepository.createVideoStory( 'user', currentUserId, formData, metadata, - imageMode, items, ); - if (imageData) { + if (videoData) { notification.success({ content: 'Successfully shared story', }); } - } else { - if (currentUserId) { - const { data: videoData } = await StoryRepository.createVideoStory( - 'user', - currentUserId, - formData, - metadata, - items, - ); - if (videoData) { - notification.success({ - content: 'Successfully shared story', - }); - } - } - } - } catch (error: unknown) { - if (error instanceof Error) { - notification.info({ - content: error.message ?? 'Failed to share story', - }); } } - }, - [currentUserId, notification, setFile], - ); + } catch (error: unknown) { + if (error instanceof Error) { + notification.info({ + content: error.message ?? 'Failed to share story', + }); + } + } + }; const discardStory = () => { setFile(null); }; - const addStoryButton = useMemo( - () => ( - <FileTrigger - ref={fileInputRef} - onSelect={(e) => { - const files = Array.from(e as FileList); - setFile(files[0]); - }} - > - <CreateNewStoryButton pageId={pageId} /> - </FileTrigger> - ), - [pageId, setFile], + const addStoryButton = ( + <FileTrigger + ref={fileInputRef} + onSelect={(e) => { + const files = Array.from(e as FileList); + setFile(files[0]); + }} + > + <CreateNewStoryButton pageId={pageId} /> + </FileTrigger> ); const increaseIndex = () => { setCurrentIndex((prevIndex) => prevIndex + 1); }; - const formattedStories = useMemo( - () => - stories?.map((story) => { - const isImage = story?.dataType === 'image'; - const url = isImage ? story?.imageData?.fileUrl : story?.videoData?.videoUrl?.['720p']; + const formattedStories = stories?.map((story) => { + const isImage = story?.dataType === 'image'; + const url = isImage ? story?.imageData?.fileUrl : story?.videoData?.videoUrl?.['720p']; - return { - ...story, - url, - type: isImage ? 'image' : 'video', - actions: [ - isStoryCreator || isModerator - ? { - name: 'delete', - action: () => deleteStory(story?.storyId as string), - icon: <TrashIcon className={styles.deleteIcon} />, - } - : null, - ].filter(isNonNullable), - onCreateStory, - discardStory, - addStoryButton, - fileInputRef, - currentIndex, - storiesCount: stories?.length, - increaseIndex, - pageId, - }; - }), - [ - stories, - deleteStory, + return { + ...story, + url, + type: isImage ? 'image' : 'video', + actions: [ + isStoryCreator || isModerator + ? { + name: 'delete', + action: () => deleteStory(story?.storyId as string), + icon: <TrashIcon className={styles.deleteIcon} />, + } + : null, + ].filter(isNonNullable), onCreateStory, discardStory, addStoryButton, fileInputRef, currentIndex, + storiesCount: stories?.length, increaseIndex, - ], - ); + pageId, + }; + }); const nextStory = () => { if (currentIndex === formattedStories?.length - 1) { @@ -257,24 +238,20 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ setCurrentIndex((prevIndex) => prevIndex + 1); }; - const globalFeedRenderers = useMemo( - () => - renderers.map(({ renderer, tester }) => { - const newRenderer = (props: CustomRendererProps) => - renderer({ - ...props, - onClose: () => onClose(targetId), - onSwipeDown: () => onSwipeDown(targetId), - onClickCommunity: () => onClickCommunity(targetId), - }); + const globalFeedRenderers = renderers.map(({ renderer, tester }) => { + const newRenderer = (props: CustomRendererProps) => + renderer({ + ...props, + onClose: () => onClose(targetId), + onSwipeDown: () => onSwipeDown(targetId), + onClickCommunity: () => onClickCommunity(targetId), + }); - return { - renderer: newRenderer, - tester, - }; - }), - [renderers, onClose, onSwipeDown, onClickCommunity, targetId], - ); + return { + renderer: newRenderer, + tester, + }; + }); const targetRootId = 'asc-uikit-stories-viewer'; @@ -296,7 +273,8 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ } }, [stories]); - if (file) { + useEffect(() => { + if (!file) return; goToDraftStoryPage({ targetId, targetType: 'community', @@ -305,7 +283,7 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ : { type: 'video', url: URL.createObjectURL(file) }, storyType: 'globalFeed', }); - } + }, [file, goToDraftStoryPage, targetId]); if (!stories || stories.length === 0) return null; diff --git a/src/v4/social/pages/StoryPage/ViewStoryPage.tsx b/src/v4/social/pages/StoryPage/ViewStoryPage.tsx index 041f7a54e..3333b5ac9 100644 --- a/src/v4/social/pages/StoryPage/ViewStoryPage.tsx +++ b/src/v4/social/pages/StoryPage/ViewStoryPage.tsx @@ -2,8 +2,7 @@ import React from 'react'; import { CommunityFeedStory } from '~/v4/social/pages/StoryPage/CommunityFeedStory'; import { useNavigation } from '~/v4/core/providers/NavigationProvider'; import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; -import { ViewGlobalFeedStoryPage } from './ViewGlobalFeedStory'; -import { useAmityPage } from '~/v4/core/hooks/uikit/index'; +import { ViewGlobalFeedStoryPage } from '~/v4/social/pages/StoryPage/ViewGlobalFeedStory'; type ViewStoryPageType = 'communityFeed' | 'globalFeed'; From e68a72270f9d15aaf24eff267376f60368ae7590 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Tue, 2 Jul 2024 15:59:55 +0700 Subject: [PATCH 185/300] fix: ASC-23419 - like reaction in story reaction list show unknown reaction (#469) * fix: upload story video in android device * fix: prop type * fix: reaction list reactions * fix: rte * fix: console.log --- .../components/ReactionList/ReactionList.tsx | 3 - .../ReactionList/ReactionListPanel.tsx | 30 ++++------ .../pages/StoryPage/CommunityFeedStory.tsx | 60 ++++++++++--------- .../pages/StoryPage/GlobalFeedStory.tsx | 42 ++++++++----- 4 files changed, 72 insertions(+), 63 deletions(-) diff --git a/src/v4/social/components/ReactionList/ReactionList.tsx b/src/v4/social/components/ReactionList/ReactionList.tsx index 74dfb0eaf..7150539fb 100644 --- a/src/v4/social/components/ReactionList/ReactionList.tsx +++ b/src/v4/social/components/ReactionList/ReactionList.tsx @@ -43,7 +43,6 @@ const RenderCondition = ({ removeReaction, error, currentRef, - showReactionUserDetails = false, }: { filteredReactions: Amity.Reactor[]; isLoading: boolean; @@ -52,7 +51,6 @@ const RenderCondition = ({ removeReaction: (reaction: string) => Promise<void>; error: Error | null; currentRef: HTMLDivElement | null; - showReactionUserDetails?: boolean; }) => { if (isLoading) { return <ReactionListLoadingState />; @@ -78,7 +76,6 @@ const RenderCondition = ({ isLoading={isLoading} filteredReactions={filteredReactions} removeReaction={removeReaction} - showReactionUserDetails={showReactionUserDetails} /> ); }; diff --git a/src/v4/social/components/ReactionList/ReactionListPanel.tsx b/src/v4/social/components/ReactionList/ReactionListPanel.tsx index c40a85874..e53071b3c 100644 --- a/src/v4/social/components/ReactionList/ReactionListPanel.tsx +++ b/src/v4/social/components/ReactionList/ReactionListPanel.tsx @@ -16,7 +16,6 @@ export const ReactionListPanel = ({ loadMore, isLoading, currentRef, - showReactionUserDetails, }: { filteredReactions: Amity.Reactor[]; removeReaction: (reaction: string) => Promise<void>; @@ -24,7 +23,6 @@ export const ReactionListPanel = ({ loadMore: () => void; isLoading: boolean; currentRef: HTMLDivElement | null; - showReactionUserDetails?: boolean; }) => { const { currentUserId } = useSDK(); const { config } = useCustomReaction(); @@ -60,7 +58,7 @@ export const ReactionListPanel = ({ </div> <Typography.BodyBold data-qa-anchor="user_display_name"> {reaction.user?.displayName} - {currentUserId === reaction.user?.userId && showReactionUserDetails && ( + {currentUserId === reaction.user?.userId && ( <> <br /> <div onClick={() => removeReaction(reaction.reactionName)}> @@ -73,20 +71,18 @@ export const ReactionListPanel = ({ </Typography.BodyBold> </div> - {showReactionUserDetails && ( - <div className={styles.userDetailsReaction}> - {reactionList.includes(reaction.reactionName) ? ( - <ReactionIcon - reactionConfigItem={ - config.find(({ name }) => name === reaction.reactionName)! - } - className={styles.reactionIcon} - /> - ) : ( - <FallbackReaction className={styles.reactionIcon} /> - )} - </div> - )} + <div className={styles.userDetailsReaction}> + {reactionList.includes(reaction.reactionName) ? ( + <ReactionIcon + reactionConfigItem={ + config.find(({ name }) => name === reaction.reactionName)! + } + className={styles.reactionIcon} + /> + ) : ( + <FallbackReaction className={styles.reactionIcon} /> + )} + </div> </div> </div> </Fragment> diff --git a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx index 455071948..c133a620d 100644 --- a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx @@ -27,6 +27,7 @@ import { FileTrigger } from 'react-aria-components'; import styles from './StoryPage.module.css'; import { useGetActiveStoriesByTarget } from '~/v4/social/hooks/useGetActiveStories'; +import useCommunityStoriesSubscription from '~/v4/social/hooks/useCommunityStoriesSubscription'; interface CommunityFeedStoryProps { pageId?: string; @@ -74,20 +75,24 @@ export const CommunityFeedStory = ({ }, }); - const communityFeedRenderers = renderers.map(({ renderer, tester }) => { - const newRenderer = (props: CustomRendererProps) => - renderer({ - ...props, - onClose: () => onClose(communityId), - onSwipeDown: () => onSwipeDown(communityId), - onClickCommunity: () => onClickCommunity(communityId), - }); + const communityFeedRenderers = useMemo( + () => + renderers.map(({ renderer, tester }) => { + const newRenderer = (props: CustomRendererProps) => + renderer({ + ...props, + onClose: () => onClose(communityId), + onSwipeDown: () => onSwipeDown(communityId), + onClickCommunity: () => onClickCommunity(communityId), + }); - return { - renderer: newRenderer, - tester, - }; - }); + return { + renderer: newRenderer, + tester, + }; + }), + [renderers, onClose, onSwipeDown, onClickCommunity, communityId], + ); const fileInputRef = useRef<HTMLInputElement>(null); @@ -185,19 +190,16 @@ export const CommunityFeedStory = ({ const discardStory = () => { setFile(null); }; - const addStoryButton = useMemo( - () => ( - <FileTrigger - ref={fileInputRef} - onSelect={(e) => { - const files = Array.from(e as FileList); - setFile(files[0]); - }} - > - <CreateNewStoryButton pageId={pageId} /> - </FileTrigger> - ), - [pageId, setFile], + const addStoryButton = ( + <FileTrigger + ref={fileInputRef} + onSelect={(e) => { + const files = Array.from(e as FileList); + setFile(files[0]); + }} + > + <CreateNewStoryButton pageId={pageId} /> + </FileTrigger> ); const increaseIndex = () => { @@ -231,7 +233,6 @@ export const CommunityFeedStory = ({ discardStory, addStoryButton, fileInputRef, - // storyStyles, currentIndex, storiesCount: stories?.length, increaseIndex, @@ -259,6 +260,11 @@ export const CommunityFeedStory = ({ } }, [stories]); + useCommunityStoriesSubscription({ + targetId: communityId, + targetType: 'community', + }); + if (file) { goToDraftStoryPage({ targetId: communityId, diff --git a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx index 0835c5b8e..64bb4f8c6 100644 --- a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; import { StoryRepository } from '@amityco/ts-sdk'; import { isNonNullable } from '~/v4/helpers/utils'; import Stories from 'react-insta-stories'; @@ -20,6 +20,7 @@ import { TrashIcon } from '~/v4/social/icons'; import { CreateNewStoryButton } from '~/v4/social/elements/CreateNewStoryButton'; import { useAmityPage } from '~/v4/core/hooks/uikit'; import { FileTrigger } from 'react-aria-components'; +import useCommunityStoriesSubscription from '~/v4/social/hooks/useCommunityStoriesSubscription'; import styles from './StoryPage.module.css'; @@ -72,6 +73,25 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ }, }); + const globalFeedRenderers = useMemo( + () => + renderers.map(({ renderer, tester }) => { + const newRenderer = (props: CustomRendererProps) => + renderer({ + ...props, + onClose: () => onClose(targetId), + onSwipeDown: () => onSwipeDown(targetId), + onClickCommunity: () => onClickCommunity(targetId), + }); + + return { + renderer: newRenderer, + tester, + }; + }), + [renderers, onClose, onSwipeDown, onClickCommunity, targetId], + ); + const isStoryCreator = stories[currentIndex]?.creator?.userId === currentUserId; const isModerator = checkStoryPermission(client, stories[currentIndex]?.targetId); @@ -238,21 +258,6 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ setCurrentIndex((prevIndex) => prevIndex + 1); }; - const globalFeedRenderers = renderers.map(({ renderer, tester }) => { - const newRenderer = (props: CustomRendererProps) => - renderer({ - ...props, - onClose: () => onClose(targetId), - onSwipeDown: () => onSwipeDown(targetId), - onClickCommunity: () => onClickCommunity(targetId), - }); - - return { - renderer: newRenderer, - tester, - }; - }); - const targetRootId = 'asc-uikit-stories-viewer'; useEffect(() => { @@ -285,6 +290,11 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ }); }, [file, goToDraftStoryPage, targetId]); + useCommunityStoriesSubscription({ + targetId, + targetType: 'community', + }); + if (!stories || stories.length === 0) return null; return ( From e990805c82b01dda8065151063f5fd96cacd64d7 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Tue, 2 Jul 2024 16:05:16 +0700 Subject: [PATCH 186/300] fix: swipe down (#470) --- .../StoryViewer/Renderers/Image.tsx | 33 +++++++++++----- .../StoryViewer/Renderers/Video.tsx | 35 +++++++++++------ .../pages/StoryPage/CommunityFeedStory.tsx | 39 +++++++++++++++++-- .../pages/StoryPage/GlobalFeedStory.tsx | 35 ++++++++++++++++- 4 files changed, 116 insertions(+), 26 deletions(-) diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index 97339de8b..ca14d260c 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -62,6 +62,7 @@ export const renderer: CustomRenderer = ({ storiesCount, increaseIndex, pageId, + dragEventTarget, } = story; const { members } = useCommunityMembersCollection(community?.communityId as string); @@ -206,16 +207,30 @@ export const renderer: CustomRenderer = ({ } }, [fileInputRef]); + useEffect(() => { + if (dragEventTarget) { + const handleDragStart = () => { + action('pause', true); + setIsPaused(true); + }; + const handleDragEnd = () => { + action('play', true); + setIsPaused(false); + }; + + dragEventTarget.addEventListener('dragstart', handleDragStart); + dragEventTarget.addEventListener('dragend', handleDragEnd); + + return () => { + dragEventTarget.removeEventListener('dragstart', handleDragStart); + dragEventTarget.removeEventListener('dragend', handleDragEnd); + }; + } + }, [dragEventTarget]); + return ( - <motion.div + <div className={styles.rendererContainer} - animate={controls} - drag="y" - dragConstraints={{ top: 0, bottom: 0 }} - dragElastic={0.7} - onDragStart={handleDragStart} - onDragEnd={handleDragEnd} - whileDrag={{ scale: 0.95, borderRadius: '8px', cursor: 'grabbing' }} style={{ background: backgroundGradient, }} @@ -336,7 +351,7 @@ export const renderer: CustomRenderer = ({ } isMember={isMember} /> - </motion.div> + </div> ); }; diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx index 0f0ae4ef3..80203a7de 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx @@ -66,6 +66,7 @@ export const renderer: CustomRenderer = ({ storiesCount, increaseIndex, pageId, + dragEventTarget, } = story; const { members } = useCommunityMembersCollection(community?.communityId as string); @@ -210,17 +211,29 @@ export const renderer: CustomRenderer = ({ } }, []); + useEffect(() => { + if (dragEventTarget) { + const handleDragStart = () => { + action('pause', true); + setIsPaused(true); + }; + const handleDragEnd = () => { + action('play', true); + setIsPaused(false); + }; + + dragEventTarget.addEventListener('dragstart', handleDragStart); + dragEventTarget.addEventListener('dragend', handleDragEnd); + + return () => { + dragEventTarget.removeEventListener('dragstart', handleDragStart); + dragEventTarget.removeEventListener('dragend', handleDragEnd); + }; + } + }, [dragEventTarget]); + return ( - <motion.div - className={clsx(rendererStyles.rendererContainer)} - animate={controls} - drag="y" - dragConstraints={{ top: 0, bottom: 0 }} - dragElastic={0.7} - onDragStart={handleDragStart} - onDragEnd={handleDragEnd} - whileDrag={{ scale: 0.95, borderRadius: '8px', cursor: 'grabbing' }} - > + <div className={clsx(rendererStyles.rendererContainer)}> <StoryProgressBar pageId={pageId} duration={5000} @@ -341,7 +354,7 @@ export const renderer: CustomRenderer = ({ } isMember={isMember} /> - </motion.div> + </div> ); }; diff --git a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx index c133a620d..ac767459f 100644 --- a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx @@ -27,6 +27,7 @@ import { FileTrigger } from 'react-aria-components'; import styles from './StoryPage.module.css'; import { useGetActiveStoriesByTarget } from '~/v4/social/hooks/useGetActiveStories'; +import { useMotionValue, motion } from 'framer-motion'; import useCommunityStoriesSubscription from '~/v4/social/hooks/useCommunityStoriesSubscription'; interface CommunityFeedStoryProps { @@ -65,6 +66,9 @@ export const CommunityFeedStory = ({ }); const { confirm } = useConfirmContext(); const notification = useNotifications(); + const y = useMotionValue(0); + const motionRef = useRef<HTMLDivElement>(null); + const dragEventTarget = useRef(new EventTarget()); const { stories } = useGetActiveStoriesByTarget({ targetId: communityId, @@ -237,6 +241,7 @@ export const CommunityFeedStory = ({ storiesCount: stories?.length, increaseIndex, pageId, + dragEventTarget: dragEventTarget.current, }; }); @@ -279,14 +284,40 @@ export const CommunityFeedStory = ({ if (!stories || stories.length === 0) return null; return ( - <div className={clsx(styles.storyWrapper)} data-qa-anchor={accessibilityId}> + <div className={clsx(styles.storyWrapper)}> <ArrowLeftButton onClick={previousStory} /> - <div id={targetRootId} className={clsx(styles.viewStoryContainer)}> + <motion.div + id={targetRootId} + ref={motionRef} + data-qa-anchor={accessibilityId} + initial={{ y: 0 }} + drag="y" + whileDrag={{ scale: 0.95, borderRadius: '8px', cursor: 'grabbing' }} + dragConstraints={{ top: 0, bottom: 200 }} + dragElastic={{ top: 0, bottom: 0.5 }} + onDragStart={() => { + dragEventTarget.current.dispatchEvent(new Event('dragstart')); + }} + onDrag={(_, info) => { + // Prevent dragging upwards + if (info.point.y < info.point.y - info.offset.y) { + y.set(0); + } + }} + onDragEnd={(_, info) => { + dragEventTarget.current.dispatchEvent(new Event('dragend')); + if (info.offset.y > 100) { + onSwipeDown(communityId); + } else { + y.set(0); + } + }} + className={clsx(styles.viewStoryContainer)} + > <div className={clsx(styles.viewStoryContent)}> <div className={clsx(styles.overlayLeft)} onClick={previousStory} /> <div className={clsx(styles.overlayRight)} onClick={nextStory} /> <div className={clsx(styles.viewStoryOverlay)} /> - {/* NOTE: Do not use isPaused prop, it will cause the first video story skipped */} <Stories key={stories?.length} progressWrapperStyles={{ @@ -304,7 +335,7 @@ export const CommunityFeedStory = ({ onAllStoriesEnd={nextStory} /> </div> - </div> + </motion.div> <ArrowRightButton onClick={nextStory} /> </div> ); diff --git a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx index 64bb4f8c6..09c7d1e20 100644 --- a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx @@ -20,6 +20,7 @@ import { TrashIcon } from '~/v4/social/icons'; import { CreateNewStoryButton } from '~/v4/social/elements/CreateNewStoryButton'; import { useAmityPage } from '~/v4/core/hooks/uikit'; import { FileTrigger } from 'react-aria-components'; +import { useMotionValue, motion } from 'framer-motion'; import useCommunityStoriesSubscription from '~/v4/social/hooks/useCommunityStoriesSubscription'; import styles from './StoryPage.module.css'; @@ -59,6 +60,9 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ const notification = useNotifications(); const { client, currentUserId } = useSDK(); const { file, setFile } = useStoryContext(); + const y = useMotionValue(0); + const motionRef = useRef<HTMLDivElement>(null); + const dragEventTarget = useRef(new EventTarget()); const [currentIndex, setCurrentIndex] = useState(0); @@ -300,7 +304,34 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ return ( <div className={clsx(styles.storyWrapper)} data-qa-anchor={accessibilityId}> <ArrowLeftButton onClick={previousStory} /> - <div id={targetRootId} className={clsx(styles.viewStoryContainer)}> + <motion.div + id={targetRootId} + ref={motionRef} + data-qa-anchor={accessibilityId} + initial={{ y: 0 }} + drag="y" + whileDrag={{ scale: 0.95, borderRadius: '8px', cursor: 'grabbing' }} + dragConstraints={{ top: 0, bottom: 200 }} + dragElastic={{ top: 0, bottom: 0.5 }} + onDragStart={() => { + dragEventTarget.current.dispatchEvent(new Event('dragstart')); + }} + onDrag={(_, info) => { + // Prevent dragging upwards + if (info.point.y < info.point.y - info.offset.y) { + y.set(0); + } + }} + onDragEnd={(_, info) => { + dragEventTarget.current.dispatchEvent(new Event('dragend')); + if (info.offset.y > 100) { + onSwipeDown(targetId); + } else { + y.set(0); + } + }} + className={clsx(styles.viewStoryContainer)} + > <div className={clsx(styles.viewStoryContent)}> <div className={clsx(styles.overlayLeft)} onClick={previousStory} /> <div className={clsx(styles.overlayRight)} onClick={nextStory} /> @@ -323,7 +354,7 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ onAllStoriesEnd={nextStory} /> </div> - </div> + </motion.div> <ArrowRightButton onClick={nextStory} /> </div> ); From 384e59e4a49feba0900d303446992e9200fb1925 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Tue, 2 Jul 2024 16:09:57 +0700 Subject: [PATCH 187/300] fix: ASC-00000 - css broken in v3 (#473) * fix: css * fix: css --- .../components/StoryTab/StoryTabCommunity.module.css | 1 + .../social/elements/CloseButton/CloseButton.module.css | 1 + .../CreateNewStoryButton/CreateNewStoryButton.module.css | 4 +++- .../CreateNewStoryButton/CreateNewStoryButton.tsx | 2 +- .../OverflowMenuButton/OverflowMenuButton.module.css | 3 ++- .../elements/OverflowMenuButton/OverflowMenuButton.tsx | 2 +- .../StoryCommentButton/StoryCommentButton.module.css | 1 + .../StoryReactionButton/StoryReactionButton.module.css | 1 + .../internal-components/StoryViewer/Renderers/Image.tsx | 1 + .../StoryViewer/Renderers/Wrappers/Footer/index.tsx | 9 ++++++++- 10 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/v4/social/components/StoryTab/StoryTabCommunity.module.css b/src/v4/social/components/StoryTab/StoryTabCommunity.module.css index 97e990987..3378a1b08 100644 --- a/src/v4/social/components/StoryTab/StoryTabCommunity.module.css +++ b/src/v4/social/components/StoryTab/StoryTabCommunity.module.css @@ -38,6 +38,7 @@ } .storyAvatarContainer { + all: unset; position: absolute; top: 50%; left: 50%; diff --git a/src/v4/social/elements/CloseButton/CloseButton.module.css b/src/v4/social/elements/CloseButton/CloseButton.module.css index 65eddf849..52d2c3d2c 100644 --- a/src/v4/social/elements/CloseButton/CloseButton.module.css +++ b/src/v4/social/elements/CloseButton/CloseButton.module.css @@ -25,6 +25,7 @@ } .closeButton { + all: unset; cursor: pointer; width: 1.5rem; height: 1.25rem; diff --git a/src/v4/social/elements/CreateNewStoryButton/CreateNewStoryButton.module.css b/src/v4/social/elements/CreateNewStoryButton/CreateNewStoryButton.module.css index b4c87e34a..d757731e9 100644 --- a/src/v4/social/elements/CreateNewStoryButton/CreateNewStoryButton.module.css +++ b/src/v4/social/elements/CreateNewStoryButton/CreateNewStoryButton.module.css @@ -1,4 +1,6 @@ -.createNewStoryIcon { +.createNewStoryButton { + all: unset; + cursor: pointer; position: absolute; bottom: 0; right: 0; diff --git a/src/v4/social/elements/CreateNewStoryButton/CreateNewStoryButton.tsx b/src/v4/social/elements/CreateNewStoryButton/CreateNewStoryButton.tsx index ac1a4d370..297105072 100644 --- a/src/v4/social/elements/CreateNewStoryButton/CreateNewStoryButton.tsx +++ b/src/v4/social/elements/CreateNewStoryButton/CreateNewStoryButton.tsx @@ -51,7 +51,7 @@ export const CreateNewStoryButton = ({ return ( <Button style={themeStyles} - className={clsx(styles.createNewStoryIcon, defaultClassName)} + className={clsx(styles.createNewStoryButton, defaultClassName)} data-qa-anchor={accessibilityId} onPress={onPress} > diff --git a/src/v4/social/elements/OverflowMenuButton/OverflowMenuButton.module.css b/src/v4/social/elements/OverflowMenuButton/OverflowMenuButton.module.css index ff6e0e1b2..61405faca 100644 --- a/src/v4/social/elements/OverflowMenuButton/OverflowMenuButton.module.css +++ b/src/v4/social/elements/OverflowMenuButton/OverflowMenuButton.module.css @@ -1,3 +1,4 @@ -.overflowMenuIcon { +.overflowMenuButton { + all: unset; cursor: pointer; } diff --git a/src/v4/social/elements/OverflowMenuButton/OverflowMenuButton.tsx b/src/v4/social/elements/OverflowMenuButton/OverflowMenuButton.tsx index 64ddb5ac8..750e0e8ac 100644 --- a/src/v4/social/elements/OverflowMenuButton/OverflowMenuButton.tsx +++ b/src/v4/social/elements/OverflowMenuButton/OverflowMenuButton.tsx @@ -50,7 +50,7 @@ export const OverflowMenuButton = ({ <Button style={themeStyles} data-qa-anchor={accessibilityId} - className={styles.overflowMenuIcon} + className={styles.overflowMenuButton} onPress={onPress} > <IconComponent diff --git a/src/v4/social/elements/StoryCommentButton/StoryCommentButton.module.css b/src/v4/social/elements/StoryCommentButton/StoryCommentButton.module.css index 9e10a7467..bf77e4b4e 100644 --- a/src/v4/social/elements/StoryCommentButton/StoryCommentButton.module.css +++ b/src/v4/social/elements/StoryCommentButton/StoryCommentButton.module.css @@ -1,4 +1,5 @@ .storyCommentButton { + all: unset; display: flex; padding: var(--asc-spacing-s1) var(--asc-spacing-s2); align-items: center; diff --git a/src/v4/social/elements/StoryReactionButton/StoryReactionButton.module.css b/src/v4/social/elements/StoryReactionButton/StoryReactionButton.module.css index 1032c4c10..fa3fcc04b 100644 --- a/src/v4/social/elements/StoryReactionButton/StoryReactionButton.module.css +++ b/src/v4/social/elements/StoryReactionButton/StoryReactionButton.module.css @@ -1,4 +1,5 @@ .storyReactionButton { + all: unset; display: flex; padding: var(--asc-spacing-s1) var(--asc-spacing-s2); align-items: center; diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index ca14d260c..2db18f7fc 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -338,6 +338,7 @@ export const renderer: CustomRenderer = ({ )} <Footer + pageId={pageId} storyId={storyId} syncState={syncState} reach={reach} diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx index 8968c5869..7c0541898 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx @@ -14,6 +14,7 @@ import styles from './Footer.module.css'; const Footer: React.FC< React.PropsWithChildren<{ + pageId?: string; storyId: string; showImpression: boolean; reach: number | null; @@ -26,6 +27,7 @@ const Footer: React.FC< myReactions?: string[]; }> > = ({ + pageId = '*', syncState, reach, commentsCount, @@ -90,8 +92,13 @@ const Footer: React.FC< )} </div> <div className={styles.viewStoryCompostBarEngagementContainer}> - <StoryCommentButton commentsCount={commentsCount} onPress={onClickComment} /> + <StoryCommentButton + pageId={pageId} + commentsCount={commentsCount} + onPress={onClickComment} + /> <StoryReactionButton + pageId={pageId} myReactions={myReactions} reactionsCount={reactionsCount} onPress={handleClickReaction} From 674ccce987872f35be8bb4e722ebbbe0f628e292 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Tue, 2 Jul 2024 16:33:44 +0700 Subject: [PATCH 188/300] fix: ASC-23389 - disable overlay when desktop screen (#477) * fix: swipe down * fix: css * fix: next story condition * fix: css --- src/v4/social/pages/StoryPage/GlobalFeedStory.tsx | 1 + src/v4/social/pages/StoryPage/StoryPage.module.css | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx index 09c7d1e20..41899d386 100644 --- a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx @@ -214,6 +214,7 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ ); const increaseIndex = () => { + if (currentIndex === stories.length - 1) return; setCurrentIndex((prevIndex) => prevIndex + 1); }; diff --git a/src/v4/social/pages/StoryPage/StoryPage.module.css b/src/v4/social/pages/StoryPage/StoryPage.module.css index 58fcf19cd..b12a15bec 100644 --- a/src/v4/social/pages/StoryPage/StoryPage.module.css +++ b/src/v4/social/pages/StoryPage/StoryPage.module.css @@ -60,3 +60,17 @@ .deleteIcon { fill: var(--asc-color-base-default); } + +/* TODO: should standardize media variable eg. mobile screen, tablet screen ,desktop screen */ + +/* Mobile-first approach */ +@media (width >= 768px) { + .overlayLeft, + .overlayRight { + display: none; + } + + .arrowButton { + display: block; + } +} From 74a85f2c1b1d6f45163c2e765846d7ecfeb555ad Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Tue, 2 Jul 2024 16:48:52 +0700 Subject: [PATCH 189/300] style: ASC-23581 - add width full button (#476) * style: add width full * fix: type create post --- .../elements/CreatePostButton/CreatePostButton.module.css | 1 + src/v4/social/pages/PostComposerPage/PostComposerPage.tsx | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/v4/social/elements/CreatePostButton/CreatePostButton.module.css b/src/v4/social/elements/CreatePostButton/CreatePostButton.module.css index bac185ecf..ab3d9513e 100644 --- a/src/v4/social/elements/CreatePostButton/CreatePostButton.module.css +++ b/src/v4/social/elements/CreatePostButton/CreatePostButton.module.css @@ -1,5 +1,6 @@ .createPostButton { display: flex; + width: 100%; padding: 0.75rem 0; cursor: pointer; } diff --git a/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx b/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx index 9bc5facb0..670423ae0 100644 --- a/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx +++ b/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx @@ -26,12 +26,12 @@ export enum Mode { } interface AmityPostComposerEditOptions { mode: Mode.EDIT; - post?: Amity.Post; + post: Amity.Post; } interface AmityPostComposerCreateOptions { mode: Mode.CREATE; - targetId: string | null; + targetId?: string | null; targetType: 'community' | 'user'; community?: Amity.Community; } From 3b54ede69a44b68bf48886ba1bed89523df6e8a1 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Tue, 2 Jul 2024 17:04:23 +0700 Subject: [PATCH 190/300] fix: ASC-23552 - fix reaction button interaction (#472) * fix: fix reaction button interaction * chore: remove unused code and apply configuration --- .../ReactionButton/ReactionButton.tsx | 233 +----------------- 1 file changed, 6 insertions(+), 227 deletions(-) diff --git a/src/v4/social/elements/ReactionButton/ReactionButton.tsx b/src/v4/social/elements/ReactionButton/ReactionButton.tsx index 2a61c593e..7332a88ad 100644 --- a/src/v4/social/elements/ReactionButton/ReactionButton.tsx +++ b/src/v4/social/elements/ReactionButton/ReactionButton.tsx @@ -9,6 +9,7 @@ import Like from '~/v4/social/elements/ReactionButton/Like'; import Love from '~/v4/social/elements/ReactionButton/Love'; import styles from './ReactionButton.module.css'; import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { Button } from '~/v4/core/natives/Button'; const LikeSvg = (props: React.SVGProps<SVGSVGElement>) => ( <svg @@ -52,44 +53,8 @@ export function ReactionButton({ elementId, }); - // const clickTimerRef = useRef(0); - // const touchTimerRef = useRef<NodeJS.Timeout | null>(null); - // const [isShowReactionPanel, setIsShowReactionPanel] = useState(false); - - // const likeRef = useRef<HTMLDivElement>(null); - // const loveRef = useRef<HTMLDivElement>(null); - // const fireRef = useRef<HTMLDivElement>(null); - // const happyRef = useRef<HTMLDivElement>(null); - // const cryingRef = useRef<HTMLDivElement>(null); - const hasMyReaction = myReaction != null; - // const [selectedReaction, setSelectedReaction] = useState<string | null>(null); - // const [activeReaction, setActiveReaction] = useState<string | null>(null); - - // useEffect(() => { - // if (selectedReaction) { - // setTimeout(() => { - // setSelectedReaction(null); - // setIsShowReactionPanel(false); - // }, 250); - // } - // }, [selectedReaction]); - - // const hideReactionPanel = (ev: MouseEvent) => { - // ev.preventDefault(); - // ev.stopPropagation(); - // setIsShowReactionPanel(false); - // }; - - // useEffect(() => { - // if (isShowReactionPanel) { - // window.addEventListener('click', hideReactionPanel); - // } else { - // window.removeEventListener('click', hideReactionPanel); - // } - // }, [isShowReactionPanel]); - if (isExcluded) return null; const renderMyReaction = () => { @@ -110,149 +75,12 @@ export function ReactionButton({ }; return ( - <div - className={styles.reactButton} - data-qa-anchor={accessibilityId} + <Button style={themeStyles} - onMouseDown={(ev) => { - ev.preventDefault(); - ev.stopPropagation(); - - // clickTimerRef.current = Date.now(); - - // touchTimerRef.current = setTimeout(() => { - // setIsShowReactionPanel(true); - // }, MOUSE_DURATION); - }} - onTouchStart={(ev) => { - ev.preventDefault(); - ev.stopPropagation(); - - // clickTimerRef.current = Date.now(); - - // touchTimerRef.current = setTimeout(() => { - // setIsShowReactionPanel(true); - // }, MOUSE_DURATION); - }} - // onMouseMove={(ev) => { - // ev.preventDefault(); - // ev.stopPropagation(); - - // if ( - // likeRef.current && - // likeRef.current.offsetLeft < ev.clientX && - // ev.clientX < likeRef.current.offsetLeft + likeRef.current.clientWidth - // ) { - // setActiveReaction('like'); - // return; - // } - // if ( - // loveRef.current && - // loveRef.current.offsetLeft < ev.clientX && - // ev.clientX < loveRef.current.offsetLeft + loveRef.current.clientWidth - // ) { - // setActiveReaction('love'); - // return; - // } - // if ( - // fireRef.current && - // fireRef.current.offsetLeft < ev.clientX && - // ev.clientX < fireRef.current.offsetLeft + fireRef.current.clientWidth - // ) { - // setActiveReaction('fire'); - // return; - // } - // if ( - // happyRef.current && - // happyRef.current.offsetLeft < ev.clientX && - // ev.clientX < happyRef.current.offsetLeft + happyRef.current.clientWidth - // ) { - // setActiveReaction('happy'); - // return; - // } - // if ( - // cryingRef.current && - // cryingRef.current.offsetLeft < ev.clientX && - // ev.clientX < cryingRef.current.offsetLeft + cryingRef.current.clientWidth - // ) { - // setActiveReaction('crying'); - // return; - // } - - // setActiveReaction(null); - // }} - // onTouchMove={(ev) => { - // ev.preventDefault(); - // ev.stopPropagation(); - - // if ( - // likeRef.current && - // likeRef.current.offsetLeft < ev.touches[0].clientX && - // ev.touches[0].clientX < likeRef.current.offsetLeft + likeRef.current.clientWidth - // ) { - // setActiveReaction('like'); - // return; - // } - // if ( - // loveRef.current && - // loveRef.current.offsetLeft < ev.touches[0].clientX && - // ev.touches[0].clientX < loveRef.current.offsetLeft + loveRef.current.clientWidth - // ) { - // setActiveReaction('love'); - // return; - // } - // if ( - // fireRef.current && - // fireRef.current.offsetLeft < ev.touches[0].clientX && - // ev.touches[0].clientX < fireRef.current.offsetLeft + fireRef.current.clientWidth - // ) { - // setActiveReaction('fire'); - // return; - // } - // if ( - // happyRef.current && - // happyRef.current.offsetLeft < ev.touches[0].clientX && - // ev.touches[0].clientX < happyRef.current.offsetLeft + happyRef.current.clientWidth - // ) { - // setActiveReaction('happy'); - // return; - // } - // if ( - // cryingRef.current && - // cryingRef.current.offsetLeft < ev.touches[0].clientX && - // ev.touches[0].clientX < cryingRef.current.offsetLeft + cryingRef.current.clientWidth - // ) { - // setActiveReaction('crying'); - // return; - // } - - // setActiveReaction(null); - // }} - onTouchEnd={(ev) => { - // touchTimerRef.current && clearTimeout(touchTimerRef.current); - // touchTimerRef.current = null; - // setIsShowReactionPanel(false); - // if (activeReaction) { - // setSelectedReaction(activeReaction); - // onReactionClick(activeReaction); - // setActiveReaction(null); - // } else { - // setSelectedReaction('like'); - onReactionClick('like'); - // } - }} - onMouseUp={(ev) => { - // touchTimerRef.current && clearTimeout(touchTimerRef.current); - // touchTimerRef.current = null; - // setIsShowReactionPanel(false); - // if (activeReaction) { - // setSelectedReaction(activeReaction); - // onReactionClick(activeReaction); - // setActiveReaction(null); - // } else { - // setSelectedReaction('like'); + data-qa-anchor={accessibilityId} + className={styles.reactButton} + onPress={() => { onReactionClick('like'); - // } }} > {myReaction ? ( @@ -276,55 +104,6 @@ export function ReactionButton({ > {typeof reactionsCount === 'number' ? reactionsCount : myReaction || config.text} </Typography.BodyBold> - {/* {isShowReactionPanel ? ( - <div className={styles.reactButton__panel}> - <div - className={styles.reactButton__panel__reaction} - data-active={selectedReaction === 'like'} - data-touch-over={activeReaction === 'like'} - ref={likeRef} - > - <div className={styles.reactButton__panel__reaction__text}>Like</div> - <Like /> - </div> - <div - className={styles.reactButton__panel__reaction} - data-active={selectedReaction === 'love'} - data-touch-over={activeReaction === 'love'} - ref={loveRef} - > - <div className={styles.reactButton__panel__reaction__text}>Love</div> - <Love /> - </div> - <div - className={styles.reactButton__panel__reaction} - data-active={selectedReaction === 'fire'} - data-touch-over={activeReaction === 'fire'} - ref={fireRef} - > - <div className={styles.reactButton__panel__reaction__text}>Fire</div> - <Fire /> - </div> - <div - className={styles.reactButton__panel__reaction} - data-active={selectedReaction === 'happy'} - data-touch-over={activeReaction === 'happy'} - ref={happyRef} - > - <div className={styles.reactButton__panel__reaction__text}>Happy</div> - <Happy /> - </div> - <div - className={styles.reactButton__panel__reaction} - data-active={selectedReaction === 'crying'} - data-touch-over={activeReaction === 'crying'} - ref={cryingRef} - > - <div className={styles.reactButton__panel__reaction__text}>Crying</div> - <Crying /> - </div> - </div> - ) : null} */} - </div> + </Button> ); } From a3552a1e0d98e9ad0bd0842b4ca08f32fdf08bca Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Tue, 2 Jul 2024 17:08:31 +0700 Subject: [PATCH 191/300] fix: type error (#471) --- src/v4/social/components/PostContent/PostContent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v4/social/components/PostContent/PostContent.tsx b/src/v4/social/components/PostContent/PostContent.tsx index 0f4fdd4f6..deb906684 100644 --- a/src/v4/social/components/PostContent/PostContent.tsx +++ b/src/v4/social/components/PostContent/PostContent.tsx @@ -193,7 +193,7 @@ export const PostContent = ({ useEffect(() => { if (post == null) return; - setReactionByMe(post.myReactions[0] || null); + setReactionByMe(post.myReactions?.[0] || null); }, [post.myReactions]); useEffect(() => { From 413845bd96dab12ffba6198a7bdcb45dfd70f471 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Tue, 2 Jul 2024 17:12:22 +0700 Subject: [PATCH 192/300] fix: ASC-23659 - newsfeed layout (#474) * fix: newsfeed layout * chore: Update SocialHomePage.module.css --- .../SocialHomePage/SocialHomePage.module.css | 34 ++++++++++++------- .../pages/SocialHomePage/SocialHomePage.tsx | 14 ++++---- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/v4/social/pages/SocialHomePage/SocialHomePage.module.css b/src/v4/social/pages/SocialHomePage/SocialHomePage.module.css index d4f63f76d..164175798 100644 --- a/src/v4/social/pages/SocialHomePage/SocialHomePage.module.css +++ b/src/v4/social/pages/SocialHomePage/SocialHomePage.module.css @@ -1,25 +1,30 @@ .socialHomePage { - display: flex; - flex-direction: column; - height: 3.625rem; + position: relative; width: 100%; + height: 100%; background-color: var(--asc-color-base-background); } .socialHomePage__topBar { + top: 0; + position: absolute; width: 100%; + height: 7.5rem; + background-color: var(--asc-color-base-background); +} + +.socialHomePage__topNavigation { padding-left: 1rem; padding-right: 1rem; - background-color: var(--asc-color-base-background); } .socialHomePage__tabs { display: inline-flex; flex-wrap: nowrap; gap: 0.5rem; - overflow-y: scroll; + overflow-x: scroll; width: 100%; - padding: 0.75rem 0; + padding: 0.75rem 1rem; -ms-overflow-style: none; /* IE and Edge */ scrollbar-width: none; /* Firefox */ } @@ -29,10 +34,15 @@ } .socialHomePage__contents { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - gap: 0.5rem; - flex: 1; + position: absolute; + top: 7.5rem; + width: 100%; + height: calc(100% - 7.5rem); + overflow-y: scroll; + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ +} + +.socialHomePage__contents::-webkit-scrollbar { + display: none; } diff --git a/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx b/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx index f08ad5cde..925c35e2f 100644 --- a/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx +++ b/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx @@ -70,12 +70,14 @@ export function SocialHomePage() { return ( <div className={styles.socialHomePage} style={themeStyles}> <div className={styles.socialHomePage__topBar}> - <TopNavigation - pageId={pageId} - onClickPostCreationButton={handleClickButton} - onGlobalSearchButtonClick={handleGlobalSearchClick} - createPostButtonRef={createPostButtonRef} - /> + <div className={styles.socialHomePage__topNavigation}> + <TopNavigation + pageId={pageId} + onClickPostCreationButton={handleClickButton} + onGlobalSearchButtonClick={handleGlobalSearchClick} + createPostButtonRef={createPostButtonRef} + /> + </div> {isShowCreatePostMenu && ( <div ref={createPostMenuRef}> <CreatePostMenu pageId={pageId} /> From 6ca94efb436ab6817c1b22374cd2a5d77af22000 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Tue, 2 Jul 2024 17:26:08 +0700 Subject: [PATCH 193/300] fix: ASC-23007 - community member collection limit (#478) * fix: swipe down * fix: css * fix: next story condition * fix: limit * fix: type * fix: hooks --- .../collections/useCommentsCollection.ts | 9 ++-- .../collections/useCommunitiesCollection.ts | 2 +- .../useCommunityMembersCollection.ts | 14 +++-- .../internal-components/Comment/index.tsx | 9 +++- .../StoryViewer/Renderers/Image.tsx | 42 ++++----------- .../StoryViewer/Renderers/Video.tsx | 53 ++++--------------- .../StoryViewer/Renderers/types.ts | 1 + 7 files changed, 45 insertions(+), 85 deletions(-) diff --git a/src/v4/social/hooks/collections/useCommentsCollection.ts b/src/v4/social/hooks/collections/useCommentsCollection.ts index b642f9be5..190e39adb 100644 --- a/src/v4/social/hooks/collections/useCommentsCollection.ts +++ b/src/v4/social/hooks/collections/useCommentsCollection.ts @@ -1,13 +1,12 @@ import { CommentRepository } from '@amityco/ts-sdk'; - -import useLiveCollection from '~/core/hooks/useLiveCollection'; +import useLiveCollection from '~/v4/core/hooks/useLiveCollection'; type useCommentsParams = { parentId?: string | null; referenceId?: string | null; referenceType: Amity.CommentReferenceType; limit?: number; - shouldCall?: () => boolean; + shouldCall?: boolean; includeDeleted?: boolean; }; @@ -16,7 +15,7 @@ export default function useCommentsCollection({ referenceId, referenceType, limit = 10, - shouldCall = () => true, + shouldCall = true, includeDeleted = false, }: useCommentsParams) { const { items, ...rest } = useLiveCollection({ @@ -28,7 +27,7 @@ export default function useCommentsCollection({ limit, includeDeleted, }, - shouldCall: () => shouldCall() && !!referenceId && !!referenceType, + shouldCall: !!referenceId && !!referenceType && shouldCall, }); return { diff --git a/src/v4/social/hooks/collections/useCommunitiesCollection.ts b/src/v4/social/hooks/collections/useCommunitiesCollection.ts index dcb66f8fe..8d3765f6c 100644 --- a/src/v4/social/hooks/collections/useCommunitiesCollection.ts +++ b/src/v4/social/hooks/collections/useCommunitiesCollection.ts @@ -11,7 +11,7 @@ export default function useCommunitiesCollection({ const { items, ...rest } = useLiveCollection({ fetcher: CommunityRepository.getCommunities, params: queryParams as Parameters<typeof CommunityRepository.getCommunities>[0], - shouldCall: !!queryParams && shouldCall, + shouldCall: !!queryParams?.categoryId && shouldCall, }); return { diff --git a/src/v4/social/hooks/collections/useCommunityMembersCollection.ts b/src/v4/social/hooks/collections/useCommunityMembersCollection.ts index be5668978..d1de0f488 100644 --- a/src/v4/social/hooks/collections/useCommunityMembersCollection.ts +++ b/src/v4/social/hooks/collections/useCommunityMembersCollection.ts @@ -1,11 +1,17 @@ import { CommunityRepository } from '@amityco/ts-sdk'; -import useLiveCollection from '~/core/hooks/useLiveCollection'; +import useLiveCollection from '~/v4/core/hooks/useLiveCollection'; -export default function useCommunityMembersCollection(communityId?: string, limit: number = 5) { +export default function useCommunityMembersCollection({ + queryParams, + shouldCall = true, +}: { + queryParams?: Parameters<typeof CommunityRepository.Membership.getMembers>[0]; + shouldCall?: boolean; +}) { const { items, ...rest } = useLiveCollection({ fetcher: CommunityRepository.Membership.getMembers, - params: { communityId: communityId as string, limit, memberships: ['member'] }, - shouldCall: () => !!communityId, + params: queryParams as Parameters<typeof CommunityRepository.Membership.getMembers>[0], + shouldCall: !!queryParams?.communityId && shouldCall, }); return { diff --git a/src/v4/social/internal-components/Comment/index.tsx b/src/v4/social/internal-components/Comment/index.tsx index c6e5e1b5c..a911aeb20 100644 --- a/src/v4/social/internal-components/Comment/index.tsx +++ b/src/v4/social/internal-components/Comment/index.tsx @@ -66,7 +66,13 @@ export const Comment = ({ const comment = useComment(commentId); const { item: story } = useGetStoryByStoryId(comment?.referenceId); - const { members } = useCommunityMembersCollection(story?.community?.communityId); + const { members } = useCommunityMembersCollection({ + queryParams: { + communityId: story?.community?.communityId as string, + limit: 10, + }, + shouldCall: !!story?.community?.communityId, + }); const [bottomSheet, setBottomSheet] = useState(false); const [selectedCommentId, setSelectedCommentId] = useState(''); @@ -78,6 +84,7 @@ export const Comment = ({ fileId: commentAuthor?.user?.avatarFileId, imageSize: 'small', }); + const { userRoles } = useSDK(); const { toggleFlagComment, isFlaggedByMe } = useCommentFlaggedByMe(commentId); diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index 2db18f7fc..47d890175 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -10,7 +10,6 @@ import { CommentTray } from '~/v4/social/components'; import { HyperLink } from '~/v4/social/elements/HyperLink'; import Footer from '~/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer'; import Header from '~/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header'; -import { motion, PanInfo, useAnimationControls } from 'framer-motion'; import { BottomSheet } from '~/v4/core/components/BottomSheet'; import { Typography } from '~/v4/core/components'; import { Button } from '~/v4/core/components/Button'; @@ -65,12 +64,16 @@ export const renderer: CustomRenderer = ({ dragEventTarget, } = story; - const { members } = useCommunityMembersCollection(community?.communityId as string); + const { members } = useCommunityMembersCollection({ + queryParams: { + communityId: community?.communityId as string, + }, + shouldCall: !!community?.communityId, + }); const member = members?.find((member) => member.userId === client?.userId); const isMember = member != null; const { user } = useUser(client?.userId); - const controls = useAnimationControls(); const isOfficial = community?.isOfficial || false; const isCreator = creator?.userId === user?.userId; @@ -145,31 +148,6 @@ export const renderer: CustomRenderer = ({ setIsOpenCommentSheet(false); }; - const handleSwipeDown = () => { - controls - .start({ - y: '100%', - transition: { duration: 0.3, ease: 'easeOut' }, - }) - .then(() => { - onSwipeDown?.(); - }); - }; - - const handleDragStart = () => { - action('pause', true); - }; - - const handleDragEnd = (_event: MouseEvent | TouchEvent | PointerEvent, info: PanInfo) => { - if (info.offset.y > 100) { - handleSwipeDown(); - } else { - controls.start({ y: 0, transition: { duration: 0.3, ease: 'easeOut' } }).then(() => { - action('play', true); - }); - } - }; - const handleOnClose = () => { onClose(); }; @@ -218,12 +196,12 @@ export const renderer: CustomRenderer = ({ setIsPaused(false); }; - dragEventTarget.addEventListener('dragstart', handleDragStart); - dragEventTarget.addEventListener('dragend', handleDragEnd); + dragEventTarget.current?.addEventListener('dragstart', handleDragStart); + dragEventTarget.current?.addEventListener('dragend', handleDragEnd); return () => { - dragEventTarget.removeEventListener('dragstart', handleDragStart); - dragEventTarget.removeEventListener('dragend', handleDragEnd); + dragEventTarget.current?.removeEventListener('dragstart', handleDragStart); + dragEventTarget.current?.removeEventListener('dragend', handleDragEnd); }; } }, [dragEventTarget]); diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx index 80203a7de..a0bacd083 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx @@ -1,6 +1,4 @@ -import React, { useCallback, useEffect, useRef, useState } from 'react'; - -import { useIntl } from 'react-intl'; +import React, { useEffect, useRef, useState } from 'react'; import Truncate from 'react-truncate-markup'; import { @@ -15,17 +13,12 @@ import { CommentTray } from '~/v4/social/components'; import { HyperLink } from '~/v4/social/elements/HyperLink'; import Header from '~/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header'; import Footer from '~/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer'; - -import { motion, PanInfo, useAnimationControls } from 'framer-motion'; - import useCommunityMembersCollection from '~/v4/social/hooks/collections/useCommunityMembersCollection'; import useSDK from '~/v4/core/hooks/useSDK'; import useUser from '~/v4/core/hooks/objects/useUser'; - import clsx from 'clsx'; import { LIKE_REACTION_KEY } from '~/v4/social/constants/reactions'; import { checkStoryPermission, formatTimeAgo, isAdmin, isModerator } from '~/v4/social/utils'; - import rendererStyles from './Renderers.module.css'; import { StoryProgressBar } from '~/v4/social/elements/StoryProgressBar/StoryProgressBar'; @@ -34,7 +27,6 @@ export const renderer: CustomRenderer = ({ action, config, messageHandler, - onSwipeDown, onClose, onClickCommunity, }) => { @@ -69,7 +61,12 @@ export const renderer: CustomRenderer = ({ dragEventTarget, } = story; - const { members } = useCommunityMembersCollection(community?.communityId as string); + const { members } = useCommunityMembersCollection({ + queryParams: { + communityId: community?.communityId as string, + }, + shouldCall: !!community?.communityId, + }); const member = members?.find((member) => member.userId === client?.userId); const isMember = member != null; @@ -92,7 +89,6 @@ export const renderer: CustomRenderer = ({ isGlobalAdmin || isCommunityModerator || checkStoryPermission(client, community?.communityId); const vid = useRef<HTMLVideoElement>(null); - const controls = useAnimationControls(); const onWaiting = () => action('pause', true); const onPlaying = () => action('play', true); @@ -147,33 +143,6 @@ export const renderer: CustomRenderer = ({ const targetRootId = 'asc-uikit-stories-viewer'; - const handleSwipeDown = () => { - controls - .start({ - y: '100%', - transition: { duration: 0.3, ease: 'easeOut' }, - }) - .then(() => { - onSwipeDown?.(); - }); - }; - - const handleDragStart = () => { - setIsPaused(true); - action('pause', true); - }; - - const handleDragEnd = (_event: MouseEvent | TouchEvent | PointerEvent, info: PanInfo) => { - if (info.offset.y > 100) { - handleSwipeDown(); - } else { - controls.start({ y: 0, transition: { duration: 0.3, ease: 'easeOut' } }).then(() => { - setIsPaused(false); - action('play', true); - }); - } - }; - const handleOnClose = () => { onClose(); }; @@ -222,12 +191,12 @@ export const renderer: CustomRenderer = ({ setIsPaused(false); }; - dragEventTarget.addEventListener('dragstart', handleDragStart); - dragEventTarget.addEventListener('dragend', handleDragEnd); + dragEventTarget.current?.addEventListener('dragstart', handleDragStart); + dragEventTarget.current?.addEventListener('dragend', handleDragEnd); return () => { - dragEventTarget.removeEventListener('dragstart', handleDragStart); - dragEventTarget.removeEventListener('dragend', handleDragEnd); + dragEventTarget.current?.removeEventListener('dragstart', handleDragStart); + dragEventTarget.current?.removeEventListener('dragend', handleDragEnd); }; } }, [dragEventTarget]); diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/types.ts b/src/v4/social/internal-components/StoryViewer/Renderers/types.ts index 056796ac5..8a24f7fcd 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/types.ts +++ b/src/v4/social/internal-components/StoryViewer/Renderers/types.ts @@ -31,6 +31,7 @@ export type CustomStory = Story & storiesCount: number; increaseIndex: () => void; pageId?: string; + dragEventTarget?: React.RefObject<HTMLElement>; }; export type CustomRendererProps = RendererProps & { From 32464b8ec2c503e1ad46e57839a6a371ee8acdee Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Tue, 2 Jul 2024 18:10:22 +0700 Subject: [PATCH 194/300] fix(sdk): ASC-22474 - story preview thumbnail hyperlink in console (#480) * fix: swipe down * fix: css * fix: next story condition * fix: limit * fix: story preview thumbnail for console * fix: story preview * fix: npmrc * fix: export * fix: export * fix: export * fix: export * fix: export --- .npmrc | 1 + src/index.ts | 5 +- .../StoryPreview/StoryPreview.module.css | 50 +++- .../StoryPreview/StoryPreview.tsx | 261 ++++++++++++------ .../StoryPreviewThumbnail.module.css | 166 +++++++++++ .../StoryPreviewThumbnail.tsx | 122 ++++++++ .../StoryPreviewThumbnail/ui.stories.tsx | 56 ++++ .../internal-components/StoryPreview/index.ts | 1 + .../StoryPreview/ui.stories.tsx | 11 +- 9 files changed, 580 insertions(+), 93 deletions(-) create mode 100644 src/v4/social/internal-components/StoryPreview/StoryPreviewThumbnail/StoryPreviewThumbnail.module.css create mode 100644 src/v4/social/internal-components/StoryPreview/StoryPreviewThumbnail/StoryPreviewThumbnail.tsx create mode 100644 src/v4/social/internal-components/StoryPreview/StoryPreviewThumbnail/ui.stories.tsx diff --git a/.npmrc b/.npmrc index e69de29bb..bd3327ab5 100644 --- a/.npmrc +++ b/.npmrc @@ -0,0 +1 @@ +//registry.npmjs.org/:_authToken=${NPM_TOKEN} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 1d3bbd08a..860a99bf6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -49,7 +49,10 @@ export type { ReactionListProps as AmityReactionListProps }; export { AmityLiveChatPage } from '~/v4/chat/pages'; // v4 internal use only (Amity Console) -export { StoryPreview as AmityStoryPreview } from './v4/social/internal-components/StoryPreview'; +export { + StoryPreview as AmityStoryPreview, + StoryPreviewThumbnail as AmityStoryPreviewThumbnail, +} from './v4/social/internal-components/StoryPreview'; // import AmityComment from './components/Comment'; // import AmityCommentComposeBar from './components/CommentComposeBar'; diff --git a/src/v4/social/internal-components/StoryPreview/StoryPreview.module.css b/src/v4/social/internal-components/StoryPreview/StoryPreview.module.css index ad5440977..777a0d3a3 100644 --- a/src/v4/social/internal-components/StoryPreview/StoryPreview.module.css +++ b/src/v4/social/internal-components/StoryPreview/StoryPreview.module.css @@ -2,6 +2,24 @@ color: var(--asc-color-white); } +.contentWrapper { + width: 100%; + height: 100%; + position: relative; + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.storyPreviewWrapper { + width: 100%; + height: 100%; + position: relative; + display: flex; + flex-direction: column; + justify-content: space-between; +} + .storyPreview_description { color: var(--asc-color-white); } @@ -42,7 +60,7 @@ .userInfo { width: 100%; display: flex; - justify-content: flex-start; + justify-content: space-between; align-items: center; gap: 0.5rem; } @@ -59,7 +77,7 @@ .hyperLinkContainer { position: absolute; - bottom: 0; + bottom: 2rem; z-index: 1; display: flex; justify-content: center; @@ -80,7 +98,29 @@ object-fit: cover; } -.avatar { - width: 2.5rem; - height: 2.5rem; +.avatarContainer { + width: 2rem; + height: 2rem; +} + +.imageFit { + object-fit: contain; +} + +.imageCover { + object-fit: cover; +} + +.headerContent { + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; +} + +.userInfoContent { + display: flex; + justify-content: space-between; + align-items: center; + gap: 0.5rem; } diff --git a/src/v4/social/internal-components/StoryPreview/StoryPreview.tsx b/src/v4/social/internal-components/StoryPreview/StoryPreview.tsx index 772922cbd..29af176e1 100644 --- a/src/v4/social/internal-components/StoryPreview/StoryPreview.tsx +++ b/src/v4/social/internal-components/StoryPreview/StoryPreview.tsx @@ -1,26 +1,27 @@ import React, { useEffect, useRef, useState } from 'react'; -import Community from '~/v4/icons/Community'; -import Verified from '~/v4/social/icons/verified'; -import { Typography } from '~/v4/core/components'; +import { VerifiedIcon } from '~/v4/social/icons'; +import { Avatar, Typography } from '~/v4/core/components'; import { HyperLink } from '~/v4/social/elements/HyperLink'; import TruncateMarkup from 'react-truncate-markup'; import styles from './StoryPreview.module.css'; -import { Avatar } from '~/v4/core/components/Avatar/'; +import { PauseIcon, PlayIcon } from '~/icons'; +import ColorThief from 'colorthief'; +import Community from '~/v4/icons/Community'; +import clsx from 'clsx'; type AmityStoryMediaType = { type: 'image'; url: string } | { type: 'video'; url: string }; type StoryPreviewProps = { mediaType?: AmityStoryMediaType; - file?: File | null; imageMode: 'fit' | 'fill'; - + width?: number; + height?: number; hyperLink: { data: { url: string; customText: string }; type: Amity.StoryItemType; }[]; onPlay?: () => void; onPause?: () => void; - onComplete?: () => void; avatar: string; title: string; description: string; @@ -30,44 +31,27 @@ type StoryPreviewProps = { export const StoryPreview: React.FC<StoryPreviewProps> = ({ mediaType, - file, hyperLink, + width = '100%', + height = '100%', onPlay, onPause, - onComplete, avatar, title, duration = 5000, isOfficial = true, + imageMode, }) => { const [isPlaying, setIsPlaying] = useState(true); const [progress, setProgress] = useState(0); + const [backgroundColor, setBackgroundColor] = useState<string[]>([]); const videoRef = useRef<HTMLVideoElement>(null); const imageRef = useRef<HTMLImageElement>(null); + const [isLoading, setIsLoading] = useState(true); + const [isImageLoaded, setIsImageLoaded] = useState(false); let progressInterval: NodeJS.Timeout; - useEffect(() => { - if (isPlaying) { - progressInterval = setInterval(() => { - setProgress((prevProgress) => { - if (prevProgress >= 100) { - clearInterval(progressInterval); - handleMediaComplete(); - return 100; - } - return prevProgress + 1; - }); - }, duration / 100); - } else { - clearInterval(progressInterval); - } - - return () => { - clearInterval(progressInterval); - }; - }, [isPlaying, duration]); - const handleMediaPlay = () => { setIsPlaying(true); if (onPlay) { @@ -88,74 +72,189 @@ export const StoryPreview: React.FC<StoryPreviewProps> = ({ videoRef.current.play(); } else if (mediaType?.type === 'image') { setIsPlaying(false); - if (onComplete) { - onComplete(); - } + } + }; + + const handleVideoEnded = () => { + if (mediaType?.type === 'video' && videoRef.current) { + videoRef.current.currentTime = 0; + videoRef.current.play(); + setProgress(0); } }; const handlePlayPauseClick = () => { if (isPlaying) { handleMediaPause(); + if (mediaType?.type === 'video' && videoRef.current) { + videoRef.current.pause(); + } } else { handleMediaPlay(); + if (mediaType?.type === 'video' && videoRef.current) { + videoRef.current.play(); + } } }; + useEffect(() => { + const extractColorsFromImage = async (url: string) => { + const img = new Image(); + img.crossOrigin = 'Anonymous'; + img.src = url; + + img.onload = () => { + const colorThief = new ColorThief(); + const palette = colorThief.getPalette(img, 2); + if (palette) { + const colors = palette.map((color) => `rgb(${color[0]}, ${color[1]}, ${color[2]})`); + setBackgroundColor(colors); + setIsLoading(false); + } + }; + + img.onerror = () => { + console.error('Error loading image for color extraction'); + setBackgroundColor([]); + setIsLoading(false); + }; + }; + + if (mediaType?.url) { + extractColorsFromImage(mediaType.url); + } else { + setBackgroundColor([]); + setIsLoading(false); + } + + return () => { + setBackgroundColor([]); + setIsLoading(true); + }; + }, [mediaType?.url]); + + useEffect(() => { + if (isPlaying) { + progressInterval = setInterval(() => { + if (mediaType?.type === 'video' && videoRef.current) { + const currentTime = videoRef.current.currentTime; + const duration = videoRef.current.duration; + const progress = (currentTime / duration) * 100; + setProgress(progress); + + if (progress >= 100) { + clearInterval(progressInterval); + handleMediaComplete(); + } + } else if (mediaType?.type === 'image' && isImageLoaded) { + setProgress((prevProgress) => { + if (prevProgress >= 100) { + clearInterval(progressInterval); + handleMediaComplete(); + return 100; + } + return prevProgress + 100 / (duration / 100); + }); + } + }, 100); + } else { + clearInterval(progressInterval); + } + + return () => { + clearInterval(progressInterval); + }; + }, [isPlaying, mediaType, duration, isImageLoaded]); + return ( - <div className={styles.storyPreviewContainer}> - <div className={styles.headerContainer}> - <div className={styles.progressBar}> - <div className={styles.progressFill} style={{ width: `${progress}%` }} /> - </div> - <div className={styles.userInfo}> - <div className={styles.avatar}> - <Avatar avatarUrl={avatar} defaultImage={<Community />} /> + <div className={styles.storyPreviewWrapper} style={{ width, height }}> + <div className={styles.storyPreviewContainer}> + <div className={styles.headerContainer}> + <div className={styles.progressBar}> + <div className={styles.progressFill} style={{ width: `${progress}%` }} /> + </div> + <div className={styles.headerContent}> + <div className={styles.userInfo}> + <div className={styles.userInfoContent}> + <div className={styles.avatarContainer}> + <Avatar avatarUrl={avatar} defaultImage={<Community />} /> + </div> + <Typography.BodyBold className={styles.storyPreviewTitle}> + <span className={styles.nameContainer}> + {title} {isOfficial && <VerifiedIcon fill="white" />} + </span> + </Typography.BodyBold> + </div> + {mediaType?.type === 'video' && ( + <div className={styles.playPauseButton} onClick={handlePlayPauseClick}> + {isPlaying ? ( + <PauseIcon width={24} height={24} /> + ) : ( + <PlayIcon width={24} height={24} /> + )} + </div> + )} + </div> </div> - <Typography.BodyBold className={styles.storyPreviewTitle}> - <span className={styles.nameContainer}> - {title} {isOfficial && <Verified fill="white" />} - </span> - </Typography.BodyBold> </div> - </div> - <div className={styles.hyperLinkContainer}> - {hyperLink[0]?.data?.url && ( - <HyperLink - href={ - hyperLink[0].data.url.startsWith('http') - ? hyperLink[0].data.url - : `https://${hyperLink[0].data.url}` - } - target="_blank" - rel="noopener noreferrer" + <div className={styles.contentWrapper}> + <div + className={styles.mediaContainer} + style={{ + background: `linear-gradient( + 180deg, + ${backgroundColor[0] || '#000'} 0%, + ${backgroundColor[1] || '#000'} 100% + )`, + }} > - <TruncateMarkup lines={1}> - <span>{hyperLink[0]?.data?.customText || hyperLink[0].data.url}</span> - </TruncateMarkup> - </HyperLink> - )} - </div> + {mediaType?.type === 'video' ? ( + <video + ref={videoRef} + src={mediaType.url} + className={styles.media} + loop + autoPlay + muted + onEnded={handleVideoEnded} + /> + ) : ( + <> + {!isLoading && backgroundColor.length > 0 && ( + <img + ref={imageRef} + src={mediaType?.url} + alt="Story" + onLoad={() => setIsImageLoaded(true)} + className={clsx(styles.media, { + [styles.imageFit]: imageMode === 'fit', + [styles.imageCover]: imageMode === 'fill', + })} + /> + )} + </> + )} + </div> - <div className={styles.mediaContainer}> - {mediaType?.type === 'video' ? ( - <video - ref={videoRef} - src={file ? URL.createObjectURL(file) : mediaType.url} - className={styles.media} - loop - autoPlay - muted - /> - ) : ( - <img - ref={imageRef} - src={file ? URL.createObjectURL(file) : mediaType?.url} - className={styles.media} - alt="Story" - /> - )} + <div className={styles.hyperLinkContainer}> + {hyperLink[0]?.data?.url && ( + <HyperLink + href={ + hyperLink[0].data.url.startsWith('http') + ? hyperLink[0].data.url + : `https://${hyperLink[0].data.url}` + } + target="_blank" + rel="noopener noreferrer" + > + <TruncateMarkup lines={1}> + <span>{hyperLink[0]?.data?.customText || hyperLink[0].data.url}</span> + </TruncateMarkup> + </HyperLink> + )} + </div> + </div> </div> </div> ); diff --git a/src/v4/social/internal-components/StoryPreview/StoryPreviewThumbnail/StoryPreviewThumbnail.module.css b/src/v4/social/internal-components/StoryPreview/StoryPreviewThumbnail/StoryPreviewThumbnail.module.css new file mode 100644 index 000000000..e434a47e0 --- /dev/null +++ b/src/v4/social/internal-components/StoryPreview/StoryPreviewThumbnail/StoryPreviewThumbnail.module.css @@ -0,0 +1,166 @@ +.containerWrapper { + position: relative; + width: 7.4rem; + height: 13rem; +} + +.storyPreviewAvatar { + width: 0.7951rem; + height: 0.7951rem; + flex-shrink: 0; +} + +.storyPreviewContainer { + position: relative; + width: 100%; + height: 100%; + overflow: hidden; +} + +.thumbnailMedia { + width: 100%; + height: 100%; +} + +.thumbnailMedia img { + width: 100%; + height: 100%; + object-fit: cover; + transition: transform 0.3s ease-in-out; +} + +.storyPreviewContainer:hover .thumbnailMedia img { + transform: scale(1.1); + cursor: pointer; +} + +.thumbnailInfo { + position: absolute; + top: 0; + left: 0; + right: 0; + padding: 0.5rem; +} + +.userInfo { + display: flex; + align-items: center; + gap: 0.25rem; + margin-bottom: 0.25rem; +} + +.storyPreviewTitle { + color: #ebecef; + font-size: 0.25rem; +} + +.nameContainer { + display: flex; + align-items: center; +} + +.hyperLinkContainer { + position: absolute; + bottom: 1rem; + left: 50%; + transform: translateX(-50%); + display: flex; + justify-content: center; + align-items: center; + max-width: 3rem; +} + +.hyperlink__icon { + width: 0.5rem; + height: 0.5rem; + color: #1054de; +} + +.hyperlink { + gap: 0.125rem; + display: inline-flex; + color: var(--asc-color-black); + text-decoration: none; + padding: 0.1988rem 0.3181rem 0.1988rem 0.2386rem; + font-size: 0.5rem; + text-align: center; + align-items: center; + border-radius: 1.5rem; + border: 0.0625rem solid #ebecef; + background: rgb(255 255 255 / 80%); +} + +.storyPreviewContainer::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgb(0 0 0 / 60%); + opacity: 0; + transition: opacity 0.3s ease-in-out; +} + +.storyPreviewContainer::after { + content: 'View preview'; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: #ebecef; + font-size: 0.75rem; + font-weight: 400; + text-align: center; + opacity: 0; + transition: opacity 0.3s ease-in-out; + line-height: 1rem; + letter-spacing: -0.0063rem; +} + +.storyPreviewContainer:hover::after { + width: 100%; + height: 100%; + opacity: 1; + z-index: 9999; + cursor: pointer; + background-color: rgb(0 0 0 / 80%); + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; +} + +.thumbnailImage { + width: 100%; + height: 100%; +} + +.imageFit { + object-fit: contain; +} + +.imageCover { + object-fit: cover; +} + +.hyperlinkPosition { + position: absolute; + bottom: 1rem; + left: 50%; + transform: translateX(-50%); +} + +.hyperlinkIcon { + width: 0.75rem; + height: 0.75rem; + color: #1054de; + margin-right: 0.13rem; +} + +.hyperlinkText { + max-width: 4rem; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} diff --git a/src/v4/social/internal-components/StoryPreview/StoryPreviewThumbnail/StoryPreviewThumbnail.tsx b/src/v4/social/internal-components/StoryPreview/StoryPreviewThumbnail/StoryPreviewThumbnail.tsx new file mode 100644 index 000000000..8226e95c1 --- /dev/null +++ b/src/v4/social/internal-components/StoryPreview/StoryPreviewThumbnail/StoryPreviewThumbnail.tsx @@ -0,0 +1,122 @@ +import React, { useEffect, useState, useRef } from 'react'; +import clsx from 'clsx'; +import ColorThief from 'colorthief'; +import { LinkIcon, VerifiedIcon } from '~/v4/social/icons'; +import { Avatar, Typography } from '~/v4/core/components'; +import TruncateMarkup from 'react-truncate-markup'; +import styles from './StoryPreviewThumbnail.module.css'; +import Community from '~/v4/icons/Community'; + +type StoryPreviewThumbnailProps = { + thumbnailUrl?: string; + hyperLink: { + data: { url: string; customText: string }; + type: Amity.StoryItemType; + }[]; + avatar: string; + title: string; + isOfficial?: boolean; + onClick?: () => void; + imageMode?: 'fit' | 'fill'; +}; + +export const StoryPreviewThumbnail: React.FC<StoryPreviewThumbnailProps> = ({ + thumbnailUrl, + hyperLink, + avatar, + title, + isOfficial = false, + onClick, + imageMode = 'fill', +}) => { + const [backgroundColor, setBackgroundColor] = useState<string>(''); + const imageRef = useRef<HTMLImageElement>(null); + + useEffect(() => { + const extractColorsFromImage = async () => { + if (imageRef.current && imageRef.current.complete) { + const colorThief = new ColorThief(); + try { + const palette = colorThief.getPalette(imageRef.current, 2); + if (palette) { + const gradient = `linear-gradient( + 180deg, + rgb(${palette[0].join(',')}) 0%, + rgb(${palette[1].join(',')}) 100% + )`; + setBackgroundColor(gradient); + } + } catch (error) { + console.error('Error extracting colors:', error); + setBackgroundColor(''); + } + } + }; + + if (thumbnailUrl) { + if (imageRef.current && imageRef.current.complete) { + extractColorsFromImage(); + } else { + imageRef.current?.addEventListener('load', extractColorsFromImage); + } + } else { + setBackgroundColor(''); + } + + return () => { + imageRef.current?.removeEventListener('load', extractColorsFromImage); + }; + }, [thumbnailUrl]); + + return ( + <div + className={styles.containerWrapper} + onClick={onClick} + style={{ + background: backgroundColor || '#000', + }} + > + <div className={styles.storyPreviewContainer}> + <div className={styles.thumbnailMedia}> + <img + ref={imageRef} + src={thumbnailUrl} + alt="Story Thumbnail" + className={clsx(styles.thumbnailImage, { + [styles.imageFit]: imageMode === 'fit', + [styles.imageCover]: imageMode === 'fill', + })} + crossOrigin="anonymous" + /> + </div> + <div className={styles.thumbnailInfo}> + <div className={styles.userInfo}> + <div className={styles.storyPreviewAvatar}> + <Avatar avatarUrl={avatar} defaultImage={<Community />} /> + </div> + <Typography.BodyBold className={styles.storyPreviewTitle}> + <span className={styles.nameContainer}> + <TruncateMarkup lines={1}> + <span>{title}</span> + </TruncateMarkup>{' '} + {isOfficial && <VerifiedIcon width={12} height={12} fill="white" />} + </span> + </Typography.BodyBold> + </div> + </div> + + {hyperLink[0]?.data?.url && ( + <a + href={hyperLink[0].data.url} + className={clsx(styles.hyperlink, styles.hyperlinkPosition)} + > + <LinkIcon className={styles.hyperlinkIcon} /> + <span className={styles.hyperlinkText}> + {hyperLink[0]?.data?.customText || hyperLink[0].data.url} + </span> + </a> + )} + </div> + </div> + ); +}; diff --git a/src/v4/social/internal-components/StoryPreview/StoryPreviewThumbnail/ui.stories.tsx b/src/v4/social/internal-components/StoryPreview/StoryPreviewThumbnail/ui.stories.tsx new file mode 100644 index 000000000..bb9001893 --- /dev/null +++ b/src/v4/social/internal-components/StoryPreview/StoryPreviewThumbnail/ui.stories.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { ComponentStory, ComponentMeta } from '@storybook/react'; +import { StoryPreviewThumbnail } from './StoryPreviewThumbnail'; + +export default { + title: 'v4/Social/Story Preview Thumbnail', + component: StoryPreviewThumbnail, +} as ComponentMeta<typeof StoryPreviewThumbnail>; + +const Template: ComponentStory<typeof StoryPreviewThumbnail> = (args) => ( + <StoryPreviewThumbnail {...args} /> +); + +export const Default = Template.bind({}); +Default.args = { + thumbnailUrl: 'https://picsum.photos/400/600', + hyperLink: [ + { + data: { + url: 'https://example.com', + customText: 'Visit our website', + }, + type: 'hyperlink' as Amity.StoryItemType, + }, + ], + avatar: 'https://picsum.photos/400/600', + title: 'John Doe', + isOfficial: true, + imageMode: 'fit', +}; + +export const LongTitle = Template.bind({}); +LongTitle.args = { + thumbnailUrl: 'https://picsum.photos/400/600', + hyperLink: [ + { + data: { + url: 'https://example.com', + customText: 'Visit our website', + }, + type: 'hyperlink' as Amity.StoryItemType, + }, + ], + avatar: 'https://picsum.photos/400/600', + title: 'John Doe with a very long name that exceeds the available space', + isOfficial: true, +}; + +export const NoHyperlink = Template.bind({}); +NoHyperlink.args = { + thumbnailUrl: 'https://picsum.photos/400/600', + hyperLink: [], + avatar: 'https://picsum.photos/400/600', + title: 'John Doe', + isOfficial: false, +}; diff --git a/src/v4/social/internal-components/StoryPreview/index.ts b/src/v4/social/internal-components/StoryPreview/index.ts index f1a01cb5e..fa6683c90 100644 --- a/src/v4/social/internal-components/StoryPreview/index.ts +++ b/src/v4/social/internal-components/StoryPreview/index.ts @@ -1 +1,2 @@ export { StoryPreview } from './StoryPreview'; +export { StoryPreviewThumbnail } from './StoryPreviewThumbnail/StoryPreviewThumbnail'; diff --git a/src/v4/social/internal-components/StoryPreview/ui.stories.tsx b/src/v4/social/internal-components/StoryPreview/ui.stories.tsx index 4d7d7527a..ac28f7a49 100644 --- a/src/v4/social/internal-components/StoryPreview/ui.stories.tsx +++ b/src/v4/social/internal-components/StoryPreview/ui.stories.tsx @@ -12,7 +12,6 @@ const Template: ComponentStory<typeof StoryPreview> = (args) => ( style={{ width: '23.4375rem', height: '40.875rem', - flexShrink: 0, }} > <StoryPreview {...args} /> @@ -21,8 +20,7 @@ const Template: ComponentStory<typeof StoryPreview> = (args) => ( export const ImageStory = Template.bind({}); ImageStory.args = { - mediaType: { type: 'image', url: 'https://picsum.photos/400/600' }, - file: null, + mediaType: { type: 'image', url: 'https://picsum.photos/400/400' }, imageMode: 'fit', hyperLink: [], avatar: 'https://picsum.photos/120/120', @@ -32,9 +30,10 @@ ImageStory.args = { export const StoryWithHyperlink = Template.bind({}); StoryWithHyperlink.args = { - mediaType: { type: 'image', url: 'https://picsum.photos/400/600' }, - file: null, - imageMode: 'fit', + mediaType: { + type: 'video', + url: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4', + }, hyperLink: [ { data: { url: 'https://example.com', customText: 'Visit Website' }, From 8d9b0113ebf3ac06e52d62c4c411024b5ee0f827 Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Wed, 3 Jul 2024 10:44:17 +0700 Subject: [PATCH 195/300] fix: ASC-23583 - discard post modal (#483) * style: modal * style: modal --- .../components/ConfirmModal/styles.module.css | 8 +- src/v4/core/components/Modal/index.tsx | 10 +- .../core/components/Modal/styles.module.css | 51 +++++++---- .../PostComposerPage/PostComposerPage.tsx | 91 +++++++++++-------- 4 files changed, 96 insertions(+), 64 deletions(-) diff --git a/src/v4/core/components/ConfirmModal/styles.module.css b/src/v4/core/components/ConfirmModal/styles.module.css index da6c634b9..f175e0b3b 100644 --- a/src/v4/core/components/ConfirmModal/styles.module.css +++ b/src/v4/core/components/ConfirmModal/styles.module.css @@ -1,5 +1,5 @@ .modal { - max-width: 22.5rem !important; + max-width: 22.5rem; } .background { @@ -12,15 +12,15 @@ } .okButton { - background: var(--asc-color-alert) !important; + background: var(--asc-color-alert); border: none; } .cancelButton { margin-right: var(--asc-spacing-s1); - background: transparent !important; + background: transparent; color: var(--asc-color-secondary-default); - border: 1px solid var(--asc-color-secondary-default) !important; + border: 1px solid var(--asc-color-base-shade1); } .cancelButton:hover { diff --git a/src/v4/core/components/Modal/index.tsx b/src/v4/core/components/Modal/index.tsx index 938359bc8..5bdc39f6c 100644 --- a/src/v4/core/components/Modal/index.tsx +++ b/src/v4/core/components/Modal/index.tsx @@ -40,17 +40,13 @@ const Modal = ({ return ( <div className={styles.overlay} onClick={onOverlayClick} style={themeStyles}> <div - className={clsx(styles.modalWindow, `${size === 'small' ? 'smallModalWindow' : ''}`)} + className={clsx(styles.modalWindow)} data-qa-anchor={accessibilityId} ref={modalRef} tabIndex={0} > - {(title || onCancel) && ( - <div className={styles.header}> - {title} - {onCancel && <Close className={styles.closeIcon} onClick={onCancel} />} - </div> - )} + {onCancel && <Close className={styles.closeIcon} onClick={onCancel} />} + {title && <div className={styles.title}>{title}</div>} <div className={clsx(styles.content)}>{children}</div> {footer && <div className={styles.footer}>{footer}</div>} diff --git a/src/v4/core/components/Modal/styles.module.css b/src/v4/core/components/Modal/styles.module.css index 12714ffd1..b4cc637a3 100644 --- a/src/v4/core/components/Modal/styles.module.css +++ b/src/v4/core/components/Modal/styles.module.css @@ -1,9 +1,12 @@ .closeIcon { - width: 1.125rem; - height: 1.125rem; + display: flex; + justify-content: flex-end; + width: 1.5rem; + height: 1.5rem; padding: 0 0.375rem; cursor: pointer; margin-left: auto; + color: var(--asc-color-base-shade3); &.svg-inline--fa { width: auto; @@ -21,7 +24,6 @@ } .overlay { - z-index: 9999; position: fixed; inset: 0; overflow-y: auto; @@ -38,9 +40,8 @@ border-radius: var(--asc-border-radius-lg); max-width: 32.5rem; min-width: 20rem; - - /* TOFIX --asc-color-base-background is not defined some how */ - background-color: var(--asc-color-white); + padding: 1.5rem; + background-color: var(--asc-color-base-background); } .modalWindow:focus { @@ -48,25 +49,41 @@ } .smallModalWindow { - width: 27.5rem !important; + width: 16.8rem; } -.header { - padding: var(--asc-spacing-m1) var(--asc-spacing-m1) 0 var(--asc-spacing-m1); +.title { display: flex; align-items: center; - color: var(--asc-color-base-inverse); - border-bottom: 1px solid var(--theme-palette-base-shade4); - font-size: var(--asc-text-font-size-lg) !important; - font-weight: var(--asc-text-font-weight-bold); + margin-top: 0.5rem; + color: var(--asc-color-base-default); + font-size: 1.25rem; + font-weight: 600; } .content { - color: var(--asc-color-base-default); - padding: var(--asc-spacing-m2) var(--asc-spacing-m1); + margin-top: 0.5rem; + line-height: 1.25rem; + font-size: 0.875rem; + color: var(--asc-color-base-shade1); } .footer { - padding: var(--asc-spacing-m2) var(--asc-spacing-s2); - padding-top: var(--asc-spacing-xxs2); + margin-top: 0.5rem; + padding-top: 1rem; +} + +@media (width <= 768px) { + .modalWindow { + max-width: 16.8rem; + } + + .title { + font-size: 1rem; + } + + .footer { + display: flex; + justify-content: center; + } } diff --git a/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx b/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx index 670423ae0..d1af186e9 100644 --- a/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx +++ b/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx @@ -19,6 +19,7 @@ import { Drawer } from 'vaul'; import { MediaAttachment } from '../../components/MediaAttachment'; import { DetailedMediaAttachment } from '../../components/DetailedMediaAttachment'; import ReactDOM from 'react-dom'; +import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; export enum Mode { CREATE = 'create', @@ -90,6 +91,7 @@ const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCr const [snap, setSnap] = useState<number | string | null>(HEIGHT_MEDIA_ATTACHMENT_MENU); const [isShowBottomMenu, setIsShowBottomMenu] = useState<boolean>(true); const drawerRef = useRef<HTMLDivElement>(null); + const { confirm } = useConfirmContext(); const [textValue, setTextValue] = useState<createPostParams>({ text: '', @@ -141,6 +143,20 @@ const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCr setSnap(newSnap); }; + const onClickClose = () => { + confirm({ + pageId: pageId, + type: 'confirm', + title: 'Discard this post?', + content: 'The post will be permanently deleted. It cannot be undone.', + onOk: () => { + AmityPostComposerPageBehavior.goToSocialHomePage(); + }, + okText: 'Discard', + cancelText: 'Keep editing', + }); + }; + return ( <div className={styles.postComposerPage} style={themeStyles}> <form @@ -152,7 +168,7 @@ const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCr } > <div className={styles.postComposerPage__topBar}> - <CloseButton pageId={pageId} onPress={onBack} /> + <CloseButton pageId={pageId} onPress={onClickClose} /> <CommunityDisplayName pageId={pageId} community={community} /> <CreateNewPostButton pageId={pageId} @@ -163,41 +179,44 @@ const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCr <PostTextField ref={editorRef} onChange={onChange} /> </form> <div ref={drawerRef}></div> - {drawerRef.current ? ReactDOM.createPortal( - <Drawer.Root - snapPoints={[HEIGHT_MEDIA_ATTACHMENT_MENU, HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU]} - activeSnapPoint={snap} - setActiveSnapPoint={handleSnapChange} - open={isShowBottomMenu} - modal={false} - > - <Drawer.Portal container={drawerRef.current}> - <Drawer.Content className={styles.drawer__content}> - <div className={styles.postComposerPage__notiWrap}> - {isPending && ( - <Notification - content="Posting..." - icon={<Spinner />} - className={styles.postComposerPage__status} - /> - )} - {isError && ( - <Notification - content="Failed to create post" - icon={<ExclamationCircle className={styles.selectPostTargetPag_infoIcon} />} - className={styles.postComposerPage__status} - /> - )} - </div> - {snap == HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU ? ( - <DetailedMediaAttachment pageId={pageId} /> - ) : ( - <MediaAttachment pageId={pageId} /> - )} - </Drawer.Content> - </Drawer.Portal> - </Drawer.Root>, drawerRef.current - ) : null } + {drawerRef.current + ? ReactDOM.createPortal( + <Drawer.Root + snapPoints={[HEIGHT_MEDIA_ATTACHMENT_MENU, HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU]} + activeSnapPoint={snap} + setActiveSnapPoint={handleSnapChange} + open={isShowBottomMenu} + modal={false} + > + <Drawer.Portal container={drawerRef.current}> + <Drawer.Content className={styles.drawer__content}> + <div className={styles.postComposerPage__notiWrap}> + {isPending && ( + <Notification + content="Posting..." + icon={<Spinner />} + className={styles.postComposerPage__status} + /> + )} + {isError && ( + <Notification + content="Failed to create post" + icon={<ExclamationCircle className={styles.selectPostTargetPag_infoIcon} />} + className={styles.postComposerPage__status} + /> + )} + </div> + {snap == HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU ? ( + <DetailedMediaAttachment pageId={pageId} /> + ) : ( + <MediaAttachment pageId={pageId} /> + )} + </Drawer.Content> + </Drawer.Portal> + </Drawer.Root>, + drawerRef.current, + ) + : null} </div> ); }; From cef079614b984ac174253d2180f8fcdecdc87d07 Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Wed, 3 Jul 2024 10:52:19 +0700 Subject: [PATCH 196/300] fix: ASC-23600 - create post params (#475) * fix: push userIds * fix: prevent empty mentionees --- src/v4/social/elements/PostTextField/PostTextField.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/v4/social/elements/PostTextField/PostTextField.tsx b/src/v4/social/elements/PostTextField/PostTextField.tsx index addcaa190..a34ff5f46 100644 --- a/src/v4/social/elements/PostTextField/PostTextField.tsx +++ b/src/v4/social/elements/PostTextField/PostTextField.tsx @@ -44,7 +44,7 @@ function editorStateToText(editor: LexicalEditor) { const mentionees: { type: string; userIds: string[]; - }[] = []; + }[] = [{ type: 'user', userIds: [] }]; let runningIndex = 0; paragraphs.forEach((paragraph) => { @@ -63,7 +63,7 @@ function editorStateToText(editor: LexicalEditor) { userId: child.userId, }); - mentionees.push({ type: 'user', userIds: [child.userId] }); + mentionees.length && mentionees[0].userIds.push(child.userId); } runningIndex += child.text.length; }); From 4f83068269e41511d2d1577410abbb65c2257b6f Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Wed, 3 Jul 2024 11:21:23 +0700 Subject: [PATCH 197/300] fix: ASC-23590 - add toast duration (#479) * fix: add toast duration * feat: prevent click when loading * feat: check offline post * refactor: remove comment code * feat: import isOnline hooks * refactor: remove log --- src/v4/core/components/Notification/index.tsx | 27 ++++-- src/v4/social/hooks/useConnectionState.ts | 31 +++++++ .../PostComposerPage/PostComposerPage.tsx | 87 +++++-------------- 3 files changed, 72 insertions(+), 73 deletions(-) create mode 100644 src/v4/social/hooks/useConnectionState.ts diff --git a/src/v4/core/components/Notification/index.tsx b/src/v4/core/components/Notification/index.tsx index 4a23570c2..d0a8b12d4 100644 --- a/src/v4/core/components/Notification/index.tsx +++ b/src/v4/core/components/Notification/index.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode } from 'react'; +import React, { ReactNode, useState } from 'react'; import clsx from 'clsx'; import styles from './Notification.module.css'; import { useNotificationData } from '~/v4/core/providers/NotificationProvider'; @@ -7,13 +7,28 @@ interface NotificationProps { className?: string; content: ReactNode; icon?: ReactNode; + duration?: number; } -export const Notification = ({ className, content, icon }: NotificationProps) => ( - <div className={clsx(styles.notificationContainer, className)}> - <div className={clsx(styles.icon__container)}>{icon}</div> {content} - </div> -); +export const Notification = ({ className, content, icon, duration }: NotificationProps) => { + const [isVisible, setIsVisible] = useState(true); + + if (duration) { + setTimeout(() => { + setIsVisible(false); + }, duration); + } + + if (!isVisible) return null; + + return ( + isVisible && ( + <div className={clsx(styles.notificationContainer, className)}> + <div className={clsx(styles.icon__container)}>{icon}</div> {content} + </div> + ) + ); +}; // rendered by provider, to allow spawning of notification from notification function below export const NotificationsContainer = () => { diff --git a/src/v4/social/hooks/useConnectionState.ts b/src/v4/social/hooks/useConnectionState.ts new file mode 100644 index 000000000..77db4d12f --- /dev/null +++ b/src/v4/social/hooks/useConnectionState.ts @@ -0,0 +1,31 @@ +import { useEffect, useState } from 'react'; +import { Client as ASCClient } from '@amityco/ts-sdk'; + +const useConnectionState = (initialStatus = undefined) => { + const [isOnline, setIsOnline] = useState( + initialStatus === undefined ? window.navigator.onLine ?? ASCClient.isConnected : initialStatus, + ); + + // Monitor with native connection status from browser, + // because SDK is a bit delay to update session state sometime connection is disconnect already, + // but session still struck in established state + useEffect(() => { + window.addEventListener('online', () => setIsOnline(true)); + return window.removeEventListener('online', () => setIsOnline(true)); + }, []); + + useEffect(() => { + window.addEventListener('offline', () => setIsOnline(false)); + return window.removeEventListener('offline', () => setIsOnline(false)); + }, []); + + useEffect(() => { + return ASCClient.onSessionStateChange(() => { + setIsOnline(ASCClient.isConnected()); + }); + }, []); + + return isOnline; +}; + +export default useConnectionState; diff --git a/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx b/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx index d1af186e9..40829ff6f 100644 --- a/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx +++ b/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useRef, useState } from 'react'; import styles from './PostComposerPage.module.css'; import { useAmityPage } from '~/v4/core/hooks/uikit'; import { CloseButton } from '~/v4/social/elements/CloseButton/CloseButton'; @@ -15,11 +15,8 @@ import { Spinner } from '../../internal-components/Spinner'; import ExclamationCircle from '~/v4/icons/ExclamationCircle'; import { useForm } from 'react-hook-form'; import { Mentioned } from '~/v4/helpers/utils'; -import { Drawer } from 'vaul'; -import { MediaAttachment } from '../../components/MediaAttachment'; -import { DetailedMediaAttachment } from '../../components/DetailedMediaAttachment'; -import ReactDOM from 'react-dom'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; +import useConnectionState from '~/v4/social/hooks/useConnectionState'; export enum Mode { CREATE = 'create', @@ -81,17 +78,11 @@ const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCr pageId, }); - // const HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU = '290px'; //Including file button - const HEIGHT_MEDIA_ATTACHMENT_MENU = '92px'; - const HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU = '236px'; //Not including file button - const { onBack } = useNavigation(); const editorRef = useRef<LexicalEditor | null>(null); const { AmityPostComposerPageBehavior } = usePageBehavior(); - const [snap, setSnap] = useState<number | string | null>(HEIGHT_MEDIA_ATTACHMENT_MENU); - const [isShowBottomMenu, setIsShowBottomMenu] = useState<boolean>(true); - const drawerRef = useRef<HTMLDivElement>(null); const { confirm } = useConfirmContext(); + const isOnline = useConnectionState(); const [textValue, setTextValue] = useState<createPostParams>({ text: '', @@ -136,13 +127,6 @@ const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCr setTextValue(val); }; - const handleSnapChange = (newSnap: string | number | null) => { - if (snap === HEIGHT_MEDIA_ATTACHMENT_MENU && newSnap === '0px') { - return; - } - setSnap(newSnap); - }; - const onClickClose = () => { confirm({ pageId: pageId, @@ -159,64 +143,33 @@ const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCr return ( <div className={styles.postComposerPage} style={themeStyles}> - <form - onSubmit={handleSubmit(onSubmit)} - className={ - snap == HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU - ? styles.postComposerPage__formShort - : styles.postComposerPage__formLong - } - > + <form onSubmit={handleSubmit(onSubmit)}> <div className={styles.postComposerPage__topBar}> <CloseButton pageId={pageId} onPress={onClickClose} /> <CommunityDisplayName pageId={pageId} community={community} /> <CreateNewPostButton pageId={pageId} onSubmit={handleSubmit(onSubmit)} - isValid={textValue.text.length > 0} + isValid={textValue.text.length > 0 && !isPending} /> </div> <PostTextField ref={editorRef} onChange={onChange} /> </form> - <div ref={drawerRef}></div> - {drawerRef.current - ? ReactDOM.createPortal( - <Drawer.Root - snapPoints={[HEIGHT_MEDIA_ATTACHMENT_MENU, HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU]} - activeSnapPoint={snap} - setActiveSnapPoint={handleSnapChange} - open={isShowBottomMenu} - modal={false} - > - <Drawer.Portal container={drawerRef.current}> - <Drawer.Content className={styles.drawer__content}> - <div className={styles.postComposerPage__notiWrap}> - {isPending && ( - <Notification - content="Posting..." - icon={<Spinner />} - className={styles.postComposerPage__status} - /> - )} - {isError && ( - <Notification - content="Failed to create post" - icon={<ExclamationCircle className={styles.selectPostTargetPag_infoIcon} />} - className={styles.postComposerPage__status} - /> - )} - </div> - {snap == HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU ? ( - <DetailedMediaAttachment pageId={pageId} /> - ) : ( - <MediaAttachment pageId={pageId} /> - )} - </Drawer.Content> - </Drawer.Portal> - </Drawer.Root>, - drawerRef.current, - ) - : null} + {isPending && isOnline && ( + <Notification + content="Posting..." + icon={<Spinner />} + className={styles.postComposerPage__status} + /> + )} + {(isError || (!isOnline && isPending)) && ( + <Notification + content="Failed to create post" + icon={<ExclamationCircle className={styles.selectPostTargetPag_infoIcon} />} + className={styles.postComposerPage__status} + duration={3000} + /> + )} </div> ); }; From 3121dbfd0ea93cb2ea7c02c9759b65fdb6d1629d Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Wed, 3 Jul 2024 11:25:21 +0700 Subject: [PATCH 198/300] fix: ASC-23599 - mention member in private community (#468) * feat: member mention * refactor: import * fix: query params * refactor: rename component * fix: undefined type * fix: shouldCall --- .../elements/PostTextField/PostTextField.tsx | 47 ++-- .../collections/useCommunitiesCollection.ts | 2 +- .../hooks/useMemberQueryByDisplayName.ts | 66 +++++ .../social/hooks/useUserQueryByDisplayName.ts | 2 +- .../MentionTextInput/MentionTextInput.tsx | 225 ++++++++---------- .../PostComposerPage/PostComposerPage.tsx | 2 +- 6 files changed, 199 insertions(+), 145 deletions(-) create mode 100644 src/v4/social/hooks/useMemberQueryByDisplayName.ts diff --git a/src/v4/social/elements/PostTextField/PostTextField.tsx b/src/v4/social/elements/PostTextField/PostTextField.tsx index a34ff5f46..f3f27b4f4 100644 --- a/src/v4/social/elements/PostTextField/PostTextField.tsx +++ b/src/v4/social/elements/PostTextField/PostTextField.tsx @@ -8,7 +8,7 @@ import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary'; import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'; import { AutoFocusPlugin } from '@lexical/react/LexicalAutoFocusPlugin'; import { MentionNode } from '~/v4/social/internal-components/MentionTextInput/MentionNodes'; -import { MentionTextInput } from '~/v4/social/internal-components/MentionTextInput/MentionTextInput'; +import { MentionTextInputPlugin } from '~/v4/social/internal-components/MentionTextInput/MentionTextInput'; import { MetaData, createPostParams } from '~/v4/social/pages/PostComposerPage/PostComposerPage'; import styles from './PostTextField.module.css'; @@ -34,6 +34,7 @@ interface EditorStateJson extends SerializedLexicalNode { interface PostTextFieldProps { onChange: (data: createPostParams) => void; + communityId?: string | null; } function editorStateToText(editor: LexicalEditor) { @@ -73,24 +74,26 @@ function editorStateToText(editor: LexicalEditor) { return { mentioned, text: editorStateTextString.join('\n'), mentionees }; } -export const PostTextField = forwardRef<LexicalEditor, PostTextFieldProps>(({ onChange }) => { - return ( - <LexicalComposer initialConfig={editorConfig}> - <div className={styles.editorContainer}> - <RichTextPlugin - contentEditable={<ContentEditable />} - placeholder={<div className={styles.editorPlaceholder}>What’s going on...</div>} - ErrorBoundary={LexicalErrorBoundary} - /> - <OnChangePlugin - onChange={(editorState, editor) => { - onChange(editorStateToText(editor)); - }} - /> - <HistoryPlugin /> - <AutoFocusPlugin /> - <MentionTextInput /> - </div> - </LexicalComposer> - ); -}); +export const PostTextField = forwardRef<LexicalEditor, PostTextFieldProps>( + ({ onChange, communityId }) => { + return ( + <LexicalComposer initialConfig={editorConfig}> + <div className={styles.editorContainer}> + <RichTextPlugin + contentEditable={<ContentEditable />} + placeholder={<div className={styles.editorPlaceholder}>What’s going on...</div>} + ErrorBoundary={LexicalErrorBoundary} + /> + <OnChangePlugin + onChange={(editorState, editor) => { + onChange(editorStateToText(editor)); + }} + /> + <HistoryPlugin /> + <AutoFocusPlugin /> + <MentionTextInputPlugin communityId={communityId} /> + </div> + </LexicalComposer> + ); + }, +); diff --git a/src/v4/social/hooks/collections/useCommunitiesCollection.ts b/src/v4/social/hooks/collections/useCommunitiesCollection.ts index 8d3765f6c..878f37189 100644 --- a/src/v4/social/hooks/collections/useCommunitiesCollection.ts +++ b/src/v4/social/hooks/collections/useCommunitiesCollection.ts @@ -11,7 +11,7 @@ export default function useCommunitiesCollection({ const { items, ...rest } = useLiveCollection({ fetcher: CommunityRepository.getCommunities, params: queryParams as Parameters<typeof CommunityRepository.getCommunities>[0], - shouldCall: !!queryParams?.categoryId && shouldCall, + shouldCall: shouldCall, }); return { diff --git a/src/v4/social/hooks/useMemberQueryByDisplayName.ts b/src/v4/social/hooks/useMemberQueryByDisplayName.ts new file mode 100644 index 000000000..f05948163 --- /dev/null +++ b/src/v4/social/hooks/useMemberQueryByDisplayName.ts @@ -0,0 +1,66 @@ +import { CommunityRepository } from '@amityco/ts-sdk'; +import { useEffect, useRef, useState } from 'react'; + +export const useMemberQueryByDisplayName = ({ + communityId, + displayName, + limit, + enabled, +}: { + communityId: string; + displayName: string; + limit: number; + enabled: boolean; +}) => { + const [items, setItems] = useState<Amity.Membership<'community'>[]>([]); + const [isLoading, setIsLoading] = useState(false); + const [hasMore, setHasMore] = useState(false); + const [loadMoreHasBeenCalled, setLoadMoreHasBeenCalled] = useState(false); + const loadMoreRef = useRef<(() => void) | null>(null); + const unSubRef = useRef<(() => void) | null>(null); + + const loadMore = () => { + if (loadMoreRef.current) { + loadMoreRef.current(); + setLoadMoreHasBeenCalled(true); + } + }; + + useEffect(() => { + if (!enabled) return; + + if (unSubRef.current) { + unSubRef.current(); + unSubRef.current = null; + } + + const unSubFn = CommunityRepository.Membership.searchMembers( + { + communityId, + search: displayName, + limit, + sortBy: 'displayName', + }, + (response) => { + setHasMore(response.hasNextPage || false); + setIsLoading(response.loading); + loadMoreRef.current = response.onNextPage || null; + setItems(response.data); + }, + ); + unSubRef.current = unSubFn; + + return () => { + unSubRef.current?.(); + unSubRef.current = null; + }; + }, [communityId, displayName, enabled]); + + return { + users: items, + isLoading, + hasMore, + loadMore, + loadMoreHasBeenCalled, + }; +}; diff --git a/src/v4/social/hooks/useUserQueryByDisplayName.ts b/src/v4/social/hooks/useUserQueryByDisplayName.ts index ab8916988..4b884307a 100644 --- a/src/v4/social/hooks/useUserQueryByDisplayName.ts +++ b/src/v4/social/hooks/useUserQueryByDisplayName.ts @@ -1,7 +1,7 @@ import { UserRepository } from '@amityco/ts-sdk'; import { useEffect, useRef, useState } from 'react'; -const MINIMUM_STRING_LENGTH_TO_TRIGGER_QUERY = 1; +const MINIMUM_STRING_LENGTH_TO_TRIGGER_QUERY = 3; export const useUserQueryByDisplayName = ( displayName: string, diff --git a/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx b/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx index fa6fcadc8..19b8cf651 100644 --- a/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx +++ b/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx @@ -11,6 +11,9 @@ import ReactDOM from 'react-dom'; import { $createMentionNode } from './MentionNodes'; import { CommunityMember } from '../CommunityMember'; import { UserRepository } from '@amityco/ts-sdk'; +import { useMemberQueryByDisplayName } from '~/v4/social/hooks/useMemberQueryByDisplayName'; +import useCommunity from '~/v4/chat/hooks/useCommunity'; +import { useUserQueryByDisplayName } from '~/v4/core/hooks/collections/useUsersCollection'; const MAX_LENGTH = 5000; @@ -97,7 +100,7 @@ function checkForAtSignMentions(text: string, minMatchLength: number): MenuTextM } function getPossibleQueryMatch(text: string): MenuTextMatch | null { - return checkForAtSignMentions(text, 1); + return checkForAtSignMentions(text, 0); } export class MentionTypeaheadOption extends MenuOption { @@ -109,130 +112,112 @@ export class MentionTypeaheadOption extends MenuOption { } } -export const useUserQueryByDisplayName = (displayName: string) => { - const [items, setItems] = useState<Amity.User[]>([]); - const [isLoading, setIsLoading] = useState(false); - const [hasMore, setHasMore] = useState(false); - const [loadMoreHasBeenCalled, setLoadMoreHasBeenCalled] = useState(false); - const loadMoreRef = useRef<(() => void) | null>(null); - const unSubRef = useRef<(() => void) | null>(null); - - const loadMore = () => { - if (loadMoreRef.current) { - loadMoreRef.current(); - setLoadMoreHasBeenCalled(true); - } - }; - - useEffect(() => { - if (unSubRef.current) { - unSubRef.current(); - unSubRef.current = null; - } - - const unSubFn = UserRepository.searchUserByDisplayName({ displayName }, (response) => { - setHasMore(response.hasNextPage || false); - setIsLoading(response.loading); - loadMoreRef.current = response.onNextPage || null; - setItems(response.data); - }); - unSubRef.current = unSubFn; - - return () => { - unSubRef.current?.(); - unSubRef.current = null; - }; - }, [displayName]); - - return { - users: items, - isLoading, - hasMore, - loadMore, - loadMoreHasBeenCalled, - }; -}; - -export const MentionTextInput = () => { +export const MentionTextInputPlugin = ({ communityId }: { communityId?: string | null }) => { const mentionTextInputItemRef = useRef<HTMLDivElement>(null); return ( <div> <div ref={mentionTextInputItemRef}></div> - <Mention anchorRef={mentionTextInputItemRef} /> + <Mention anchorRef={mentionTextInputItemRef} communityId={communityId} /> </div> ); }; - function Mention({ anchorRef }: { anchorRef: RefObject<HTMLDivElement> }) { - const [editor] = useLexicalComposerContext(); - - const [queryString, setQueryString] = useState<string | null>(null); - - const { users } = useUserQueryByDisplayName(queryString || ''); - - const options = useMemo(() => users.map((user) => new MentionTypeaheadOption(user)), [users]); - - const onSelectOption = useCallback( - ( - selectedOption: MentionTypeaheadOption, - nodeToReplace: TextNode | null, - closeMenu: () => void, - ) => { - editor.update(() => { - const mentionNode = $createMentionNode({ - mentionName: selectedOption.key, - displayName: selectedOption.user.displayName, - userId: selectedOption.user.userId, - }); - if (nodeToReplace) { - nodeToReplace.replace(mentionNode); - } - mentionNode.select(); - closeMenu(); - }); - }, - [editor], - ); - - const checkForMentionMatch = useCallback( - (text: string) => { - return getPossibleQueryMatch(text); - }, - [editor], - ); - - return ( - <LexicalTypeaheadMenuPlugin - onQueryChange={setQueryString} - onSelectOption={onSelectOption} - triggerFn={checkForMentionMatch} - options={options} - menuRenderFn={( - anchorElementRef, - { selectedIndex, selectOptionAndCleanUp, setHighlightedIndex }, - ) => - anchorRef.current && users.length - ? ReactDOM.createPortal( - <div className={styles.mentionTextInput_item}> - {options.map((option, i: number) => ( - <CommunityMember - isSelected={selectedIndex === i} - onClick={() => { - setHighlightedIndex(i); - selectOptionAndCleanUp(option); - }} - onMouseEnter={() => { - setHighlightedIndex(i); - }} - key={option.key} - option={option} - /> - ))} - </div>, - anchorRef.current, - ) - : null - } - /> - ); - } +function Mention({ + anchorRef, + communityId, +}: { + anchorRef: RefObject<HTMLDivElement>; + communityId?: string | null; +}) { + const [editor] = useLexicalComposerContext(); + + const [queryString, setQueryString] = useState<string | null>(null); + let options: MentionTypeaheadOption[] = []; + const { users: members } = useMemberQueryByDisplayName({ + communityId: communityId || '', + displayName: queryString || '', + limit: 10, + enabled: !!communityId && !!queryString, + }); + const { users } = useUserQueryByDisplayName({ + displayName: queryString || '', + limit: 10, + enabled: !!queryString, + }); + + const community = useCommunity(communityId || ''); + const isPublic = community?.isPublic; + + if (communityId && !isPublic) { + options = useMemo( + () => members.map((member) => new MentionTypeaheadOption(member.user as Amity.User)), + [members], + ); + } else { + options = useMemo(() => users.map((user) => new MentionTypeaheadOption(user)), [users]); + } + + const onSelectOption = useCallback( + ( + selectedOption: MentionTypeaheadOption, + nodeToReplace: TextNode | null, + closeMenu: () => void, + ) => { + editor.update(() => { + const mentionNode = $createMentionNode({ + mentionName: selectedOption.key, + displayName: selectedOption.user.displayName, + userId: selectedOption.user.userId, + }); + if (nodeToReplace) { + nodeToReplace.replace(mentionNode); + } + mentionNode.select(); + closeMenu(); + }); + }, + [editor], + ); + + const checkForMentionMatch = useCallback( + (text: string) => { + return getPossibleQueryMatch(text); + }, + [editor], + ); + + return ( + <LexicalTypeaheadMenuPlugin + onQueryChange={setQueryString} + onSelectOption={onSelectOption} + triggerFn={checkForMentionMatch} + options={options} + menuRenderFn={( + anchorElementRef, + { selectedIndex, selectOptionAndCleanUp, setHighlightedIndex }, + ) => + anchorRef.current && options.length > 0 + ? ReactDOM.createPortal( + <div className={styles.mentionTextInput_item}> + {options.map((option, i: number) => ( + <CommunityMember + isSelected={selectedIndex === i} + onClick={() => { + setHighlightedIndex(i); + selectOptionAndCleanUp(option); + }} + onMouseEnter={() => { + setHighlightedIndex(i); + }} + key={option.key} + option={option} + /> + ))} + </div>, + anchorRef.current, + ) + : null + } + /> + ); +} diff --git a/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx b/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx index 40829ff6f..0c865f4f8 100644 --- a/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx +++ b/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx @@ -153,7 +153,7 @@ const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCr isValid={textValue.text.length > 0 && !isPending} /> </div> - <PostTextField ref={editorRef} onChange={onChange} /> + <PostTextField ref={editorRef} onChange={onChange} communityId={targetId} /> </form> {isPending && isOnline && ( <Notification From 72735f43b7811c3ec66c751a2437e0dbb18df753 Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Wed, 3 Jul 2024 13:40:57 +0700 Subject: [PATCH 199/300] fix: query mention (#484) --- src/v4/core/hooks/collections/useUsersCollection.ts | 4 ++-- src/v4/social/hooks/useMemberQueryByDisplayName.ts | 4 ++-- .../MentionTextInput/MentionTextInput.tsx | 6 ++---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/v4/core/hooks/collections/useUsersCollection.ts b/src/v4/core/hooks/collections/useUsersCollection.ts index b873ab92e..9670340d9 100644 --- a/src/v4/core/hooks/collections/useUsersCollection.ts +++ b/src/v4/core/hooks/collections/useUsersCollection.ts @@ -6,11 +6,11 @@ import useLiveCollection from '~/v4/core/hooks/useLiveCollection'; export const useUserQueryByDisplayName = ({ displayName, limit, - enabled, + enabled = true, }: { displayName: string; limit: number; - enabled: boolean; + enabled?: boolean; }) => { const [items, setItems] = useState<Amity.User[]>([]); const [isLoading, setIsLoading] = useState(false); diff --git a/src/v4/social/hooks/useMemberQueryByDisplayName.ts b/src/v4/social/hooks/useMemberQueryByDisplayName.ts index f05948163..fc33f4649 100644 --- a/src/v4/social/hooks/useMemberQueryByDisplayName.ts +++ b/src/v4/social/hooks/useMemberQueryByDisplayName.ts @@ -5,12 +5,12 @@ export const useMemberQueryByDisplayName = ({ communityId, displayName, limit, - enabled, + enabled = true, }: { communityId: string; displayName: string; limit: number; - enabled: boolean; + enabled?: boolean; }) => { const [items, setItems] = useState<Amity.Membership<'community'>[]>([]); const [isLoading, setIsLoading] = useState(false); diff --git a/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx b/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx index 19b8cf651..15c5cb593 100644 --- a/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx +++ b/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx @@ -6,11 +6,10 @@ import { MenuTextMatch, } from '@lexical/react/LexicalTypeaheadMenuPlugin'; import { TextNode } from 'lexical'; -import React, { RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import React, { RefObject, useCallback, useMemo, useRef, useState } from 'react'; import ReactDOM from 'react-dom'; import { $createMentionNode } from './MentionNodes'; import { CommunityMember } from '../CommunityMember'; -import { UserRepository } from '@amityco/ts-sdk'; import { useMemberQueryByDisplayName } from '~/v4/social/hooks/useMemberQueryByDisplayName'; import useCommunity from '~/v4/chat/hooks/useCommunity'; import { useUserQueryByDisplayName } from '~/v4/core/hooks/collections/useUsersCollection'; @@ -137,12 +136,11 @@ function Mention({ communityId: communityId || '', displayName: queryString || '', limit: 10, - enabled: !!communityId && !!queryString, + enabled: !!communityId, }); const { users } = useUserQueryByDisplayName({ displayName: queryString || '', limit: 10, - enabled: !!queryString, }); const community = useCommunity(communityId || ''); From ef1562cc5b8e33e5cf50b137f9917ceee59ea2fa Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Wed, 3 Jul 2024 15:52:15 +0700 Subject: [PATCH 200/300] feat: ASC-00000 - story preview skeleton (#486) * feat: story preview skeleton * fix: remove z-index --- src/icons/Play.tsx | 5 +- .../StoryPreview/StoryPreview.module.css | 75 +++++++++++++++- .../StoryPreview/StoryPreview.tsx | 10 ++- .../StoryPreview/StoryPreviewSkeleton.tsx | 37 ++++++++ .../StoryPreviewThumbnail.module.css | 85 ++++++++++++++++++- .../StoryPreviewThumbnail.tsx | 83 ++++++++++++------ .../StoryPreviewThumbnailSkeleton.tsx | 17 ++++ 7 files changed, 281 insertions(+), 31 deletions(-) create mode 100644 src/v4/social/internal-components/StoryPreview/StoryPreviewSkeleton.tsx create mode 100644 src/v4/social/internal-components/StoryPreview/StoryPreviewThumbnail/StoryPreviewThumbnailSkeleton.tsx diff --git a/src/icons/Play.tsx b/src/icons/Play.tsx index bd71bb88b..c2480ff91 100644 --- a/src/icons/Play.tsx +++ b/src/icons/Play.tsx @@ -10,7 +10,10 @@ function Icon(props: React.SVGProps<SVGSVGElement>) { viewBox="0 0 25 24" {...props} > - <path d="M19.531 10.809a1.71 1.71 0 010 2.918L7.156 21.039c-1.125.668-2.531-.14-2.531-1.477V4.938c0-1.44 1.512-2.039 2.531-1.44l12.375 7.312z"></path> + <path + fill="white" + d="M19.531 10.809a1.71 1.71 0 010 2.918L7.156 21.039c-1.125.668-2.531-.14-2.531-1.477V4.938c0-1.44 1.512-2.039 2.531-1.44l12.375 7.312z" + ></path> </svg> ); } diff --git a/src/v4/social/internal-components/StoryPreview/StoryPreview.module.css b/src/v4/social/internal-components/StoryPreview/StoryPreview.module.css index 777a0d3a3..98b7d8085 100644 --- a/src/v4/social/internal-components/StoryPreview/StoryPreview.module.css +++ b/src/v4/social/internal-components/StoryPreview/StoryPreview.module.css @@ -90,6 +90,9 @@ .mediaContainer { width: 100%; height: 100%; + position: absolute; + top: 0; + left: 0; } .media { @@ -120,7 +123,77 @@ .userInfoContent { display: flex; - justify-content: space-between; align-items: center; gap: 0.5rem; + width: 100%; +} + +.shadowOverlay { + position: absolute; + inset: 0; + background: linear-gradient(180deg, rgb(0 0 0 / 16%) 100%, rgb(255 255 255 / 0%) 55.05%); + pointer-events: none; +} + +@keyframes pulse { + 0% { + opacity: 0.6; + } + + 50% { + opacity: 1; + } + + 100% { + opacity: 0.6; + } +} + +.skeleton { + background-color: #e0e0e0; + animation: pulse 1.5s ease-in-out infinite; +} + +.progressBar.skeleton { + height: 0.25rem; + width: 100%; +} + +.avatarContainer.skeleton { + width: 2rem; + height: 2rem; + border-radius: 50%; +} + +.storyPreviewTitle.skeleton { + width: 60%; + height: 1rem; + border-radius: 0.25rem; +} + +.mediaContainer.skeleton { + width: 100%; + height: 100%; +} + +.textSkeleton { + flex-grow: 1; + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.nameSkeleton { + width: 80%; + height: 0.5rem; + background-color: #e0e0e0; + margin-bottom: 0.25rem; + border-radius: 0.25rem; +} + +.timeSkeleton { + width: 40%; + height: 0.5rem; + background-color: #e0e0e0; + border-radius: 0.25rem; } diff --git a/src/v4/social/internal-components/StoryPreview/StoryPreview.tsx b/src/v4/social/internal-components/StoryPreview/StoryPreview.tsx index 29af176e1..d40ca6d10 100644 --- a/src/v4/social/internal-components/StoryPreview/StoryPreview.tsx +++ b/src/v4/social/internal-components/StoryPreview/StoryPreview.tsx @@ -8,6 +8,7 @@ import { PauseIcon, PlayIcon } from '~/icons'; import ColorThief from 'colorthief'; import Community from '~/v4/icons/Community'; import clsx from 'clsx'; +import { StoryPreviewSkeleton } from './StoryPreviewSkeleton'; type AmityStoryMediaType = { type: 'image'; url: string } | { type: 'video'; url: string }; @@ -120,7 +121,7 @@ export const StoryPreview: React.FC<StoryPreviewProps> = ({ }; }; - if (mediaType?.url) { + if (mediaType?.url && mediaType?.type === 'image') { extractColorsFromImage(mediaType.url); } else { setBackgroundColor([]); @@ -166,6 +167,10 @@ export const StoryPreview: React.FC<StoryPreviewProps> = ({ }; }, [isPlaying, mediaType, duration, isImageLoaded]); + if (isLoading) { + return <StoryPreviewSkeleton />; + } + return ( <div className={styles.storyPreviewWrapper} style={{ width, height }}> <div className={styles.storyPreviewContainer}> @@ -186,7 +191,7 @@ export const StoryPreview: React.FC<StoryPreviewProps> = ({ </Typography.BodyBold> </div> {mediaType?.type === 'video' && ( - <div className={styles.playPauseButton} onClick={handlePlayPauseClick}> + <div onClick={handlePlayPauseClick}> {isPlaying ? ( <PauseIcon width={24} height={24} /> ) : ( @@ -235,6 +240,7 @@ export const StoryPreview: React.FC<StoryPreviewProps> = ({ )} </> )} + <div className={styles.shadowOverlay} /> </div> <div className={styles.hyperLinkContainer}> diff --git a/src/v4/social/internal-components/StoryPreview/StoryPreviewSkeleton.tsx b/src/v4/social/internal-components/StoryPreview/StoryPreviewSkeleton.tsx new file mode 100644 index 000000000..d68b8a439 --- /dev/null +++ b/src/v4/social/internal-components/StoryPreview/StoryPreviewSkeleton.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import styles from './StoryPreview.module.css'; +import clsx from 'clsx'; + +type StoryPreviewSkeletonProps = { + width?: number | string; + height?: number | string; +}; + +export const StoryPreviewSkeleton: React.FC<StoryPreviewSkeletonProps> = ({ + width = '100%', + height = '100%', +}) => { + return ( + <div className={styles.storyPreviewWrapper} style={{ width, height }}> + <div className={styles.storyPreviewContainer}> + <div className={styles.shadowOverlay} /> + <div className={styles.headerContainer}> + <div className={styles.headerContent}> + <div className={styles.userInfo}> + <div className={styles.userInfoContent}> + <div className={clsx(styles.avatarContainer, styles.skeleton)} /> + <div className={styles.textSkeleton}> + <div className={clsx(styles.nameSkeleton, styles.skeleton)}></div> + <div className={clsx(styles.timeSkeleton, styles.skeleton)}></div> + </div> + </div> + </div> + </div> + </div> + <div className={styles.contentWrapper}> + <div className={clsx(styles.mediaContainer, styles.skeleton)} /> + </div> + </div> + </div> + ); +}; diff --git a/src/v4/social/internal-components/StoryPreview/StoryPreviewThumbnail/StoryPreviewThumbnail.module.css b/src/v4/social/internal-components/StoryPreview/StoryPreviewThumbnail/StoryPreviewThumbnail.module.css index e434a47e0..c391ddb1f 100644 --- a/src/v4/social/internal-components/StoryPreview/StoryPreviewThumbnail/StoryPreviewThumbnail.module.css +++ b/src/v4/social/internal-components/StoryPreview/StoryPreviewThumbnail/StoryPreviewThumbnail.module.css @@ -5,8 +5,8 @@ } .storyPreviewAvatar { - width: 0.7951rem; - height: 0.7951rem; + width: 1rem; + height: 1rem; flex-shrink: 0; } @@ -164,3 +164,84 @@ white-space: nowrap; text-overflow: ellipsis; } + +.header { + position: absolute; + top: 0.5rem; + left: 0.5rem; + right: 0.5rem; + display: flex; + align-items: center; +} + +.skeleton { + background-color: #636878; + animation: pulse 1.5s ease-in-out infinite; +} + +@keyframes pulse { + 0% { + opacity: 1; + } + + 50% { + opacity: 0.4; + } + + 100% { + opacity: 1; + } +} + +.avatarSkeleton { + width: 1rem; + height: 1rem; + border-radius: 50%; + background-color: #e0e0e0; + margin-right: 0.5rem; +} + +.textSkeleton { + flex-grow: 1; +} + +.nameSkeleton { + width: 80%; + height: 0.25rem; + background-color: #e0e0e0; + margin-bottom: 0.25rem; + border-radius: 0.25rem; +} + +.timeSkeleton { + width: 40%; + height: 0.25rem; + background-color: #e0e0e0; + border-radius: 0.25rem; +} + +.skeletonContainer { + width: 7.4rem; + height: 13rem; + position: relative; + overflow: hidden; +} + +.shadowOverlay { + position: absolute; + inset: 0; + background: linear-gradient(180deg, rgb(0 0 0 / 16%) 100%, rgb(255 255 255 / 0%) 55.05%); + pointer-events: none; + z-index: 0; +} + +.fallbackThumbnail { + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + background-color: #f0f0f0; + color: #666; + text-align: center; +} diff --git a/src/v4/social/internal-components/StoryPreview/StoryPreviewThumbnail/StoryPreviewThumbnail.tsx b/src/v4/social/internal-components/StoryPreview/StoryPreviewThumbnail/StoryPreviewThumbnail.tsx index 8226e95c1..de92ce30d 100644 --- a/src/v4/social/internal-components/StoryPreview/StoryPreviewThumbnail/StoryPreviewThumbnail.tsx +++ b/src/v4/social/internal-components/StoryPreview/StoryPreviewThumbnail/StoryPreviewThumbnail.tsx @@ -6,6 +6,7 @@ import { Avatar, Typography } from '~/v4/core/components'; import TruncateMarkup from 'react-truncate-markup'; import styles from './StoryPreviewThumbnail.module.css'; import Community from '~/v4/icons/Community'; +import { StoryPreviewThumbnailSkeleton } from './StoryPreviewThumbnailSkeleton'; type StoryPreviewThumbnailProps = { thumbnailUrl?: string; @@ -30,14 +31,31 @@ export const StoryPreviewThumbnail: React.FC<StoryPreviewThumbnailProps> = ({ imageMode = 'fill', }) => { const [backgroundColor, setBackgroundColor] = useState<string>(''); + const [imageError, setImageError] = useState(false); + const [isLoading, setIsLoading] = useState(true); const imageRef = useRef<HTMLImageElement>(null); useEffect(() => { - const extractColorsFromImage = async () => { - if (imageRef.current && imageRef.current.complete) { + console.log('Effect start - thumbnailUrl:', thumbnailUrl); + setIsLoading(true); + setImageError(false); + + if (!thumbnailUrl) { + setBackgroundColor(''); + setImageError(true); + setIsLoading(false); + return; + } + + const img = new Image(); + img.crossOrigin = 'anonymous'; + img.src = thumbnailUrl; + + img.onload = () => { + if (imageRef.current) { const colorThief = new ColorThief(); try { - const palette = colorThief.getPalette(imageRef.current, 2); + const palette = colorThief.getPalette(img, 2); if (palette) { const gradient = `linear-gradient( 180deg, @@ -51,23 +69,49 @@ export const StoryPreviewThumbnail: React.FC<StoryPreviewThumbnailProps> = ({ setBackgroundColor(''); } } + setIsLoading(false); }; - if (thumbnailUrl) { - if (imageRef.current && imageRef.current.complete) { - extractColorsFromImage(); - } else { - imageRef.current?.addEventListener('load', extractColorsFromImage); - } - } else { - setBackgroundColor(''); - } + img.onerror = () => { + setImageError(true); + setIsLoading(false); + }; return () => { - imageRef.current?.removeEventListener('load', extractColorsFromImage); + img.onload = null; + img.onerror = null; }; }, [thumbnailUrl]); + const renderThumbnail = () => { + if (imageError || !thumbnailUrl) { + return ( + <div className={styles.fallbackThumbnail}> + <Typography.Body>No Image Available</Typography.Body> + </div> + ); + } + return ( + <> + <img + ref={imageRef} + src={thumbnailUrl} + alt="Story Thumbnail" + className={clsx(styles.thumbnailImage, { + [styles.imageFit]: imageMode === 'fit', + [styles.imageCover]: imageMode === 'fill', + })} + crossOrigin="anonymous" + /> + <div className={styles.shadowOverlay}></div> + </> + ); + }; + + if (isLoading) { + return <StoryPreviewThumbnailSkeleton />; + } + return ( <div className={styles.containerWrapper} @@ -77,18 +121,7 @@ export const StoryPreviewThumbnail: React.FC<StoryPreviewThumbnailProps> = ({ }} > <div className={styles.storyPreviewContainer}> - <div className={styles.thumbnailMedia}> - <img - ref={imageRef} - src={thumbnailUrl} - alt="Story Thumbnail" - className={clsx(styles.thumbnailImage, { - [styles.imageFit]: imageMode === 'fit', - [styles.imageCover]: imageMode === 'fill', - })} - crossOrigin="anonymous" - /> - </div> + <div className={styles.thumbnailMedia}>{renderThumbnail()}</div> <div className={styles.thumbnailInfo}> <div className={styles.userInfo}> <div className={styles.storyPreviewAvatar}> diff --git a/src/v4/social/internal-components/StoryPreview/StoryPreviewThumbnail/StoryPreviewThumbnailSkeleton.tsx b/src/v4/social/internal-components/StoryPreview/StoryPreviewThumbnail/StoryPreviewThumbnailSkeleton.tsx new file mode 100644 index 000000000..a9c4c7a26 --- /dev/null +++ b/src/v4/social/internal-components/StoryPreview/StoryPreviewThumbnail/StoryPreviewThumbnailSkeleton.tsx @@ -0,0 +1,17 @@ +import clsx from 'clsx'; +import React from 'react'; +import styles from './StoryPreviewThumbnail.module.css'; + +export const StoryPreviewThumbnailSkeleton: React.FC = () => { + return ( + <div className={clsx(styles.skeletonContainer, styles.skeleton)}> + <div className={styles.header}> + <div className={clsx(styles.avatarSkeleton, styles.skeleton)}></div> + <div className={styles.textSkeleton}> + <div className={clsx(styles.nameSkeleton, styles.skeleton)}></div> + <div className={clsx(styles.timeSkeleton, styles.skeleton)}></div> + </div> + </div> + </div> + ); +}; From ca73e6eea24dd3ec74990f33efa50a2ac9b88380 Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Wed, 3 Jul 2024 17:36:59 +0700 Subject: [PATCH 201/300] feat: ASC-22898 - create postProvider (#485) * feat: create postProvider * refactor: accessibility --- src/v4/core/providers/AmityUIKitProvider.tsx | 3 +- .../components/GlobalFeed/GlobalFeed.tsx | 25 +++++++++++++---- .../elements/PostTextField/PostTextField.tsx | 4 +-- .../PostComposerPage/PostComposerPage.tsx | 15 ++++++---- src/v4/social/providers/PostProvider.tsx | 28 +++++++++++++++++++ 5 files changed, 60 insertions(+), 15 deletions(-) create mode 100644 src/v4/social/providers/PostProvider.tsx diff --git a/src/v4/core/providers/AmityUIKitProvider.tsx b/src/v4/core/providers/AmityUIKitProvider.tsx index f10b5e4a1..1a128b15c 100644 --- a/src/v4/core/providers/AmityUIKitProvider.tsx +++ b/src/v4/core/providers/AmityUIKitProvider.tsx @@ -35,6 +35,7 @@ import { NotificationProvider } from '~/v4/core/providers/NotificationProvider'; import { DrawerProvider } from '~/v4/core/providers/DrawerProvider'; import { NotificationProvider as LegacyNotificationProvider } from '~/core/providers/NotificationProvider'; import { CustomReactionProvider } from './CustomReactionProvider'; +import { PostProvider } from '~/v4/social/providers/PostProvider'; export type AmityUIKitConfig = Config; @@ -160,7 +161,7 @@ const AmityUIKitProvider: React.FC<AmityUIKitProviderProps> = ({ <PostRendererProvider config={postRendererConfig}> <NavigationProvider> <PageBehaviorProvider pageBehavior={pageBehavior}> - {children} + <PostProvider>{children}</PostProvider> </PageBehaviorProvider> </NavigationProvider> </PostRendererProvider> diff --git a/src/v4/social/components/GlobalFeed/GlobalFeed.tsx b/src/v4/social/components/GlobalFeed/GlobalFeed.tsx index 9a303bc33..81ee94301 100644 --- a/src/v4/social/components/GlobalFeed/GlobalFeed.tsx +++ b/src/v4/social/components/GlobalFeed/GlobalFeed.tsx @@ -1,14 +1,12 @@ import React, { useRef } from 'react'; import { PostContent, PostContentSkeleton } from '../PostContent'; -import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; -import { useGenerateStylesShadeColors } from '~/v4/core/providers/ThemeProvider'; import { EmptyNewsfeed } from '~/v4/social/components/EmptyNewsFeed/EmptyNewsFeed'; -import useGlobalFeed from '~/v4/core/hooks/collections/useGlobalFeed'; import useIntersectionObserver from '~/v4/core/hooks/useIntersectionObserver'; import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; import styles from './GlobalFeed.module.css'; import { useAmityComponent } from '~/v4/core/hooks/uikit'; +import { usePostContext } from '~/v4/social/providers/PostProvider'; interface GlobalFeedProps { pageId?: string; @@ -25,8 +23,7 @@ export const GlobalFeed = ({ isLoading, onFeedReachBottom, }: GlobalFeedProps) => { - const { getConfig } = useCustomization(); - const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + const { accessibilityId, themeStyles } = useAmityComponent({ pageId, componentId, @@ -35,6 +32,7 @@ export const GlobalFeed = ({ const intersectionRef = useRef<HTMLDivElement>(null); const { AmityGlobalFeedComponentBehavior } = usePageBehavior(); + const { post: newPost } = usePostContext(); useIntersectionObserver({ ref: intersectionRef, @@ -48,7 +46,22 @@ export const GlobalFeed = ({ } return ( - <div className={styles.global_feed} style={themeStyles}> + <div className={styles.global_feed} style={themeStyles} data-qa-anchor={accessibilityId}> + {newPost && ( + <> + <div className={styles.global_feed__postContainer}> + <PostContent + pageId={pageId} + post={newPost} + type="feed" + onClick={() => { + AmityGlobalFeedComponentBehavior?.goToPostDetailPage?.({ postId: newPost.postId }); + }} + /> + </div> + <div className={styles.global_feed__divider} /> + </> + )} {posts.map((post, index) => ( <div key={post.postId}> {index !== 0 ? <div className={styles.global_feed__divider} /> : null} diff --git a/src/v4/social/elements/PostTextField/PostTextField.tsx b/src/v4/social/elements/PostTextField/PostTextField.tsx index f3f27b4f4..af7e3f47d 100644 --- a/src/v4/social/elements/PostTextField/PostTextField.tsx +++ b/src/v4/social/elements/PostTextField/PostTextField.tsx @@ -9,7 +9,7 @@ import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'; import { AutoFocusPlugin } from '@lexical/react/LexicalAutoFocusPlugin'; import { MentionNode } from '~/v4/social/internal-components/MentionTextInput/MentionNodes'; import { MentionTextInputPlugin } from '~/v4/social/internal-components/MentionTextInput/MentionTextInput'; -import { MetaData, createPostParams } from '~/v4/social/pages/PostComposerPage/PostComposerPage'; +import { MetaData, CreatePostParams } from '~/v4/social/pages/PostComposerPage/PostComposerPage'; import styles from './PostTextField.module.css'; const theme = { @@ -33,7 +33,7 @@ interface EditorStateJson extends SerializedLexicalNode { } interface PostTextFieldProps { - onChange: (data: createPostParams) => void; + onChange: (data: CreatePostParams) => void; communityId?: string | null; } diff --git a/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx b/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx index 0c865f4f8..d2d8c93bf 100644 --- a/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx +++ b/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx @@ -17,6 +17,7 @@ import { useForm } from 'react-hook-form'; import { Mentioned } from '~/v4/helpers/utils'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; import useConnectionState from '~/v4/social/hooks/useConnectionState'; +import { usePostContext } from '~/v4/social/providers/PostProvider'; export enum Mode { CREATE = 'create', @@ -63,7 +64,7 @@ export function PostComposerPage(props: PostComposerPageProps) { } } -export type createPostParams = { +export type CreatePostParams = { text: string; mentioned: Mentioned[]; mentionees: { @@ -78,13 +79,13 @@ const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCr pageId, }); - const { onBack } = useNavigation(); const editorRef = useRef<LexicalEditor | null>(null); const { AmityPostComposerPageBehavior } = usePageBehavior(); const { confirm } = useConfirmContext(); const isOnline = useConnectionState(); + const { setPost } = usePostContext(); - const [textValue, setTextValue] = useState<createPostParams>({ + const [textValue, setTextValue] = useState<CreatePostParams>({ text: '', mentioned: [], mentionees: [ @@ -115,15 +116,17 @@ const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCr }, }); - const { mutateAsync: mutateCreatePostAsync, isPending, isError } = useMutateCreatePost(); + const { mutateAsync: mutateCreatePostAsync, isPending, isError, isSuccess } = useMutateCreatePost(); const { handleSubmit } = useForm(); const onSubmit = () => { - mutateCreatePostAsync(); + mutateCreatePostAsync().then((response) => { + setPost(response.data); + }) }; - const onChange = (val: createPostParams) => { + const onChange = (val: CreatePostParams) => { setTextValue(val); }; diff --git a/src/v4/social/providers/PostProvider.tsx b/src/v4/social/providers/PostProvider.tsx new file mode 100644 index 000000000..5829de918 --- /dev/null +++ b/src/v4/social/providers/PostProvider.tsx @@ -0,0 +1,28 @@ +import React, { createContext, useContext, useState } from 'react'; + +type PostContextType = { + post: Amity.Post | null; + setPost: (post: Amity.Post) => void; +}; + +const PostContext = createContext<PostContextType>({ + post: null, + setPost: () => {}, +}); + +export const usePostContext = () => useContext(PostContext); + +type PostProviderProps = { + children: React.ReactNode; +}; + +export const PostProvider: React.FC<PostProviderProps> = ({ children }) => { + const [post, setPost] = useState<Amity.Post | null>(null); + + const value: PostContextType = { + post, + setPost, + }; + + return <PostContext.Provider value={value}>{children}</PostContext.Provider>; +}; From 789ac57fd8bf4ca004d2d423e43deec8b7c5abee Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Wed, 3 Jul 2024 21:39:23 +0700 Subject: [PATCH 202/300] fix: story bugs (#487) --- .../components/StoryTab/StoryTabCommunity.tsx | 18 ++++++++++-------- src/v4/social/elements/StoryRing/StoryRing.tsx | 10 +++------- src/v4/social/pages/DraftsPage/DraftsPage.tsx | 1 + .../pages/StoryPage/CommunityFeedStory.tsx | 3 +-- .../social/pages/StoryPage/GlobalFeedStory.tsx | 1 - 5 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/v4/social/components/StoryTab/StoryTabCommunity.tsx b/src/v4/social/components/StoryTab/StoryTabCommunity.tsx index 93ec60246..38a709e67 100644 --- a/src/v4/social/components/StoryTab/StoryTabCommunity.tsx +++ b/src/v4/social/components/StoryTab/StoryTabCommunity.tsx @@ -89,14 +89,16 @@ export const StoryTabCommunityFeed: React.FC<StoryTabCommunityFeedProps> = ({ className={clsx(styles.storyTabContainer)} > <div className={clsx(styles.storyWrapper)}> - <StoryRing - pageId={pageId} - componentId={componentId} - hasUnseen={hasUnSeen} - uploading={uploading} - isErrored={isErrored} - size={48} - /> + {hasStories && ( + <StoryRing + pageId={pageId} + componentId={componentId} + hasUnseen={hasUnSeen} + uploading={uploading} + isErrored={isErrored} + size={48} + /> + )} <button className={clsx(styles.storyAvatarContainer)} onClick={handleOnClick}> <CommunityAvatar pageId={pageId} componentId={componentId} community={community} /> diff --git a/src/v4/social/elements/StoryRing/StoryRing.tsx b/src/v4/social/elements/StoryRing/StoryRing.tsx index 396cb611c..741a902d2 100644 --- a/src/v4/social/elements/StoryRing/StoryRing.tsx +++ b/src/v4/social/elements/StoryRing/StoryRing.tsx @@ -3,7 +3,7 @@ import { useAmityElement } from '~/v4/core/hooks/uikit'; import styles from './StoryRing.module.css'; -const EmptyStateRingSvg = ({ +const SeenStateRingSvg = ({ pageId, componentId, elementId, @@ -183,6 +183,7 @@ export const StoryRing = ({ }); if (isExcluded) return null; + if (isErrored) { return ( <svg @@ -222,11 +223,6 @@ export const StoryRing = ({ } return ( - <EmptyStateRingSvg - pageId={pageId} - componentId={componentId} - elementId={elementId} - size={size} - /> + <SeenStateRingSvg pageId={pageId} componentId={componentId} elementId={elementId} size={size} /> ); }; diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index 267ec0b19..07353631c 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -292,6 +292,7 @@ export const PlainDraftStoryPage = ({ }; export const AmityDraftStoryPage = (props: AmityDraftStoryPageProps) => { + const { page } = useNavigation(); const { AmityDraftStoryPageBehavior } = usePageBehavior(); return ( diff --git a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx index ac767459f..67d74b1d1 100644 --- a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx @@ -86,7 +86,6 @@ export const CommunityFeedStory = ({ renderer({ ...props, onClose: () => onClose(communityId), - onSwipeDown: () => onSwipeDown(communityId), onClickCommunity: () => onClickCommunity(communityId), }); @@ -95,7 +94,7 @@ export const CommunityFeedStory = ({ tester, }; }), - [renderers, onClose, onSwipeDown, onClickCommunity, communityId], + [renderers, onClose, onClickCommunity, communityId], ); const fileInputRef = useRef<HTMLInputElement>(null); diff --git a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx index 41899d386..73280b9da 100644 --- a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx @@ -84,7 +84,6 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ renderer({ ...props, onClose: () => onClose(targetId), - onSwipeDown: () => onSwipeDown(targetId), onClickCommunity: () => onClickCommunity(targetId), }); From 39116b3bd28b02b8fe5c531392e4e8fb356345c0 Mon Sep 17 00:00:00 2001 From: Pitchaya T <33589608+ptchayap@users.noreply.github.com> Date: Mon, 8 Jul 2024 12:41:07 +0700 Subject: [PATCH 203/300] feat: ASC-00000 - new comment (#482) * chore: comments * fix: cannot see comment in storybook * feat: add ui for composer * feat: export PostCommentComposer * feat: add view reply and view all comments UI * fix: post comment input style * fix: change to new comment compose bar * fix: comment compose bar style * fix: remove compose bar on globalFeed page * fix: change className * feat: add PostReplyComment component * feat: set max line height to 10 * fix: add margin to align the item vertical center at first * feat: add comment mention input in composer * feat: add query mentionable users hook * feat: fix mention list position * fix: remove unused * feat: add reply comment ui * fix: placeholder position * fix: to clear textInput state * feat: handle loadMore in post comment list * feat: add post reply comment list * fix: load more replies button in reply list * feat: add bottomsheet for action on PostComment * feat: add bottom sheet for PostReplyComment action * feat: add to show delete comment * feat add handling action * fix: delete comment text style * feat: add delete state ui on PostReplyComment * feat: add edit text in comment details * fix: clear editor state when comment create success * feat: add edit ui * fix: button style * fix: PostCommentInput prop * feat: add logic to convert data text to edior state * fix: cannot mention user * fix: edit text styling * feat: add pointer styling * fix: change to wrap options inside component to reduce calling check isFlaggedByMe * feat: handle update comment * feat: update comment in reply comment * feat: add subscription to each comment * feat: add ui text with mention * fix: remove unused * fix: sending too many request when intersect * feat: subscribe to get new comment data * feat: fix mention panel position --------- Co-authored-by: Bonn <pittawat@amity.co> --- src/mock/useOneComment.ts | 3 +- .../core/providers/CustomizationProvider.tsx | 4 +- src/v4/icons/ReplyComment.tsx | 12 + .../CommentEdition/CommentEdition.tsx | 2 +- .../CommentOptions/CommentOptions.module.css | 17 + .../CommentOptions/CommentOptions.tsx | 95 ++++++ .../GlobalFeed/GlobalFeed.module.css | 6 +- .../components/GlobalFeed/GlobalFeed.tsx | 35 +- .../PostComment/PostComment.module.css | 175 ++++++++++ .../PostComment/PostComment.stories.tsx | 20 ++ .../components/PostComment/PostComment.tsx | 296 +++++++++++++++++ .../PostCommentSkeleton.module.css | 51 +++ .../PostCommentSkeleton.stories.tsx | 20 ++ .../PostComment/PostCommentSkeleton.tsx | 44 +++ .../social/components/PostComment/index.tsx | 2 + .../PostCommentComposer.module.css | 67 ++++ .../PostCommentComposer.tsx | 131 ++++++++ .../PostCommentInput.module.css | 45 +++ .../PostCommentComposer/PostCommentInput.tsx | 306 ++++++++++++++++++ .../components/PostCommentComposer/index.tsx | 1 + .../PostCommentList.module.css | 18 ++ .../PostCommentList/PostCommentList.tsx | 84 +++++ .../components/PostCommentList/index.ts | 1 + .../PostCommentMentionInput.module.css | 12 + .../PostCommentMentionInput.tsx | 223 +++++++++++++ .../PostCommentMentionNode.tsx | 194 +++++++++++ .../PostMentionUser.module.css | 27 ++ .../PostMentionUser.tsx | 46 +++ .../PostCommentMentionInput/index.ts | 1 + .../PostReplyComment.module.css | 197 +++++++++++ .../PostReplyComment/PostReplyComment.tsx | 222 +++++++++++++ .../PostReplyCommentList.module.css | 21 ++ .../PostReplyCommentList.tsx | 58 ++++ .../EditCancelButton/EditCancelButton.tsx | 5 +- .../elements/SaveButton/SaveButton.module.css | 11 + .../social/elements/SaveButton/SaveButton.tsx | 19 +- src/v4/social/hooks/useCommentFlaggedByMe.ts | 15 +- src/v4/social/hooks/useMentionUser.ts | 50 +++ .../TextWithMention.module.css | 9 + .../TextWithMention/TextWithMention.tsx | 47 +++ .../PostDetailPage/PostDetailPage.module.css | 6 - .../pages/PostDetailPage/PostDetailPage.tsx | 66 +--- 42 files changed, 2576 insertions(+), 88 deletions(-) create mode 100644 src/v4/icons/ReplyComment.tsx create mode 100644 src/v4/social/components/CommentOptions/CommentOptions.module.css create mode 100644 src/v4/social/components/CommentOptions/CommentOptions.tsx create mode 100644 src/v4/social/components/PostComment/PostComment.module.css create mode 100644 src/v4/social/components/PostComment/PostComment.stories.tsx create mode 100644 src/v4/social/components/PostComment/PostComment.tsx create mode 100644 src/v4/social/components/PostComment/PostCommentSkeleton.module.css create mode 100644 src/v4/social/components/PostComment/PostCommentSkeleton.stories.tsx create mode 100644 src/v4/social/components/PostComment/PostCommentSkeleton.tsx create mode 100644 src/v4/social/components/PostComment/index.tsx create mode 100644 src/v4/social/components/PostCommentComposer/PostCommentComposer.module.css create mode 100644 src/v4/social/components/PostCommentComposer/PostCommentComposer.tsx create mode 100644 src/v4/social/components/PostCommentComposer/PostCommentInput.module.css create mode 100644 src/v4/social/components/PostCommentComposer/PostCommentInput.tsx create mode 100644 src/v4/social/components/PostCommentComposer/index.tsx create mode 100644 src/v4/social/components/PostCommentList/PostCommentList.module.css create mode 100644 src/v4/social/components/PostCommentList/PostCommentList.tsx create mode 100644 src/v4/social/components/PostCommentList/index.ts create mode 100644 src/v4/social/components/PostCommentMentionInput/PostCommentMentionInput.module.css create mode 100644 src/v4/social/components/PostCommentMentionInput/PostCommentMentionInput.tsx create mode 100644 src/v4/social/components/PostCommentMentionInput/PostCommentMentionNode.tsx create mode 100644 src/v4/social/components/PostCommentMentionInput/PostMentionUser.module.css create mode 100644 src/v4/social/components/PostCommentMentionInput/PostMentionUser.tsx create mode 100644 src/v4/social/components/PostCommentMentionInput/index.ts create mode 100644 src/v4/social/components/PostReplyComment/PostReplyComment.module.css create mode 100644 src/v4/social/components/PostReplyComment/PostReplyComment.tsx create mode 100644 src/v4/social/components/PostReplyCommentList/PostReplyCommentList.module.css create mode 100644 src/v4/social/components/PostReplyCommentList/PostReplyCommentList.tsx create mode 100644 src/v4/social/elements/SaveButton/SaveButton.module.css create mode 100644 src/v4/social/hooks/useMentionUser.ts create mode 100644 src/v4/social/internal-components/TextWithMention/TextWithMention.module.css create mode 100644 src/v4/social/internal-components/TextWithMention/TextWithMention.tsx diff --git a/src/mock/useOneComment.ts b/src/mock/useOneComment.ts index 75a1e674f..e1d4b760a 100644 --- a/src/mock/useOneComment.ts +++ b/src/mock/useOneComment.ts @@ -24,12 +24,13 @@ const useOneComment = (): [Amity.Comment | null, boolean] => { }); return () => disposeFn(); } else { - await CommentRepository.createComment({ + const { data: commentCreatedData } = await CommentRepository.createComment({ referenceType: 'post', referenceId: post.postId, data: { text: 'Comment created for story' }, }); setIsLoading(false); + setComment(commentCreatedData); } } run(); diff --git a/src/v4/core/providers/CustomizationProvider.tsx b/src/v4/core/providers/CustomizationProvider.tsx index 4ad6e746b..77fa6a208 100644 --- a/src/v4/core/providers/CustomizationProvider.tsx +++ b/src/v4/core/providers/CustomizationProvider.tsx @@ -215,8 +215,8 @@ export const defaultConfig: DefaultConfig = { }, '*/edit_comment_component/edit_cancel_button': { cancel_icon: '', - cancel_button_text: 'cancel', - background_color: '#1243EE', + cancel_button_text: 'Cancel', + background_color: '', }, '*/edit_comment_component/save_button': { save_icon: '', diff --git a/src/v4/icons/ReplyComment.tsx b/src/v4/icons/ReplyComment.tsx new file mode 100644 index 000000000..b6e06b299 --- /dev/null +++ b/src/v4/icons/ReplyComment.tsx @@ -0,0 +1,12 @@ +import React from 'react'; + +const ReplyComment = (props: React.SVGProps<SVGSVGElement>) => ( + <svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}> + <path + d="M13.824 9.72708L10.6334 12.8479C10.3988 13.0544 10.0469 13.0544 9.83578 12.8249L9.31965 12.3201C9.08504 12.1136 9.08504 11.7464 9.31965 11.5399L10.7507 10.2549H2.56305C2.2346 10.2549 2 10.0024 2 9.70413V5.56C2 5.25072 2.25072 5 2.56 5L3.31683 5C3.62611 5.00001 3.87683 5.25073 3.87683 5.56V8.4191H10.7507L9.31965 7.11113C9.08504 6.9046 9.08504 6.53745 9.31965 6.33093L9.83578 5.8261C10.0469 5.59663 10.3988 5.59663 10.6334 5.80315L13.824 8.92393C14.0587 9.1534 14.0587 9.49761 13.824 9.72708Z" + fill="currentColor" + /> + </svg> +); + +export default ReplyComment; diff --git a/src/v4/social/components/CommentEdition/CommentEdition.tsx b/src/v4/social/components/CommentEdition/CommentEdition.tsx index 7bc126a63..f402b04f0 100644 --- a/src/v4/social/components/CommentEdition/CommentEdition.tsx +++ b/src/v4/social/components/CommentEdition/CommentEdition.tsx @@ -51,7 +51,7 @@ export const CommentEdition = ({ /> <ButtonContainer> <EditCancelButton pageId={pageId} componentId={componentId} onPress={onCancel} /> - <SaveButton pageId={pageId} componentId={componentId} onClick={onSubmit} /> + <SaveButton pageId={pageId} componentId={componentId} onPress={onSubmit} /> </ButtonContainer> </CommentEditContainer> ); diff --git a/src/v4/social/components/CommentOptions/CommentOptions.module.css b/src/v4/social/components/CommentOptions/CommentOptions.module.css new file mode 100644 index 000000000..4f60dd750 --- /dev/null +++ b/src/v4/social/components/CommentOptions/CommentOptions.module.css @@ -0,0 +1,17 @@ +.commentOptions__actionButton { + display: flex; + gap: 0.75rem; + padding: 1rem 0; + align-items: center; + cursor: pointer; +} + +.commentOptions__actionButton__icon { + color: var(--asc-color-base-default); + width: 1.5rem; + height: 1.5rem; +} + +.commentOptions__actionButton__text { + color: var(--asc-color-base-default); +} diff --git a/src/v4/social/components/CommentOptions/CommentOptions.tsx b/src/v4/social/components/CommentOptions/CommentOptions.tsx new file mode 100644 index 000000000..e5cb95c11 --- /dev/null +++ b/src/v4/social/components/CommentOptions/CommentOptions.tsx @@ -0,0 +1,95 @@ +import React from 'react'; +import { useCommentFlaggedByMe } from '~/v4/social/hooks/useCommentFlaggedByMe'; +import { useNotifications } from '~/v4/core/providers/NotificationProvider'; +import useCommentPermission from '~/social/hooks/useCommentPermission'; +import useSDK from '~/v4/core/hooks/useSDK'; +import { Typography } from '~/v4/core/components/index'; +import { isNonNullable } from '~/v4/helpers/utils'; +import { FlagIcon, PenIcon, TrashIcon } from '../../icons/index'; +import styles from './CommentOptions.module.css'; + +interface CommentOptionsProps { + comment: Amity.Comment; + handleEditComment: () => void; + handleDeleteComment: () => void; + onCloseBottomSheet: () => void; +} + +export const CommentOptions = ({ + comment, + handleEditComment, + handleDeleteComment, + onCloseBottomSheet, +}: CommentOptionsProps) => { + const { userRoles } = useSDK(); + const { toggleFlagComment, isFlaggedByMe } = useCommentFlaggedByMe(comment.commentId); + + // TODO: change to useCommentPermission v4 - remove readonly + const { canDelete, canEdit, canReport } = useCommentPermission(comment, false, userRoles); + const notification = useNotifications(); + + const handleReportComment = async () => { + try { + await toggleFlagComment({ + onFlagSuccess: () => + notification.success({ + content: 'Report sent', + }), + onUnFlagSuccss: () => + notification.success({ + content: 'Unreport sent', + }), + }); + } catch (err) { + if (err instanceof Error) { + notification.error({ + content: err.message, + }); + } + } + }; + + const options = [ + canEdit + ? { + name: 'Edit comment', + action: handleEditComment, + icon: <PenIcon className={styles.commentOptions__actionButton__icon} />, + } + : null, + canReport + ? { + name: isFlaggedByMe ? 'Unreport comment' : 'Report comment', + action: handleReportComment, + icon: <FlagIcon className={styles.commentOptions__actionButton__icon} />, + } + : null, + canDelete + ? { + name: 'Delete comment', + action: handleDeleteComment, + icon: <TrashIcon className={styles.commentOptions__actionButton__icon} />, + } + : null, + ].filter(isNonNullable); + + return ( + <> + {options.map((option, index) => ( + <div + className={styles.commentOptions__actionButton} + key={index} + onClick={() => { + onCloseBottomSheet(); + option.action(); + }} + > + {option.icon} + <div className={styles.commentOptions__actionButton__text}> + <Typography.BodyBold>{option.name}</Typography.BodyBold> + </div> + </div> + ))} + </> + ); +}; diff --git a/src/v4/social/components/GlobalFeed/GlobalFeed.module.css b/src/v4/social/components/GlobalFeed/GlobalFeed.module.css index 46031cf6a..70cefdc60 100644 --- a/src/v4/social/components/GlobalFeed/GlobalFeed.module.css +++ b/src/v4/social/components/GlobalFeed/GlobalFeed.module.css @@ -4,7 +4,7 @@ } .global_feed__postContainer { - padding: 0.25rem 1rem 0.75rem 0.75rem; + padding: 0.25rem 1rem 0.75rem; } .global_feed__postSkeletonContainer { @@ -75,6 +75,10 @@ height: 1px; } +.global_feed__commentContainer { + padding: 0.25rem 1rem 0 0.75rem; +} + @keyframes spin { from { transform: rotate(0deg); diff --git a/src/v4/social/components/GlobalFeed/GlobalFeed.tsx b/src/v4/social/components/GlobalFeed/GlobalFeed.tsx index 81ee94301..e230a7efc 100644 --- a/src/v4/social/components/GlobalFeed/GlobalFeed.tsx +++ b/src/v4/social/components/GlobalFeed/GlobalFeed.tsx @@ -3,10 +3,12 @@ import { PostContent, PostContentSkeleton } from '../PostContent'; import { EmptyNewsfeed } from '~/v4/social/components/EmptyNewsFeed/EmptyNewsFeed'; import useIntersectionObserver from '~/v4/core/hooks/useIntersectionObserver'; import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; +import { usePostContext } from '~/v4/social/providers/PostProvider'; import styles from './GlobalFeed.module.css'; import { useAmityComponent } from '~/v4/core/hooks/uikit'; -import { usePostContext } from '~/v4/social/providers/PostProvider'; +import { PostCommentComposer } from '../PostCommentComposer/PostCommentComposer'; +import { PostCommentList } from '../PostCommentList/PostCommentList'; interface GlobalFeedProps { pageId?: string; @@ -23,11 +25,10 @@ export const GlobalFeed = ({ isLoading, onFeedReachBottom, }: GlobalFeedProps) => { - const { accessibilityId, themeStyles } = - useAmityComponent({ - pageId, - componentId, - }); + const { accessibilityId, themeStyles } = useAmityComponent({ + pageId, + componentId, + }); const intersectionRef = useRef<HTMLDivElement>(null); @@ -65,16 +66,18 @@ export const GlobalFeed = ({ {posts.map((post, index) => ( <div key={post.postId}> {index !== 0 ? <div className={styles.global_feed__divider} /> : null} - <div className={styles.global_feed__postContainer}> - <PostContent - pageId={pageId} - post={post} - type="feed" - onClick={() => { - AmityGlobalFeedComponentBehavior?.goToPostDetailPage?.({ postId: post.postId }); - }} - /> - </div> + <> + <div className={styles.global_feed__postContainer}> + <PostContent + pageId={pageId} + post={post} + type="feed" + onClick={() => { + AmityGlobalFeedComponentBehavior?.goToPostDetailPage?.({ postId: post.postId }); + }} + /> + </div> + </> </div> ))} {isLoading diff --git a/src/v4/social/components/PostComment/PostComment.module.css b/src/v4/social/components/PostComment/PostComment.module.css new file mode 100644 index 000000000..5080a3269 --- /dev/null +++ b/src/v4/social/components/PostComment/PostComment.module.css @@ -0,0 +1,175 @@ +.postComment { + width: 100%; + display: grid; + grid-template-columns: min-content 1fr; + gap: 0.5rem; +} + +.postComment__deleteComment_container { + display: flex; + padding: 0.75rem 1rem; + align-items: flex-start; + gap: 1rem; + border-top: 1px solid var(--asc-color-base-shade4); + border-bottom: 1px solid var(--asc-color-base-shade4); +} + +.postComment__deleteComment_icon { + width: 1.25rem; + height: 1.25rem; + color: var(--asc-color-base-shade2); +} + +.postComment__deleteComment_text { + color: var(--asc-color-base-shade2); +} + +.postComment__avatar { + margin-right: 0.75rem; + display: inline-block; +} + +.postComment__details { + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.postComment__content { + display: flex; + flex-direction: column; + gap: 0.25rem; + justify-content: center; + align-items: start; + background-color: var(--asc-color-base-shade4); + border-radius: 0 0.75rem 0.75rem; + padding: 0.75rem; + max-width: max-content; +} + +.postComment__content__username { + color: var(--asc-color-base-default); +} + +.postComment__content__text { + color: var(--asc-color-base-default); +} + +.postComment__secondRow { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; +} + +.postComment__secondRow__leftPane { + display: flex; + gap: 0.75rem; + align-items: center; +} + +.postComment__secondRow__timestamp { + display: flex; + color: var(--asc-color-base-shade2); + white-space: pre; +} + +.postComment__secondRow__rightPane { + display: flex; + gap: 0.25rem; + align-items: center; +} + +.postComment__secondRow__rightPane__like { + width: 1.25rem; + height: 1.25rem; +} + +.postComment__secondRow__like { + cursor: pointer; + color: var(--asc-color-base-shade2); +} + +.postComment__secondRow__like[data-is-liked='true'] { + color: var(--asc-color-primary-default); +} + +.postComment__secondRow__reply { + cursor: pointer; + color: var(--asc-color-base-shade2); +} + +.postComment__secondRow__actionButton { + cursor: pointer; + height: 1.25rem; + fill: var(--asc-color-secondary-shade2); +} + +.postComment__viewReply_button { + display: flex; + padding: 0.3125rem 0.75rem 0.3125rem 0.5rem; + cursor: pointer; + align-items: flex-start; + gap: 0.25rem; + background: var(--asc-color-base-background); + border: 1px solid var(--asc-color-base-shade4); + border-radius: 0.25rem; + width: max-content; +} + +.postComment__viewReply_icon { + color: var(--asc-color-secondary-shade1); + width: 1rem; + height: 1rem; +} + +.postComment__viewReply_text { + color: var(--asc-color-secondary-shade1); +} + +.postComment__edit { + display: flex; + gap: 0.5rem; +} + +.postComment__edit__inputWrap { + width: 100%; +} + +.postComment__edit__input { + display: flex; + height: 7.5rem; + padding: 0.75rem; + flex-direction: column; + align-items: flex-start; + gap: 0.25rem; + align-self: stretch; + background-color: var(--asc-color-base-shade4); + width: 100%; + border-radius: 0 0.75rem 0.75rem; +} + +.postComment__edit__buttonWrap { + display: flex; + gap: var(--asc-spacing-s1); + justify-content: flex-end; + margin-top: var(--asc-spacing-s1); +} + +.postComment__edit__button { + padding: 0.375rem 0.75rem; + cursor: pointer; +} + +.postComment__edit__cancelButton { + color: var(--asc-color-secondary-shade1); + border-radius: var(--asc-border-radius-sm); + border: 1px solid var(--asc-color-secondary-shade1); + background: var(--asc-color-base-background); +} + +.postComment__edit__saveButton { + color: var(--asc-color-white); + border-radius: var(--asc-border-radius-sm); + background: var(--asc-color-primary-default); +} diff --git a/src/v4/social/components/PostComment/PostComment.stories.tsx b/src/v4/social/components/PostComment/PostComment.stories.tsx new file mode 100644 index 000000000..a003f065c --- /dev/null +++ b/src/v4/social/components/PostComment/PostComment.stories.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import useOneComment from '~/mock/useOneComment'; + +import { PostComment } from './PostComment'; + +export default { + title: 'v4-social/components/PostComment', +}; + +export const PostCommentStory = { + render: () => { + const [comment] = useOneComment(); + + if (comment == null) return null; + + return <PostComment comment={comment} />; + }, + + name: 'PostComment', +}; diff --git a/src/v4/social/components/PostComment/PostComment.tsx b/src/v4/social/components/PostComment/PostComment.tsx new file mode 100644 index 000000000..1918f3fcd --- /dev/null +++ b/src/v4/social/components/PostComment/PostComment.tsx @@ -0,0 +1,296 @@ +import React, { useCallback, useState } from 'react'; +import { Typography, BottomSheet } from '~/v4/core/components'; +import { ModeratorBadge } from '~/v4/social/elements/ModeratorBadge'; +import { Timestamp } from '~/v4/social/elements/Timestamp'; +import { UserAvatar } from '~/v4/social/internal-components/UserAvatar'; +import styles from './PostComment.module.css'; +import { useAmityComponent } from '~/v4/core/hooks/uikit'; +import ReplyComment from '~/v4/icons/ReplyComment'; +import { PostReplyCommentList } from '../PostReplyCommentList/PostReplyCommentList'; +import { MinusCircleIcon } from '../../icons/index'; +import { Mentionees } from '~/v4/helpers/utils'; +import { CommentRepository, ReactionRepository } from '@amityco/ts-sdk'; +import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; +import { LIKE_REACTION_KEY } from '../../constants/reactions'; +import { EditCancelButton } from '../../elements/EditCancelButton/EditCancelButton'; +import { SaveButton } from '../../elements/SaveButton/SaveButton'; +import clsx from 'clsx'; +import { PostCommentInput } from '../PostCommentComposer/PostCommentInput'; +import { CommentOptions } from '../CommentOptions/CommentOptions'; +import { CreateCommentParams } from '../PostCommentComposer/PostCommentComposer'; +import useCommentSubscription from '~/v4/core/hooks/subscriptions/useCommentSubscription'; +import { TextWithMention } from '../../internal-components/TextWithMention/TextWithMention'; + +const EllipsisH = ({ ...props }: React.SVGProps<SVGSVGElement>) => ( + <svg + width="16" + height="4" + viewBox="0 0 16 4" + fill="none" + xmlns="http://www.w3.org/2000/svg" + {...props} + > + <path d="M9.6875 2.25C9.6875 3.19922 8.91406 3.9375 8 3.9375C7.05078 3.9375 6.3125 3.19922 6.3125 2.25C6.3125 1.33594 7.05078 0.5625 8 0.5625C8.91406 0.5625 9.6875 1.33594 9.6875 2.25ZM13.9062 0.5625C14.8203 0.5625 15.5938 1.33594 15.5938 2.25C15.5938 3.19922 14.8203 3.9375 13.9062 3.9375C12.957 3.9375 12.2188 3.19922 12.2188 2.25C12.2188 1.33594 12.957 0.5625 13.9062 0.5625ZM2.09375 0.5625C3.00781 0.5625 3.78125 1.33594 3.78125 2.25C3.78125 3.19922 3.00781 3.9375 2.09375 3.9375C1.14453 3.9375 0.40625 3.19922 0.40625 2.25C0.40625 1.33594 1.14453 0.5625 2.09375 0.5625Z" /> + </svg> +); + +const Like = ({ ...props }: React.SVGProps<SVGSVGElement>) => ( + <svg + width="32" + height="32" + viewBox="0 0 32 32" + fill="none" + xmlns="http://www.w3.org/2000/svg" + {...props} + > + <circle cx="16" cy="16" r="16" fill="url(#paint0_linear_1709_1733)" /> + <path + d="M10.7752 12.1C11.2221 12.1 11.6002 12.4782 11.6002 12.925V21.175C11.6002 21.6563 11.2221 22 10.7752 22H8.0252C7.54395 22 7.2002 21.6563 7.2002 21.175V12.925C7.2002 12.4782 7.54395 12.1 8.0252 12.1H10.7752ZM9.4002 20.625C9.84707 20.625 10.2252 20.2813 10.2252 19.8C10.2252 19.3532 9.84707 18.975 9.4002 18.975C8.91895 18.975 8.5752 19.3532 8.5752 19.8C8.5752 20.2813 8.91895 20.625 9.4002 20.625ZM20.4002 7.2188C20.4002 8.66255 19.5064 9.48755 19.2314 10.45H22.7377C23.8721 10.45 24.7658 11.4125 24.7658 12.4782C24.8002 13.0969 24.5252 13.75 24.1127 14.1625C24.4564 14.9532 24.3877 16.0875 23.8033 16.8782C24.0783 17.7719 23.8033 18.8719 23.2189 19.4563C23.3908 20.075 23.3221 20.5907 23.0127 21.0032C22.3252 22 20.5721 22 19.0939 22H18.9908C17.3408 22 16.0002 21.4157 14.9002 20.9344C14.3502 20.6938 13.6283 20.3844 13.0783 20.3844C12.8721 20.35 12.7002 20.1782 12.7002 19.9719V12.6157C12.7002 12.5125 12.7346 12.4094 12.8033 12.3063C14.1783 10.9657 14.7627 9.5563 15.8627 8.42192C16.3783 7.9063 16.5502 7.15005 16.7564 6.3938C16.8939 5.77505 17.2033 4.40005 17.9252 4.40005C18.7502 4.40005 20.4002 4.67505 20.4002 7.2188Z" + fill="white" + /> + <defs> + <linearGradient + id="paint0_linear_1709_1733" + x1="12" + y1="3.2" + x2="26.4" + y2="39.2" + gradientUnits="userSpaceOnUse" + > + <stop stop-color="#63A1FF" /> + <stop offset="1" stop-color="#0041BE" /> + </linearGradient> + </defs> + </svg> +); + +interface PostCommentProps { + pageId?: string; + componentId?: string; + comment: Amity.Comment; + postTargetType: Amity.PostTargetType; + postTargetId: string; + onClickReply: (comment: Amity.Comment) => void; +} + +export const PostComment = ({ + pageId = '*', + componentId = 'comment_bubble', + comment, + postTargetType, + postTargetId, + onClickReply, +}: PostCommentProps) => { + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityComponent({ + pageId, + componentId, + }); + + const { confirm } = useConfirmContext(); + + const [bottomSheetOpen, setBottomSheetOpen] = useState(false); + const [hasClickLoadMore, setHasClickLoadMore] = useState(false); + const [isEditing, setIsEditing] = useState(false); + const [commentData, setCommentData] = useState<CreateCommentParams>(); + + const toggleBottomSheet = () => setBottomSheetOpen((prev) => !prev); + + const isLiked = (comment.myReactions || []).some((reaction) => reaction === 'like'); + + const replyAmount = comment.childrenNumber; + + if (isExcluded) return null; + + const deleteComment = async () => + comment.commentId && CommentRepository.deleteComment(comment.commentId); + + const handleEditComment = () => { + setIsEditing(true); + }; + + const handleDeleteComment = () => { + confirm({ + pageId, + componentId, + title: 'Delete comment', + content: 'This comment will be permanently removed.', + cancelText: 'Cancel', + okText: 'Delete', + onOk: deleteComment, + }); + }; + + const handleLike = async () => { + if (!comment) return; + + if (!isLiked) { + await ReactionRepository.addReaction('comment', comment?.commentId, LIKE_REACTION_KEY); + } else { + await ReactionRepository.removeReaction('comment', comment?.commentId, LIKE_REACTION_KEY); + } + }; + + const handleSaveComment = useCallback(async () => { + if (!commentData || !comment.commentId) return; + + await CommentRepository.updateComment(comment.commentId, { + data: commentData.data, + mentionees: commentData.mentionees as Amity.UserMention[], + metadata: commentData.metadata, + }); + + setIsEditing(false); + }, [commentData]); + + useCommentSubscription({ + commentId: comment.commentId, + }); + + return ( + <div style={themeStyles} data-qa-anchor={accessibilityId}> + {comment.isDeleted ? ( + <div className={styles.postComment__deleteComment_container}> + <MinusCircleIcon className={styles.postComment__deleteComment_icon} /> + <Typography.Body className={styles.postComment__deleteComment_text}> + This comment has been deleted + </Typography.Body> + </div> + ) : isEditing ? ( + <div className={styles.postComment__edit}> + <UserAvatar userId={comment.userId} /> + <div className={styles.postComment__edit__inputWrap}> + <div className={styles.postComment__edit__input}> + <PostCommentInput + postTargetType={postTargetType} + postTargetId={postTargetId} + value={{ + data: { + text: (comment.data as Amity.ContentDataText).text, + }, + mentionees: comment.mentionees as Mentionees, + metadata: comment.metadata || {}, + }} + onChange={(value: CreateCommentParams) => { + setCommentData(value); + }} + maxLines={5} + mentionOffsetBottom={215} + /> + </div> + <div className={styles.postComment__edit__buttonWrap}> + <EditCancelButton + componentId="edit_comment_component" + className={clsx( + styles.postComment__edit__button, + styles.postComment__edit__cancelButton, + )} + onPress={() => { + setIsEditing(false); + }} + /> + <SaveButton + className={clsx( + styles.postComment__edit__button, + styles.postComment__edit__saveButton, + )} + componentId="edit_comment_component" + onPress={handleSaveComment} + /> + </div> + </div> + </div> + ) : ( + <div className={styles.postComment}> + <UserAvatar userId={comment.userId} /> + <div className={styles.postComment__details}> + <div className={styles.postComment__content}> + <Typography.BodyBold className={styles.postComment__content__username}> + {comment.creator?.displayName} + </Typography.BodyBold> + + <ModeratorBadge pageId={pageId} componentId={componentId} /> + + <TextWithMention + data={{ text: (comment.data as Amity.ContentDataText).text }} + mentionees={comment.mentionees as Amity.UserMention[]} + metadata={comment.metadata} + /> + </div> + <div className={styles.postComment__secondRow}> + <div className={styles.postComment__secondRow__leftPane}> + <Typography.Caption className={styles.postComment__secondRow__timestamp}> + <Timestamp + pageId={pageId} + componentId={componentId} + timestamp={comment.createdAt} + /> + {comment.createdAt !== comment.editedAt && ' (edited)'} + </Typography.Caption> + + <div onClick={handleLike}> + <Typography.CaptionBold + className={styles.postComment__secondRow__like} + data-is-liked={isLiked} + > + {isLiked ? 'Liked' : 'Like'} + </Typography.CaptionBold> + </div> + <div onClick={() => onClickReply(comment)}> + <Typography.CaptionBold className={styles.postComment__secondRow__reply}> + Reply + </Typography.CaptionBold> + </div> + + <EllipsisH + className={styles.postComment__secondRow__actionButton} + onClick={() => setBottomSheetOpen(true)} + /> + </div> + {comment.reactionsCount > 0 && ( + <div className={styles.postComment__secondRow__rightPane}> + <Typography.Caption>{comment.reactionsCount}</Typography.Caption> + <Like className={styles.postComment__secondRow__rightPane__like} /> + </div> + )} + </div> + + {replyAmount > 0 && !hasClickLoadMore && ( + <div + className={styles.postComment__viewReply_button} + onClick={() => setHasClickLoadMore(true)} + > + <ReplyComment className={styles.postComment__viewReply_icon} /> + <Typography.CaptionBold className={styles.postComment__viewReply_text}> + View {replyAmount} {replyAmount > 1 ? 'replies' : 'reply'} + </Typography.CaptionBold> + </div> + )} + + {hasClickLoadMore && ( + <PostReplyCommentList + postTargetType={postTargetType} + postTargetId={postTargetId} + referenceId={comment.referenceId} + parentId={comment.commentId} + /> + )} + </div> + </div> + )} + <BottomSheet + onClose={toggleBottomSheet} + isOpen={bottomSheetOpen} + mountPoint={document.getElementById('asc-uikit-post-comment') as HTMLElement} + detent="content-height" + > + <CommentOptions + comment={comment} + handleEditComment={handleEditComment} + handleDeleteComment={handleDeleteComment} + onCloseBottomSheet={toggleBottomSheet} + /> + </BottomSheet> + </div> + ); +}; diff --git a/src/v4/social/components/PostComment/PostCommentSkeleton.module.css b/src/v4/social/components/PostComment/PostCommentSkeleton.module.css new file mode 100644 index 000000000..9539c2e3e --- /dev/null +++ b/src/v4/social/components/PostComment/PostCommentSkeleton.module.css @@ -0,0 +1,51 @@ +.postCommentSkeleton { + display: flex; + gap: 0.5rem; +} + +.postCommentSkeleton__avatar { + width: 2rem; + height: 2rem; + border-radius: 50%; + background: var(--asc-color-base-shade4); +} + +.postCommentSkeleton__details { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.postCommentSkeleton__content { + background-color: var(--asc-color-base-shade4); + border-radius: 0 0.75rem 0.75rem; + width: 13.875rem; + height: 4.25rem; +} + +.postCommentSkeleton__content__bar { + width: 10.3269rem; + height: 0.5rem; + border-radius: 0.75rem; + background: var(--asc-color-base-shade4); + padding-top: 0.19rem; + padding-bottom: 0.19rem; +} + +.postCommentSkeleton__animation { + animation: skeleton-pulse 1.5s ease-in-out infinite; +} + +@keyframes skeleton-pulse { + 0% { + opacity: 0.6; + } + + 50% { + opacity: 1; + } + + 100% { + opacity: 0.6; + } +} diff --git a/src/v4/social/components/PostComment/PostCommentSkeleton.stories.tsx b/src/v4/social/components/PostComment/PostCommentSkeleton.stories.tsx new file mode 100644 index 000000000..869f47b89 --- /dev/null +++ b/src/v4/social/components/PostComment/PostCommentSkeleton.stories.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import useOnePost from '~/mock/useOnePost'; + +import { PostCommentSkeleton } from './PostCommentSkeleton'; + +export default { + title: 'v4-social/components/PostCommentSkeleton', +}; + +export const PostCommentSkeletonStory = { + render: () => { + const [post] = useOnePost(); + + if (post == null) return null; + + return <PostCommentSkeleton />; + }, + + name: 'PostCommentSkeleton', +}; diff --git a/src/v4/social/components/PostComment/PostCommentSkeleton.tsx b/src/v4/social/components/PostComment/PostCommentSkeleton.tsx new file mode 100644 index 000000000..7ad2cbb0f --- /dev/null +++ b/src/v4/social/components/PostComment/PostCommentSkeleton.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import clsx from 'clsx'; + +import styles from './PostCommentSkeleton.module.css'; +import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; +import { useGenerateStylesShadeColors } from '~/v4/core/providers/ThemeProvider'; +import { useAmityComponent } from '~/v4/core/hooks/uikit'; + +interface PostCommentSkeletonProps { + pageId?: string; +} + +export const PostCommentSkeleton = ({ pageId = '*' }: PostCommentSkeletonProps) => { + const componentId = 'post_comment'; + const { accessibilityId, isExcluded, themeStyles } = useAmityComponent({ + pageId, + componentId, + }); + + if (isExcluded) return null; + + return ( + <div + data-qa-anchor={accessibilityId} + className={clsx(styles.postCommentSkeleton, styles.postCommentSkeleton__animation)} + style={themeStyles} + > + <div + className={clsx(styles.postCommentSkeleton__avatar, styles.postCommentSkeleton__animation)} + /> + <div + className={clsx(styles.postCommentSkeleton__details, styles.postCommentSkeleton__animation)} + > + <div + className={clsx( + styles.postCommentSkeleton__content, + styles.postCommentSkeleton__animation, + )} + /> + <div className={styles.postCommentSkeleton__content__bar} /> + </div> + </div> + ); +}; diff --git a/src/v4/social/components/PostComment/index.tsx b/src/v4/social/components/PostComment/index.tsx new file mode 100644 index 000000000..55aa22524 --- /dev/null +++ b/src/v4/social/components/PostComment/index.tsx @@ -0,0 +1,2 @@ +export { PostComment } from './PostComment'; +export { PostCommentSkeleton } from './PostCommentSkeleton'; diff --git a/src/v4/social/components/PostCommentComposer/PostCommentComposer.module.css b/src/v4/social/components/PostCommentComposer/PostCommentComposer.module.css new file mode 100644 index 000000000..6596919df --- /dev/null +++ b/src/v4/social/components/PostCommentComposer/PostCommentComposer.module.css @@ -0,0 +1,67 @@ +.postCommentComposer__container { + position: relative; + display: flex; + flex-direction: row; + background-color: var(--asc-color-base-background); + align-items: flex-end; + padding: var(--asc-spacing-s1) 0 var(--asc-spacing-s1) var(--asc-spacing-m1); + border-top: 1px solid var(--asc-color-base-shade4); + width: 100%; +} + +.postCommentComposer__avatar { + width: 2rem; + height: 2rem; + margin-bottom: var(--asc-spacing-xxs2); + border-radius: var(--asc-border-radius-full); + margin-right: var(--asc-spacing-s1); +} + +.postCommentComposer__button { + border: none; + margin-bottom: var(--asc-spacing-xxs2); +} + +.postCommentComposer__button:hover { + background-color: transparent !important; +} + +.postCommentComposer__button:disabled { + color: var(--asc-color-primary-shade2); +} + +.postCommentComposer__input { + flex-grow: 1; + background-color: var(--asc-color-base-shade4); + border-radius: 1.25rem; + padding: 0.625rem 0.75rem; +} + +.postCommentComposer__replyContainer { + position: absolute; + left: 0; + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.625rem 1rem; + background-color: var(--asc-color-base-shade4); +} + +.postCommentComposer__replyContainer__text { + font-size: var(--asc-text-font-size-sm); + font-weight: var(--asc-text-font-weight-normal); + line-height: var(--asc-line-height-sm); + color: var(--asc-color-base-shade1); +} + +.postCommentComposer__replyContainer__username { + font-weight: var(--asc-text-font-weight-bold); +} + +.postCommentComposer__replyContainer__closeButton { + fill: var(--asc-color-base-shade2); + width: 1.25rem; + height: 1.25rem; + cursor: pointer; +} diff --git a/src/v4/social/components/PostCommentComposer/PostCommentComposer.tsx b/src/v4/social/components/PostCommentComposer/PostCommentComposer.tsx new file mode 100644 index 000000000..5d1c3c10c --- /dev/null +++ b/src/v4/social/components/PostCommentComposer/PostCommentComposer.tsx @@ -0,0 +1,131 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { Avatar, Button } from '~/v4/core/components/index'; +import useUser from '~/v4/core/hooks/objects/useUser'; +import useImage from '~/v4/core/hooks/useImage'; +import useSDK from '~/v4/core/hooks/useSDK'; +import User from '~/v4/icons/User'; +import { PostCommentInput, PostCommentInputRef } from './PostCommentInput'; +import styles from './PostCommentComposer.module.css'; +import { useMutation } from '@tanstack/react-query'; +import { CommentRepository } from '@amityco/ts-sdk'; +import Close from '~/v4/icons/Close'; +import { Mentionees, Metadata } from '~/v4/helpers/utils'; + +export type CreateCommentParams = { + data: { + text: string; + }; + mentionees: Mentionees; + metadata: Metadata; +}; + +export const PostCommentComposer = ({ + post, + replyTo, + onCancelReply, +}: { + post: Amity.Post; + replyTo?: Amity.Comment; + onCancelReply: () => void; +}) => { + const userId = useSDK().currentUserId; + const { user } = useUser(userId); + const avatarUrl = useImage({ fileId: user?.avatar?.fileId, imageSize: 'small' }); + const editorRef = useRef<PostCommentInputRef | null>(null); + const composerRef = useRef<HTMLDivElement | null>(null); + + const [composerHeight, setComposerHeight] = useState(0); + + const [textValue, setTextValue] = useState<CreateCommentParams>({ + data: { + text: '', + }, + mentionees: [ + { + type: 'user', + userIds: [''], + }, + ], + metadata: {}, + }); + + const onChange = (val: any) => { + setTextValue(val); + }; + + const { mutateAsync } = useMutation({ + mutationFn: async ({ params }: { params: CreateCommentParams }) => { + const referenceId = replyTo ? replyTo.referenceId : post.postId; + const referenceType = replyTo ? replyTo.referenceType : 'post'; + const parentId = replyTo ? replyTo.commentId : undefined; + + await CommentRepository.createComment({ + referenceId, + referenceType, + parentId, + ...params, + mentionees: params.mentionees as Amity.UserMention[], + }); + }, + onError: (error) => {}, + onSuccess: () => { + setTextValue({ + data: { text: '' }, + mentionees: [], + metadata: {}, + }); + editorRef.current?.clearEditorState(); + onCancelReply(); + }, + }); + + useEffect(() => { + if (composerRef.current) { + setComposerHeight(composerRef.current.offsetHeight); + } + }, []); + + return ( + <div className={styles.postCommentComposer__container} ref={composerRef}> + <div className={styles.postCommentComposer__avatar}> + <Avatar avatarUrl={avatarUrl} defaultImage={<User />} /> + </div> + <div className={styles.postCommentComposer__input}> + <PostCommentInput + ref={editorRef} + onChange={onChange} + postTargetType={post.targetType} + postTargetId={post.targetId} + mentionOffsetBottom={-composerHeight} + value={textValue} + placehoder="Say something nice..." + /> + </div> + <Button + variant="ghost" + disabled={!textValue.data.text} + className={styles.postCommentComposer__button} + onClick={() => mutateAsync({ params: textValue })} + > + Post + </Button> + {replyTo && ( + <div + className={styles.postCommentComposer__replyContainer} + style={{ bottom: composerHeight }} + > + <div className={styles.postCommentComposer__replyContainer__text}> + <span>Replying to </span> + <span className={styles.postCommentComposer__replyContainer__username}> + {replyTo?.userId} + </span> + </div> + <Close + onClick={onCancelReply} + className={styles.postCommentComposer__replyContainer__closeButton} + /> + </div> + )} + </div> + ); +}; diff --git a/src/v4/social/components/PostCommentComposer/PostCommentInput.module.css b/src/v4/social/components/PostCommentComposer/PostCommentInput.module.css new file mode 100644 index 000000000..daae1b98a --- /dev/null +++ b/src/v4/social/components/PostCommentComposer/PostCommentInput.module.css @@ -0,0 +1,45 @@ +.editorPlaceholder { + color: #999; + overflow: hidden; + position: absolute; + top: 0; + user-select: none; + pointer-events: none; +} + +.editorParagraph { + height: 100%; + width: 100%; + color: var(--asc-color-base-default); +} + +.editorParagraph span { + color: var(--asc-color-base-default); +} + +.editorContainer { + width: 100%; + height: 100%; + color: var(--asc-color-base-default); + max-height: calc(var(--asc-line-height-md) * var(--var-max-lines) + (0.62rem * 2)); + + /* padding: 0.62rem 1rem; */ + position: relative; + + p { + line-height: var(--asc-line-height-md); + font-size: var(--asc-text-font-size-md); + margin: 0; + } +} + +.editorEditableContent { + height: max-content; + max-height: calc(var(--asc-line-height-md) * var(--var-max-lines)); + overflow-y: scroll; +} + +.editorContainer :focus { + border: none; + outline: none; +} diff --git a/src/v4/social/components/PostCommentComposer/PostCommentInput.tsx b/src/v4/social/components/PostCommentComposer/PostCommentInput.tsx new file mode 100644 index 000000000..7116b39ae --- /dev/null +++ b/src/v4/social/components/PostCommentComposer/PostCommentInput.tsx @@ -0,0 +1,306 @@ +import React, { forwardRef, MutableRefObject, useEffect, useImperativeHandle } from 'react'; +import { InitialEditorStateType, LexicalComposer } from '@lexical/react/LexicalComposer'; +import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin'; +import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'; +import { ContentEditable } from '@lexical/react/LexicalContentEditable'; +import { EditorRefPlugin } from '@lexical/react/LexicalEditorRefPlugin'; +import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; +import { + $getRoot, + EditorState, + LexicalEditor, + SerializedLexicalNode, + $createParagraphNode, + $createTextNode, + SerializedTextNode, + SerializedRootNode, + SerializedParagraphNode, + RootNode, + TextNode, + ParagraphNode, +} from 'lexical'; +import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary'; +import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'; +import { AutoFocusPlugin } from '@lexical/react/LexicalAutoFocusPlugin'; +import { + MentionNode, + SerializedMentionNode, +} from '~/v4/social/internal-components/MentionTextInput/MentionNodes'; +import styles from './PostCommentInput.module.css'; +import { PostCommentMentionInput } from '../PostCommentMentionInput'; +import { useMentionUsers } from '../../hooks/useMentionUser'; +import { CreateCommentParams } from '../PostCommentComposer/PostCommentComposer'; +import { Mentioned, Mentionees, Metadata } from '~/v4/helpers/utils'; +import { text } from '../../elements/HyperLink/HyperLink.module.css'; + +const theme = { + ltr: 'ltr', + rtl: 'rtl', + placeholder: styles.editorPlaceholder, + paragraph: styles.editorParagraph, +}; + +const editorConfig = { + namespace: 'PostCommentInput', + theme: theme, + onError(error: Error) { + throw error; + }, + nodes: [MentionNode], +}; + +interface EditorStateJson extends SerializedLexicalNode { + children: []; +} + +interface PostCommentInputProps { + postTargetType: Amity.PostTargetType; + postTargetId: string; + value?: CreateCommentParams; + mentionOffsetBottom?: number; + maxLines?: number; + placehoder?: string; + ref: MutableRefObject<LexicalEditor | null | undefined>; + onChange: (data: CreateCommentParams) => void; +} + +export interface PostCommentInputRef { + clearEditorState: () => void; +} + +function editorStateToText(editor: LexicalEditor): CreateCommentParams { + const editorStateTextString: string[] = []; + const paragraphs = editor.getEditorState().toJSON().root.children as EditorStateJson[]; + + const mentioned: Mentioned[] = []; + const mentionees: { + type: Amity.Mention['type']; + userIds: string[]; + }[] = []; + let runningIndex = 0; + + paragraphs.forEach((paragraph) => { + const children = paragraph.children; + const paragraphText: string[] = []; + + children.forEach((child: { type: string; text: string; userId: string }) => { + if (child.text) { + paragraphText.push(child.text); + } + if (child.type === 'mention') { + mentioned.push({ + index: runningIndex, + length: child.text.length, + type: 'user', + userId: child.userId, + }); + + mentionees.push({ type: 'user', userIds: [child.userId] }); + } + runningIndex += child.text.length; + }); + runningIndex += 1; + editorStateTextString.push(paragraphText.join('')); + }); + + return { + data: { text: editorStateTextString.join('\n') }, + mentionees, + metadata: { + mentioned, + }, + }; +} + +function createRootNode(): SerializedRootNode<SerializedParagraphNode> { + return { + children: [], + direction: 'ltr', + format: '', + indent: 0, + type: 'root', + version: 1, + }; +} + +function createParagraphNode(): SerializedParagraphNode { + return { + children: [], + direction: 'ltr', + format: '', + indent: 0, + type: 'paragraph', + version: 1, + textFormat: 0, + }; +} + +function createSerializeTextNode(text: string): SerializedTextNode { + return { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text, + type: 'text', + version: 1, + }; +} + +function createSerializeMentionNode(mention: Mentioned): SerializedMentionNode { + return { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: ('@' + mention.userId) as string, + type: 'mention', + version: 1, + mentionName: mention.userId as string, + displayName: mention.userId as string, + userId: mention.userId as string, + userInternalId: mention.userId as string, + userPublicId: mention.userId as string, + }; +} + +export function TextToEditorState(value: { + data: { text: string }; + metadata?: { + mentioned?: Mentioned[]; + }; + mentionees?: Mentionees; +}) { + const rootNode = createRootNode(); + + const textArray = value.data.text.split('\n'); + + const mentions = value.metadata?.mentioned; + + let start = 0; + let stop = -1; + let mentionRunningIndex = 0; + + for (let i = 0; i < textArray.length; i++) { + start = stop + 1; + stop = start + textArray[i].length; + + const paragraph = createParagraphNode(); + + if (Array.isArray(mentions) && mentions?.length > 0) { + let runningIndex = 0; + + while (runningIndex < textArray[i].length) { + if (mentionRunningIndex >= mentions.length) { + paragraph.children.push(createSerializeTextNode(textArray[i].slice(runningIndex))); + runningIndex = textArray[i].length; + break; + } + + if (mentions[mentionRunningIndex].index >= stop) { + paragraph.children.push(createSerializeTextNode(textArray[i])); + runningIndex = textArray[i].length; + } else { + const text = textArray[i].slice( + runningIndex, + runningIndex + mentions[mentionRunningIndex]?.index - start, + ); + + if (text) { + paragraph.children.push(createSerializeTextNode(text)); + } + + paragraph.children.push(createSerializeMentionNode(mentions[mentionRunningIndex])); + + runningIndex += + mentions[mentionRunningIndex].index + mentions[mentionRunningIndex].length - start; + + mentionRunningIndex++; + } + } + } + + if (!mentions || mentions?.length === 0) { + const textNode = createSerializeTextNode(textArray[i]); + paragraph.children.push(textNode); + } + + rootNode.children.push(paragraph); + } + + return { root: rootNode }; +} + +export const PostCommentInput = forwardRef<PostCommentInputRef, PostCommentInputProps>( + ( + { + postTargetType, + postTargetId, + mentionOffsetBottom = 0, + value, + onChange, + maxLines = 10, + placehoder, + }, + ref, + ) => { + const editorRef = React.useRef<LexicalEditor | null | undefined>(null); + const [queryMentionUser, setQueryMentionUser] = React.useState<string | null>(null); + + const { mentionUsers } = useMentionUsers({ + displayName: queryMentionUser || '', + postTargetType: postTargetType, + postTargetId: postTargetId, + }); + + const clearEditorState = () => { + editorRef.current?.update(() => { + const root = $getRoot(); + root.clear(); + }); + }; + + useImperativeHandle(ref, () => ({ + clearEditorState, + })); + + return ( + <LexicalComposer + initialConfig={{ + ...editorConfig, + ...(value?.data.text ? { editorState: JSON.stringify(TextToEditorState(value)) } : {}), + }} + > + <div + className={styles.editorContainer} + style={ + { + '--var-max-lines': maxLines, + } as React.CSSProperties + } + > + <RichTextPlugin + contentEditable={<ContentEditable className={styles.editorEditableContent} />} + placeholder={ + placehoder ? <div className={styles.editorPlaceholder}>{placehoder}</div> : null + } + ErrorBoundary={LexicalErrorBoundary} + /> + <OnChangePlugin + onChange={(editorState, editor) => { + onChange(editorStateToText(editor)); + }} + /> + <HistoryPlugin /> + <AutoFocusPlugin /> + <EditorRefPlugin editorRef={editorRef} /> + <PostCommentMentionInput + mentionUsers={mentionUsers as Amity.User[]} + onQueryChange={(query: string) => setQueryMentionUser(query)} + offsetBottom={mentionOffsetBottom} + /> + </div> + </LexicalComposer> + ); + }, +); diff --git a/src/v4/social/components/PostCommentComposer/index.tsx b/src/v4/social/components/PostCommentComposer/index.tsx new file mode 100644 index 000000000..9226b06ef --- /dev/null +++ b/src/v4/social/components/PostCommentComposer/index.tsx @@ -0,0 +1 @@ +export { PostCommentComposer } from './PostCommentComposer'; diff --git a/src/v4/social/components/PostCommentList/PostCommentList.module.css b/src/v4/social/components/PostCommentList/PostCommentList.module.css new file mode 100644 index 000000000..fa8ccecac --- /dev/null +++ b/src/v4/social/components/PostCommentList/PostCommentList.module.css @@ -0,0 +1,18 @@ +.postCommentList__container { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.postCommentList__viewAllComments__button { + width: 100%; + padding: var(--asc-spacing-s2) 0; + margin: auto; + border-top: 0.0625rem solid var(--asc-color-base-shade4); + cursor: pointer; + text-align: center; +} + +.postCommentList__container_intersection { + height: 1px; +} diff --git a/src/v4/social/components/PostCommentList/PostCommentList.tsx b/src/v4/social/components/PostCommentList/PostCommentList.tsx new file mode 100644 index 000000000..07ed51b1c --- /dev/null +++ b/src/v4/social/components/PostCommentList/PostCommentList.tsx @@ -0,0 +1,84 @@ +import React, { useRef } from 'react'; +import styles from './PostCommentList.module.css'; +import { PostComment } from '../PostComment/PostComment'; +import useIntersectionObserver from '~/v4/core/hooks/useIntersectionObserver'; +import useCommentsCollection from '../../hooks/collections/useCommentsCollection'; +import { useAmityComponent } from '~/v4/core/hooks/uikit/index'; +import useUserSubscription from '~/v4/core/hooks/subscriptions/useUserSubscription'; +import { SubscriptionLevels } from '@amityco/ts-sdk'; +import useCommunitySubscription from '~/v4/core/hooks/subscriptions/useCommunitySubscription'; + +type PostCommentListProps = { + post: Amity.Post; + pageId?: string; + onClickReply: (comment: Amity.Comment) => void; +}; + +export const PostCommentList = ({ post, pageId = '*', onClickReply }: PostCommentListProps) => { + const componentId = 'comment_tray_component'; + + const { themeStyles, accessibilityId } = useAmityComponent({ + componentId, + pageId, + }); + + const containerRef = React.useRef<HTMLDivElement>(null); + const intersectionRef = useRef<HTMLDivElement>(null); + + const { comments, loadMore, hasMore, isLoading } = useCommentsCollection({ + referenceId: post.postId, + referenceType: 'post', + limit: 5, + includeDeleted: true, + }); + + useIntersectionObserver({ + ref: intersectionRef, + onIntersect: () => { + if (hasMore && isLoading === false) { + loadMore(); + } + }, + }); + + useUserSubscription({ + userId: post.targetId, + level: SubscriptionLevels.COMMENT, + shouldSubscribe: post.targetType === 'user', + }); + + useCommunitySubscription({ + communityId: post.targetId, + level: SubscriptionLevels.COMMENT, + shouldSubscribe: post.targetType === 'community', + }); + + if (!comments) return null; + + return ( + <div + className={styles.postCommentList__container} + style={themeStyles} + ref={containerRef} + data-qa-anchor={accessibilityId} + > + {comments.map((comment) => { + return ( + <PostComment + key={comment.commentId} + comment={comment as Amity.Comment} + onClickReply={onClickReply} + componentId={componentId} + postTargetId={post.targetId} + postTargetType={post.targetType} + /> + ); + })} + {/* TODO: add this button when implement desktop view */} + {/* <div className={styles.postCommentList__viewAllComments__button}> + <Typography.BodyBold>View all comments...</Typography.BodyBold> + </div> */} + <div ref={intersectionRef} className={styles.postCommentList__container_intersection} /> + </div> + ); +}; diff --git a/src/v4/social/components/PostCommentList/index.ts b/src/v4/social/components/PostCommentList/index.ts new file mode 100644 index 000000000..646fb933f --- /dev/null +++ b/src/v4/social/components/PostCommentList/index.ts @@ -0,0 +1 @@ +export { PostCommentList } from './PostCommentList'; diff --git a/src/v4/social/components/PostCommentMentionInput/PostCommentMentionInput.module.css b/src/v4/social/components/PostCommentMentionInput/PostCommentMentionInput.module.css new file mode 100644 index 000000000..2b4a017b7 --- /dev/null +++ b/src/v4/social/components/PostCommentMentionInput/PostCommentMentionInput.module.css @@ -0,0 +1,12 @@ +.mentionTextInput_item { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 12.5rem; + overflow-y: scroll; +} + +.mentionTextInput_item::-webkit-scrollbar { + display: none; +} diff --git a/src/v4/social/components/PostCommentMentionInput/PostCommentMentionInput.tsx b/src/v4/social/components/PostCommentMentionInput/PostCommentMentionInput.tsx new file mode 100644 index 000000000..b747ca970 --- /dev/null +++ b/src/v4/social/components/PostCommentMentionInput/PostCommentMentionInput.tsx @@ -0,0 +1,223 @@ +import styles from './PostCommentMentionInput.module.css'; +import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; +import { + LexicalTypeaheadMenuPlugin, + MenuOption, + MenuTextMatch, +} from '@lexical/react/LexicalTypeaheadMenuPlugin'; +import { TextNode } from 'lexical'; +import React, { RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import ReactDOM from 'react-dom'; +import { $createMentionNode } from '../../internal-components/MentionTextInput/MentionNodes'; +import { CommunityMember } from '../../internal-components/CommunityMember/CommunityMember'; + +const MAX_LENGTH = 5000; + +const PUNCTUATION = '\\.,\\+\\*\\?\\$\\@\\|#{}\\(\\)\\^\\-\\[\\]\\\\/!%\'"~=<>_:;'; +const NAME = '\\b[A-Z][^\\s' + PUNCTUATION + ']'; + +const DocumentMentionsRegex = { + NAME, + PUNCTUATION, +}; + +const PUNC = DocumentMentionsRegex.PUNCTUATION; + +const TRIGGERS = ['@'].join(''); + +// Chars we expect to see in a mention (non-space, non-punctuation). +const VALID_CHARS = '[^' + TRIGGERS + PUNC + '\\s]'; + +// Non-standard series of chars. Each series must be preceded and followed by +// a valid char. +const VALID_JOINS = + '(?:' + + '\\.[ |$]|' + // E.g. "r. " in "Mr. Smith" + ' |' + // E.g. " " in "Josh Duck" + '[' + + PUNC + + ']|' + // E.g. "-' in "Salier-Hellendag" + ')'; + +const LENGTH_LIMIT = 75; + +const AtSignMentionsRegex = new RegExp( + '(^|\\s|\\()(' + + '[' + + TRIGGERS + + ']' + + '((?:' + + VALID_CHARS + + VALID_JOINS + + '){0,' + + LENGTH_LIMIT + + '})' + + ')$', +); + +// 50 is the longest alias length limit. +const ALIAS_LENGTH_LIMIT = 50; + +// Regex used to match alias. +const AtSignMentionsRegexAliasRegex = new RegExp( + '(^|\\s|\\()(' + + '[' + + TRIGGERS + + ']' + + '((?:' + + VALID_CHARS + + '){0,' + + ALIAS_LENGTH_LIMIT + + '})' + + ')$', +); + +function checkForAtSignMentions(text: string, minMatchLength: number): MenuTextMatch | null { + let match = AtSignMentionsRegex.exec(text); + + if (match === null) { + match = AtSignMentionsRegexAliasRegex.exec(text); + } + if (match !== null) { + // The strategy ignores leading whitespace but we need to know it's + // length to add it to the leadOffset + const maybeLeadingWhitespace = match[1]; + + const matchingString = match[3]; + if (matchingString.length >= minMatchLength) { + return { + leadOffset: match.index + maybeLeadingWhitespace.length, + matchingString, + replaceableString: match[2], + }; + } + } + return null; +} + +function getPossibleQueryMatch(text: string): MenuTextMatch | null { + return checkForAtSignMentions(text, 1); +} + +export class MentionTypeaheadOption extends MenuOption { + user: Amity.User; + + constructor(user: Amity.User) { + super(user.userId); + this.user = user; + } +} + +export const PostCommentMentionInput = ({ + mentionUsers, + offsetBottom = 0, + onQueryChange, +}: { + mentionUsers: Amity.User[]; + offsetBottom?: number; + onQueryChange: (query: string) => void; +}) => { + const mentionTextInputItemRef = useRef<HTMLDivElement>(null); + return ( + <div> + <div ref={mentionTextInputItemRef}></div> + <Mention + anchorRef={mentionTextInputItemRef} + mentionUsers={mentionUsers} + offsetBottom={offsetBottom} + onQueryChange={onQueryChange} + /> + </div> + ); +}; + +type MentionProps = { + anchorRef: RefObject<HTMLDivElement>; + mentionUsers: Amity.User[]; + offsetBottom?: number; + onQueryChange: (queryString: string) => void; +}; + +function Mention({ anchorRef, mentionUsers, offsetBottom = 0, onQueryChange }: MentionProps) { + const [editor] = useLexicalComposerContext(); + + const [queryString, setQueryString] = useState<string | null>(null); + + useEffect(() => { + queryString && onQueryChange(queryString); + }, [queryString]); + + const options = useMemo( + () => mentionUsers.map((user) => new MentionTypeaheadOption(user)), + [mentionUsers], + ); + + const onSelectOption = useCallback( + ( + selectedOption: MentionTypeaheadOption, + nodeToReplace: TextNode | null, + closeMenu: () => void, + ) => { + editor.update(() => { + const mentionNode = $createMentionNode({ + mentionName: selectedOption.key, + displayName: selectedOption.user.displayName, + userId: selectedOption.user.userId, + }); + if (nodeToReplace) { + nodeToReplace.replace(mentionNode); + } + mentionNode.select(); + closeMenu(); + }); + }, + [editor], + ); + + const checkForMentionMatch = useCallback( + (text: string) => { + return getPossibleQueryMatch(text); + }, + [editor], + ); + + return ( + <LexicalTypeaheadMenuPlugin + onQueryChange={setQueryString} + onSelectOption={onSelectOption} + triggerFn={checkForMentionMatch} + options={options} + menuRenderFn={( + anchorElementRef, + { selectedIndex, selectOptionAndCleanUp, setHighlightedIndex }, + ) => + anchorRef.current && options.length + ? ReactDOM.createPortal( + <div + className={styles.mentionTextInput_item} + style={{ + transform: `translateY(${offsetBottom}px)`, + }} + > + {options.map((option, i: number) => ( + <CommunityMember + isSelected={selectedIndex === i} + onClick={() => { + setHighlightedIndex(i); + selectOptionAndCleanUp(option); + }} + onMouseEnter={() => { + setHighlightedIndex(i); + }} + key={option.key} + option={option} + /> + ))} + </div>, + anchorRef.current, + ) + : null + } + /> + ); +} diff --git a/src/v4/social/components/PostCommentMentionInput/PostCommentMentionNode.tsx b/src/v4/social/components/PostCommentMentionInput/PostCommentMentionNode.tsx new file mode 100644 index 000000000..64a89d867 --- /dev/null +++ b/src/v4/social/components/PostCommentMentionInput/PostCommentMentionNode.tsx @@ -0,0 +1,194 @@ +import type { Spread } from 'lexical'; + +import { + DOMConversionMap, + DOMConversionOutput, + DOMExportOutput, + EditorConfig, + LexicalNode, + NodeKey, + SerializedTextNode, + TextNode, +} from 'lexical'; + +export type SerializedMentionNode = Spread< + { + mentionName: string; + displayName?: string; + userId?: string; + userInternalId?: string; + userPublicId?: string; + type: 'mention'; + version: 1; + }, + SerializedTextNode +>; + +function convertMentionElement(domNode: HTMLElement): DOMConversionOutput | null { + const textContent = domNode.textContent; + + if (textContent !== null) { + const node = $createMentionNode({ mentionName: textContent }); + return { + node, + }; + } + + return null; +} + +const mentionStyle = `color: var(--asc-color-primary-default)`; + +export class MentionNode extends TextNode { + __mention: string; + __displayName: string | undefined; + __userId: string | undefined; + __userInternalId: string | undefined; + __userPublicId: string | undefined; + + static getType(): string { + return 'mention'; + } + + static clone(node: MentionNode): MentionNode { + return new MentionNode({ + mentionName: node.__mention, + displayName: node.__displayName, + userId: node.__userId, + userInternalId: node.__userInternalId, + userPublicId: node.__userPublicId, + text: node.__text, + key: node.__key, + }); + } + static importJSON(serializedNode: SerializedMentionNode): MentionNode { + const node = $createMentionNode({ + mentionName: serializedNode.mentionName, + displayName: serializedNode.displayName, + userId: serializedNode.userId, + userInternalId: serializedNode.userInternalId, + userPublicId: serializedNode.userPublicId, + }); + node.setTextContent(serializedNode.text); + node.setFormat(serializedNode.format); + node.setDetail(serializedNode.detail); + node.setMode(serializedNode.mode); + node.setStyle(serializedNode.style); + return node; + } + + constructor({ + mentionName, + displayName, + userId, + userInternalId, + userPublicId, + text, + key, + }: { + mentionName: string; + text?: string; + key?: NodeKey; + displayName?: string; + userId?: string; + userInternalId?: string; + userPublicId?: string; + }) { + super(text ?? mentionName, key); + this.__mention = mentionName; + this.__displayName = displayName; + this.__userId = userId; + this.__userInternalId = userInternalId; + this.__userPublicId = userPublicId; + } + + exportJSON(): SerializedMentionNode { + return { + ...super.exportJSON(), + mentionName: this.__mention, + displayName: this.__displayName, + userId: this.__userId, + userInternalId: this.__userInternalId, + userPublicId: this.__userPublicId, + type: 'mention', + version: 1, + }; + } + + createDOM(config: EditorConfig): HTMLElement { + const dom = super.createDOM(config); + dom.style.cssText = mentionStyle; //class name css + dom.className = 'mention'; //create css + return dom; + } + + exportDOM(): DOMExportOutput { + const element = document.createElement('span'); + element.setAttribute('data-lexical-mention', 'true'); + element.textContent = this.__text; + return { element }; + } + + isSegmented(): false { + return false; + } + + static importDOM(): DOMConversionMap | null { + return { + span: (domNode: HTMLElement) => { + if (!domNode.hasAttribute('data-lexical-mention')) { + return null; + } + return { + conversion: convertMentionElement, + priority: 1, + }; + }, + }; + } + + isTextEntity(): true { + return true; + } + + isToken(): true { + return true; + } + + canInsertTextBefore(): boolean { + return false; + } + + canInsertTextAfter(): boolean { + return false; + } +} + +export function $createMentionNode({ + mentionName, + displayName, + userId, + userInternalId, + userPublicId, +}: { + mentionName: string; + displayName?: string; + userId?: string; + userInternalId?: string; + userPublicId?: string; +}): MentionNode { + const mentionNode = new MentionNode({ + mentionName: `@${mentionName}`, + displayName: displayName, + userId: userId, + userInternalId: userInternalId, + userPublicId: userPublicId, + }) + .setMode('segmented') + .toggleDirectionless(); + return mentionNode; +} + +export function $isMentionNode(node: LexicalNode | null | undefined): node is MentionNode { + return node instanceof MentionNode; +} diff --git a/src/v4/social/components/PostCommentMentionInput/PostMentionUser.module.css b/src/v4/social/components/PostCommentMentionInput/PostMentionUser.module.css new file mode 100644 index 000000000..b4b048958 --- /dev/null +++ b/src/v4/social/components/PostCommentMentionInput/PostMentionUser.module.css @@ -0,0 +1,27 @@ +.communityMember__item { + width: 100%; + display: flex; + align-items: center; + padding: 0.5rem 1rem; + background-color: var(--asc-color-base-background); + color: var(--asc-color-base-default); +} + +.communityMember__item:focus { + background-color: var(--asc-color-base-shade4); +} + +.communityMember__avatar { + width: 2.5rem; + height: 2.5rem; +} + +.communityMember__displayName { + margin-left: 0.5rem; + font-size: 1rem; + display: block; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + color: var(--asc-color-base-default); +} diff --git a/src/v4/social/components/PostCommentMentionInput/PostMentionUser.tsx b/src/v4/social/components/PostCommentMentionInput/PostMentionUser.tsx new file mode 100644 index 000000000..2925cf29c --- /dev/null +++ b/src/v4/social/components/PostCommentMentionInput/PostMentionUser.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import styles from './PostMentionUser.module.css'; +import { UserAvatar } from '~/v4/social/internal-components/UserAvatar/UserAvatar'; +import { MentionTypeaheadOption } from './PostCommentMentionInput'; + +interface PostMentionUserProps { + isSelected: boolean; + onClick: () => void; + onMouseEnter: () => void; + option: MentionTypeaheadOption; +} + +export function PostMentionUser({ + isSelected, + onClick, + onMouseEnter, + option, +}: PostMentionUserProps) { + let className = 'item'; + if (isSelected) { + className += ' selected'; + } + + return ( + <div + key={option.key} + tabIndex={-1} + className={className} + ref={option.setRefElement} + role="option" + aria-selected={isSelected} + onMouseEnter={onMouseEnter} + onClick={onClick} + > + <div key={option.key} className={styles.communityMember__item}> + <div> + <UserAvatar + className={styles.communityMember__avatar} + userId={option.user.avatarFileId} + /> + </div> + <p className={styles.communityMember__displayName}>{option.user.displayName}</p> + </div> + </div> + ); +} diff --git a/src/v4/social/components/PostCommentMentionInput/index.ts b/src/v4/social/components/PostCommentMentionInput/index.ts new file mode 100644 index 000000000..485bb6726 --- /dev/null +++ b/src/v4/social/components/PostCommentMentionInput/index.ts @@ -0,0 +1 @@ +export { PostCommentMentionInput } from './PostCommentMentionInput'; diff --git a/src/v4/social/components/PostReplyComment/PostReplyComment.module.css b/src/v4/social/components/PostReplyComment/PostReplyComment.module.css new file mode 100644 index 000000000..c8e13327b --- /dev/null +++ b/src/v4/social/components/PostReplyComment/PostReplyComment.module.css @@ -0,0 +1,197 @@ +.postReplyComment { + width: 100%; + display: grid; + grid-template-columns: min-content 1fr; + gap: 0.5rem; +} + +.postReplyComment__avatar { + margin-right: 0.75rem; + display: inline-block; +} + +.postReplyComment__details { + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.postReplyComment__content { + display: flex; + flex-direction: column; + gap: 0.25rem; + justify-content: center; + align-items: start; + background-color: var(--asc-color-base-shade4); + border-radius: 0 0.75rem 0.75rem; + padding: 0.75rem; + max-width: max-content; +} + +.postReplyComment__content__username { + color: var(--asc-color-base-default); +} + +.postReplyComment__content__text { + color: var(--asc-color-base-default); +} + +.postReplyComment__secondRow { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; +} + +.postReplyComment__secondRow__leftPane { + display: flex; + gap: 0.75rem; + align-items: center; +} + +.postReplyComment__secondRow__rightPane { + display: flex; + gap: 0.25rem; + align-items: center; +} + +.postReplyComment__secondRow__rightPane__like { + width: 1.25rem; + height: 1.25rem; +} + +.postReplyComment__secondRow__like { + cursor: pointer; + color: var(--asc-color-base-shade2); +} + +.postReplyComment__secondRow__like[data-is-liked='true'] { + color: var(--asc-color-primary-default); +} + +.postReplyComment__secondRow__reply { + color: var(--asc-color-base-shade2); +} + +.postReplyComment__secondRow__actionButton { + fill: var(--asc-color-secondary-shade2); + cursor: pointer; + width: 1.25rem; + height: 1.25rem; +} + +.postReplyComment__viewReply_button { + display: flex; + padding: 0.3125rem 0.75rem 0.3125rem 0.5rem; + cursor: pointer; + align-items: flex-start; + gap: 0.25rem; + background: var(--asc-color-base-background); + border: 1px solid var(--asc-color-base-shade4); + border-radius: 0.25rem; + width: max-content; +} + +.postReplyComment__viewReply_icon { + color: var(--asc-color-secondary-shade1); + width: 1rem; + height: 1rem; +} + +.postReplyComment__viewReply_text { + color: var(--asc-color-secondary-shade1); +} + +.postReplyComment__actionButton { + display: flex; + gap: 0.75rem; + padding: 1rem 0; + align-items: center; + cursor: pointer; +} + +.postReplyComment__actionButton__icon { + color: var(--asc-color-base-default); + width: 1.5rem; + height: 1.5rem; +} + +.postReplyComment__actionButton__text { + color: var(--asc-color-base-default); +} + +.postReplyComment__deleteComment_container { + display: flex; + padding: 0.31rem 0.5rem; + gap: 0.25rem; + background-color: var(--asc-color-secondary-shade4); + border-radius: var(--asc-border-radius-sm); + width: max-content; + + /* justify-content: center; */ + align-items: center; + margin-bottom: var(--asc-spacing-s2); +} + +.postReplyComment__deleteComment_icon { + width: 1rem; + height: 1rem; + color: var(--asc-color-secondary-shade2); +} + +.postReplyComment__deleteComment_text { + color: var(--asc-color-secondary-shade2); +} + +.postReplyComment__edit { + display: flex; + gap: 0.5rem; +} + +.postReplyComment__edit__inputWrap { + width: 100%; +} + +.postReplyComment__edit__input { + display: flex; + height: 7.5rem; + padding: 0.75rem; + flex-direction: column; + align-items: flex-start; + gap: 0.25rem; + align-self: stretch; + background-color: var(--asc-color-base-shade4); + width: 100%; + border-radius: 0 0.75rem 0.75rem; +} + +.postReplyComment__edit__buttonWrap { + display: flex; + gap: var(--asc-spacing-s1); + justify-content: flex-end; + margin-top: var(--asc-spacing-s1); +} + +.postReplyComment__edit__button { + padding: 0.375rem 0.75rem; + cursor: pointer; +} + +.postReplyComment__edit__cancelButton { + color: var(--asc-color-secondary-shade1); + border-radius: var(--asc-border-radius-sm); + border: 1px solid var(--asc-color-secondary-shade1); + background: var(--asc-color-base-background); +} + +.postReplyComment__edit__saveButton { + color: var(--asc-color-white); + border-radius: var(--asc-border-radius-sm); + background: var(--asc-color-primary-default); +} + +.postReplyComment__secondRow__timestamp { + display: flex; + color: var(--asc-color-base-shade2); + white-space: pre; +} diff --git a/src/v4/social/components/PostReplyComment/PostReplyComment.tsx b/src/v4/social/components/PostReplyComment/PostReplyComment.tsx new file mode 100644 index 000000000..022df5e6b --- /dev/null +++ b/src/v4/social/components/PostReplyComment/PostReplyComment.tsx @@ -0,0 +1,222 @@ +import { CommentRepository, ReactionRepository } from '@amityco/ts-sdk'; +import clsx from 'clsx'; +import React, { useCallback, useState } from 'react'; +import EllipsisH from '~/icons/EllipsisH'; +import { BottomSheet, Typography } from '~/v4/core/components/index'; +import useCommentSubscription from '~/v4/core/hooks/subscriptions/useCommentSubscription'; +import { useAmityComponent } from '~/v4/core/hooks/uikit/index'; +import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; +import { Mentionees } from '~/v4/helpers/utils'; +import { LIKE_REACTION_KEY } from '../../constants/reactions'; +import { EditCancelButton } from '../../elements/EditCancelButton/EditCancelButton'; +import { SaveButton } from '../../elements/index'; +import { ModeratorBadge } from '../../elements/ModeratorBadge/ModeratorBadge'; +import Like from '../../elements/ReactionButton/Like'; +import { Timestamp } from '../../elements/Timestamp/Timestamp'; +import { MinusCircleIcon } from '../../icons/index'; +import { TextWithMention } from '../../internal-components/TextWithMention/TextWithMention'; +import { UserAvatar } from '../../internal-components/UserAvatar/UserAvatar'; +import { CommentOptions } from '../CommentOptions/CommentOptions'; +import { CreateCommentParams } from '../PostCommentComposer/PostCommentComposer'; +import { PostCommentInput } from '../PostCommentComposer/PostCommentInput'; +import styles from './PostReplyComment.module.css'; + +type PostReplyCommentProps = { + pageId?: string; + postTargetId: string; + postTargetType: Amity.PostTargetType; + comment: Amity.Comment; +}; + +const PostReplyComment = ({ + pageId = '*', + postTargetId, + postTargetType, + comment, +}: PostReplyCommentProps) => { + const componentId = 'post_comment'; + const { confirm } = useConfirmContext(); + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityComponent({ + pageId, + componentId, + }); + + const [bottomSheetOpen, setBottomSheetOpen] = useState(false); + const [isEditing, setIsEditing] = useState(false); + const [commentData, setCommentData] = useState<CreateCommentParams>(); + + const isLiked = (comment.myReactions || []).some((reaction) => reaction === 'like'); + + const toggleBottomSheet = () => setBottomSheetOpen((prev) => !prev); + + const deleteComment = async () => + comment.commentId && CommentRepository.deleteComment(comment.commentId); + + const handleEditComment = () => { + setIsEditing(true); + }; + + const handleDeleteComment = () => { + confirm({ + pageId, + componentId, + title: 'Delete reply', + content: 'This reply will be permanently removed.', + cancelText: 'Cancel', + okText: 'Delete', + onOk: deleteComment, + }); + }; + + const handleLike = async () => { + if (!comment) return; + + if (!isLiked) { + await ReactionRepository.addReaction('comment', comment?.commentId, LIKE_REACTION_KEY); + } else { + await ReactionRepository.removeReaction('comment', comment?.commentId, LIKE_REACTION_KEY); + } + }; + + const handleSaveComment = useCallback(async () => { + if (!commentData || !comment.commentId) return; + + await CommentRepository.updateComment(comment.commentId, { + data: commentData.data, + mentionees: commentData.mentionees as Amity.UserMention[], + metadata: commentData.metadata, + }); + + setIsEditing(false); + }, [commentData]); + + useCommentSubscription({ + commentId: comment.commentId, + }); + + if (isExcluded) return null; + + return ( + <> + {comment.isDeleted ? ( + <div className={styles.postReplyComment__deleteComment_container} style={themeStyles}> + <MinusCircleIcon className={styles.postReplyComment__deleteComment_icon} /> + <Typography.Caption className={styles.postReplyComment__deleteComment_text}> + This reply has been deleted + </Typography.Caption> + </div> + ) : isEditing ? ( + <div className={styles.postReplyComment__edit}> + <UserAvatar userId={comment.userId} /> + <div className={styles.postReplyComment__edit__inputWrap}> + <div className={styles.postReplyComment__edit__input}> + <PostCommentInput + postTargetType={postTargetType} + postTargetId={postTargetId} + value={{ + data: { + text: (comment.data as Amity.ContentDataText).text, + }, + mentionees: comment.mentionees as Mentionees, + metadata: comment.metadata || {}, + }} + onChange={(value: CreateCommentParams) => { + setCommentData(value); + }} + /> + </div> + <div className={styles.postReplyComment__edit__buttonWrap}> + <EditCancelButton + componentId="edit_comment_component" + className={clsx( + styles.postReplyComment__edit__button, + styles.postReplyComment__edit__cancelButton, + )} + onPress={() => { + setIsEditing(false); + }} + /> + <SaveButton + className={clsx( + styles.postReplyComment__edit__button, + styles.postReplyComment__edit__saveButton, + )} + componentId="edit_comment_component" + onPress={handleSaveComment} + /> + </div> + </div> + </div> + ) : ( + <div + className={styles.postReplyComment} + style={themeStyles} + data-qa-anchor={accessibilityId} + > + <UserAvatar userId={comment.userId} /> + <div className={styles.postReplyComment__details}> + <div className={styles.postReplyComment__content}> + <Typography.BodyBold className={styles.postReplyComment__content__username}> + {comment.creator?.displayName} + </Typography.BodyBold> + + <ModeratorBadge pageId={pageId} componentId={componentId} /> + + <TextWithMention + data={{ text: (comment.data as Amity.ContentDataText).text }} + mentionees={comment.mentionees as Amity.UserMention[]} + metadata={comment.metadata} + /> + </div> + <div className={styles.postReplyComment__secondRow}> + <div className={styles.postReplyComment__secondRow__leftPane}> + <Typography.Caption className={styles.postReplyComment__secondRow__timestamp}> + <Timestamp + pageId={pageId} + componentId={componentId} + timestamp={comment.createdAt} + /> + {comment.createdAt !== comment.editedAt && ' (edited)'} + </Typography.Caption> + <div onClick={handleLike}> + <Typography.CaptionBold + className={styles.postReplyComment__secondRow__like} + data-is-liked={isLiked} + > + {isLiked ? 'Liked' : 'Like'} + </Typography.CaptionBold> + </div> + <EllipsisH + className={styles.postReplyComment__secondRow__actionButton} + onClick={() => setBottomSheetOpen(true)} + /> + </div> + {comment.reactionsCount > 0 && ( + <div className={styles.postReplyComment__secondRow__rightPane}> + <Typography.Caption>{comment.reactionsCount}</Typography.Caption> + <Like className={styles.postReplyComment__secondRow__rightPane__like} /> + </div> + )} + </div> + </div> + </div> + )} + <BottomSheet + onClose={toggleBottomSheet} + isOpen={bottomSheetOpen} + mountPoint={document.getElementById('asc-uikit-post-comment') as HTMLElement} + detent="content-height" + > + <CommentOptions + comment={comment} + onCloseBottomSheet={toggleBottomSheet} + handleEditComment={handleEditComment} + handleDeleteComment={handleDeleteComment} + /> + </BottomSheet> + </> + ); +}; + +export default PostReplyComment; diff --git a/src/v4/social/components/PostReplyCommentList/PostReplyCommentList.module.css b/src/v4/social/components/PostReplyCommentList/PostReplyCommentList.module.css new file mode 100644 index 000000000..fd5a7905d --- /dev/null +++ b/src/v4/social/components/PostReplyCommentList/PostReplyCommentList.module.css @@ -0,0 +1,21 @@ +.postReplyCommentList__viewReply_button { + display: flex; + padding: 0.3125rem 0.75rem 0.3125rem 0.5rem; + cursor: pointer; + align-items: flex-start; + gap: 0.25rem; + background: var(--asc-color-base-background); + border: 1px solid var(--asc-color-base-shade4); + border-radius: 0.25rem; + width: max-content; +} + +.postReplyCommentList__viewReply_icon { + color: var(--asc-color-secondary-shade1); + width: 1rem; + height: 1rem; +} + +.postReplyCommentList__viewReply_text { + color: var(--asc-color-secondary-shade1); +} diff --git a/src/v4/social/components/PostReplyCommentList/PostReplyCommentList.tsx b/src/v4/social/components/PostReplyCommentList/PostReplyCommentList.tsx new file mode 100644 index 000000000..c14fda533 --- /dev/null +++ b/src/v4/social/components/PostReplyCommentList/PostReplyCommentList.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { Typography } from '~/v4/core/components/index'; +import ReplyComment from '~/v4/icons/ReplyComment'; +import useCommentsCollection from '../../hooks/collections/useCommentsCollection'; +import PostReplyComment from '../PostReplyComment/PostReplyComment'; +import styles from './PostReplyCommentList.module.css'; + +interface PostReplyCommentProps { + postTargetType: Amity.PostTargetType; + postTargetId: string; + referenceId: string; + parentId: string; +} + +export const PostReplyCommentList = ({ + referenceId, + postTargetId, + postTargetType, + parentId, +}: PostReplyCommentProps) => { + const { comments, hasMore, loadMore } = useCommentsCollection({ + referenceId: referenceId, + referenceType: 'post', + parentId: parentId, + limit: 5, + shouldCall: true, + includeDeleted: true, + }); + + const handleClickLoadMore = () => { + loadMore(); + }; + + return ( + <div> + {comments.map((comment) => { + return ( + <PostReplyComment + postTargetId={postTargetId} + postTargetType={postTargetType} + comment={comment as Amity.Comment} + /> + ); + })} + {hasMore && ( + <div + className={styles.postReplyCommentList__viewReply_button} + onClick={handleClickLoadMore} + > + <ReplyComment className={styles.postReplyCommentList__viewReply_icon} /> + <Typography.CaptionBold className={styles.postReplyCommentList__viewReply_text}> + View more replies + </Typography.CaptionBold> + </div> + )} + </div> + ); +}; diff --git a/src/v4/social/elements/EditCancelButton/EditCancelButton.tsx b/src/v4/social/elements/EditCancelButton/EditCancelButton.tsx index 2350e2156..9de852f0e 100644 --- a/src/v4/social/elements/EditCancelButton/EditCancelButton.tsx +++ b/src/v4/social/elements/EditCancelButton/EditCancelButton.tsx @@ -1,3 +1,4 @@ +import clsx from 'clsx'; import React from 'react'; import { Typography } from '~/v4/core/components'; import { useAmityElement } from '~/v4/core/hooks/uikit'; @@ -7,12 +8,14 @@ import styles from './EditCancelButton.module.css'; interface EditCancelButtonProps { pageId?: string; componentId?: string; + className?: string; onPress?: ButtonProps['onPress']; } export const EditCancelButton = ({ pageId = '*', componentId = '*', + className, onPress = () => {}, }: EditCancelButtonProps) => { const elementId = 'edit_cancel_button'; @@ -27,7 +30,7 @@ export const EditCancelButton = ({ return ( <Button data-qa-anchor={accessibilityId} - className={styles.editCancelButton} + className={clsx(styles.editCancelButton, className)} style={{ ...themeStyles, backgroundColor: config.background_color as string | undefined, diff --git a/src/v4/social/elements/SaveButton/SaveButton.module.css b/src/v4/social/elements/SaveButton/SaveButton.module.css new file mode 100644 index 000000000..4e4f701bf --- /dev/null +++ b/src/v4/social/elements/SaveButton/SaveButton.module.css @@ -0,0 +1,11 @@ +.saveButton { + cursor: pointer; + color: var(--asc-color-white); + background-color: var(--asc-color-primary-default); +} + +.saveButton:disabled { + cursor: pointer; + color: var(--asc-color-white); + background-color: var(--asc-color-primary-shade2); +} diff --git a/src/v4/social/elements/SaveButton/SaveButton.tsx b/src/v4/social/elements/SaveButton/SaveButton.tsx index 5c051389a..e6f622223 100644 --- a/src/v4/social/elements/SaveButton/SaveButton.tsx +++ b/src/v4/social/elements/SaveButton/SaveButton.tsx @@ -1,24 +1,25 @@ import React from 'react'; -import clsx from 'clsx'; import { useAmityElement } from '~/v4/core/hooks/uikit'; +import styles from './SaveButton.module.css'; -import { Button } from '~/v4/core/components'; +import { Button } from '~/v4/core/natives/Button'; +import clsx from 'clsx'; interface SaveButtonProps { pageId?: string; componentId?: string; className?: string; - onClick?: () => void; + onPress?: () => void; } export function SaveButton({ pageId = '*', componentId = '*', - onClick, + onPress, className, }: SaveButtonProps) { const elementId = 'save_button'; - const { accessibilityId, config, isExcluded } = useAmityElement({ + const { accessibilityId, config, isExcluded, themeStyles } = useAmityElement({ pageId, componentId, elementId, @@ -28,12 +29,12 @@ export function SaveButton({ return ( <Button - variant="primary" - className={clsx(className)} + className={clsx(styles.saveButton, className)} data-qa-anchor={accessibilityId} - onClick={onClick} + style={{ ...themeStyles, backgroundColor: config.background_color as string | undefined }} + onPress={onPress} > - {config.text} + {config.save_button_text} </Button> ); } diff --git a/src/v4/social/hooks/useCommentFlaggedByMe.ts b/src/v4/social/hooks/useCommentFlaggedByMe.ts index 50fbfa908..9613a3496 100644 --- a/src/v4/social/hooks/useCommentFlaggedByMe.ts +++ b/src/v4/social/hooks/useCommentFlaggedByMe.ts @@ -45,15 +45,28 @@ export const useCommentFlaggedByMe = (commentId?: string) => { } }; - const toggleFlagComment = async () => { + const toggleFlagComment = async (arg?: { + onFlagSuccess?: () => void; + onUnFlagSuccss?: () => void; + }) => { if (commentId == null) return; if (isFlaggedByMe) { await unflagComment(); + + if (arg?.onUnFlagSuccss) { + return arg.onUnFlagSuccss(); + } + notification.success({ content: 'Comment unreported', }); } else { await flagComment(); + + if (arg?.onFlagSuccess) { + return arg.onFlagSuccess(); + } + notification.success({ content: 'Comment reported', }); diff --git a/src/v4/social/hooks/useMentionUser.ts b/src/v4/social/hooks/useMentionUser.ts new file mode 100644 index 000000000..7ea33569c --- /dev/null +++ b/src/v4/social/hooks/useMentionUser.ts @@ -0,0 +1,50 @@ +import { CommunityRepository, UserRepository } from '@amityco/ts-sdk'; +import useLiveCollection from '~/v4/core/hooks/useLiveCollection'; +import useCommunity from '~/social/hooks/useCommunity'; + +export type MentionUser = Amity.Membership<'community'> | Amity.User; + +export const useMentionUsers = ({ + displayName, + limit = 10, + postTargetType, + postTargetId, +}: { + displayName: string; + limit?: number; + postTargetType: Amity.PostTargetType; + postTargetId: string; +}) => { + const community = useCommunity(postTargetType === 'community' ? postTargetId : undefined); + + const fetcher = + postTargetType === 'community' && community && !community.isPublic + ? CommunityRepository.Membership.getMembers + : UserRepository.getUsers; + + const params = + postTargetType === 'community' && community && !community.isPublic + ? ({ + communityId: postTargetId, + displayName, + limit, + } as Amity.SearchCommunityMemberLiveCollection) + : ({ displayName, limit } as Amity.UserLiveCollection); + + const { items, ...rest } = useLiveCollection< + Amity.Membership<'community'> | Amity.User, + Amity.SearchCommunityMemberLiveCollection | Amity.UserLiveCollection + >({ + fetcher: fetcher as any, + params, + shouldCall: (postTargetType === 'community' && !!community) || postTargetType === 'user', + }); + + return { + mentionUsers: items.map((item) => { + if ('user' in item) return item.user; + return item; + }), + ...rest, + }; +}; diff --git a/src/v4/social/internal-components/TextWithMention/TextWithMention.module.css b/src/v4/social/internal-components/TextWithMention/TextWithMention.module.css new file mode 100644 index 000000000..05cbd073d --- /dev/null +++ b/src/v4/social/internal-components/TextWithMention/TextWithMention.module.css @@ -0,0 +1,9 @@ +.textWithMention__mention { + color: var(--asc-color-primary-default); + display: inline; +} + +.textWithMention__text { + color: var(--asc-color-base-default); + display: inline; +} diff --git a/src/v4/social/internal-components/TextWithMention/TextWithMention.tsx b/src/v4/social/internal-components/TextWithMention/TextWithMention.tsx new file mode 100644 index 000000000..4a92a2ed0 --- /dev/null +++ b/src/v4/social/internal-components/TextWithMention/TextWithMention.tsx @@ -0,0 +1,47 @@ +import { SerializedTextNode } from 'lexical'; +import React, { useMemo } from 'react'; +import { Typography } from '~/v4/core/components/index'; +import { Mentioned } from '~/v4/helpers/utils'; +import { TextToEditorState } from '../../components/PostCommentComposer/PostCommentInput'; +import styles from './TextWithMention.module.css'; + +interface TextWithMentionProps { + data: { + text: string; + }; + mentionees: Amity.UserMention[]; + metadata?: { + mentioned?: Mentioned[]; + }; +} + +export const TextWithMention = (props: TextWithMentionProps) => { + const editorState = useMemo(() => { + return TextToEditorState(props); + }, [props]); + + return ( + <> + {editorState.root.children.map((child, index) => { + return ( + <Typography.Body key={index}> + {child.children.map((text, index) => { + if ((text as SerializedTextNode).type === 'mention') { + return ( + <span key={index} className={styles.textWithMention__mention}> + {(text as SerializedTextNode).text} + </span> + ); + } + return ( + <span key={index} className={styles.textWithMention__text}> + {(text as SerializedTextNode).text} + </span> + ); + })} + </Typography.Body> + ); + })} + </> + ); +}; diff --git a/src/v4/social/pages/PostDetailPage/PostDetailPage.module.css b/src/v4/social/pages/PostDetailPage/PostDetailPage.module.css index 3818cf16c..638bfdc2a 100644 --- a/src/v4/social/pages/PostDetailPage/PostDetailPage.module.css +++ b/src/v4/social/pages/PostDetailPage/PostDetailPage.module.css @@ -86,12 +86,6 @@ padding: 0.75rem 1rem; } -.postDetailPage__commentComposeBar { - display: flex; - gap: 0.5rem; - padding: 0.5rem 1rem; -} - @keyframes skeleton-pulse { 0% { opacity: 0.6; diff --git a/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx b/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx index 23d1427c0..6ccff95ad 100644 --- a/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx +++ b/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx @@ -1,6 +1,4 @@ import React, { useState } from 'react'; -import { useMutation } from '@tanstack/react-query'; -import { CommentRepository } from '@amityco/ts-sdk'; import { Typography } from '~/v4/core/components'; import { PostContent, PostContentSkeleton } from '~/v4/social/components/PostContent'; @@ -11,11 +9,11 @@ import usePost from '~/v4/core/hooks/objects/usePost'; import { useNavigation } from '~/v4/core/providers/NavigationProvider'; import { BackButton } from '~/v4/social/elements/BackButton'; import { useAmityPage } from '~/v4/core/hooks/uikit'; -import CommentList from '~/v4/social/internal-components/CommentList/CommentList'; -import CommentComposeBar from '~/v4/social/internal-components/CommentComposeBar/CommentComposeBar'; -import { Mentionees, Metadata } from '~/v4/helpers/utils'; import styles from './PostDetailPage.module.css'; import { useDrawer } from '~/v4/core/providers/DrawerProvider'; +import { PostCommentComposer } from '../../components/PostCommentComposer/PostCommentComposer'; +import { PostCommentList } from '../../components/PostCommentList/PostCommentList'; +import useCommentsCollection from '../../hooks/collections/useCommentsCollection'; interface PostDetailPageProps { id: string; @@ -28,37 +26,10 @@ export function PostDetailPage({ id }: PostDetailPageProps) { pageId, }); const { onBack } = useNavigation(); - const [replyComment, setReplyComment] = useState<Amity.Comment | null>(null); + const [replyComment, setReplyComment] = useState<Amity.Comment>(); const { setDrawerData, removeDrawerData } = useDrawer(); - const { mutateAsync } = useMutation({ - mutationFn: async ({ - text, - mentionees, - metadata, - }: { - text: string; - mentionees: Mentionees; - metadata: Metadata; - }) => { - const referenceId = replyComment ? replyComment.referenceId : post?.postId; - const referenceType = replyComment ? replyComment.referenceType : 'post'; - const parentId = replyComment ? replyComment.commentId : undefined; - - await CommentRepository.createComment({ - referenceId, - referenceType, - data: { - text: text, - }, - parentId, - metadata, - mentionees: mentionees as Amity.UserMention[], - }); - }, - }); - return ( <div className={styles.postDetailPage} style={themeStyles}> <div className={styles.postDetailPage__container}> @@ -71,11 +42,12 @@ export function PostDetailPage({ id }: PostDetailPageProps) { </div> <div className={styles.postDetailPage__comments__divider} data-is-loading={isPostLoading} /> <div className={styles.postDetailPage__comments}> - <CommentList - referenceId={post?.postId} - referenceType={'post'} - onClickReply={(comment) => setReplyComment(comment)} - /> + {post && ( + <PostCommentList + post={post} + onClickReply={(comment: Amity.Comment) => setReplyComment(comment)} + /> + )} </div> </div> <div className={styles.postDetailPage__topBar}> @@ -98,19 +70,13 @@ export function PostDetailPage({ id }: PostDetailPageProps) { /> </div> </div> - <div className={styles.postDetailPage__commentComposeBar}> - <CommentComposeBar - targetId={post?.postId} - targetType={'post'} - userToReply={replyComment?.creator?.displayName || undefined} - isReplying={!!replyComment} - onSubmit={async (text, mentionees, metadata) => { - await mutateAsync({ text, mentionees, metadata }); - setReplyComment(null); - }} - onCancelReply={() => setReplyComment(null)} + {post && ( + <PostCommentComposer + post={post} + replyTo={replyComment} + onCancelReply={() => setReplyComment(undefined)} /> - </div> + )} </div> ); } From 3801e95c6a943e5a682cbe1b771c5fcb93f44b7f Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Mon, 8 Jul 2024 14:49:26 +0700 Subject: [PATCH 204/300] fix: ASC-00000 - story bugs (#491) * fix: story bugs * fix: button style * fix: image mode * fix: story bugs * fix: remove console.log * fix: onPress * fix: css --- src/social/components/SocialSearch/styles.tsx | 1 - src/v4/core/components/ConfirmModal/index.tsx | 19 ++++--- .../components/ConfirmModal/styles.module.css | 19 +++++-- .../components/CommentTray/CommentTray.tsx | 10 ++-- .../HyperLinkConfig/HyperLinkConfig.tsx | 2 +- .../AspectRatioButton.module.css | 2 + .../elements/BackButton/BackButton.module.css | 2 + .../social/elements/BackButton/BackButton.tsx | 2 +- .../elements/DoneButton/DoneButton.module.css | 5 ++ .../social/elements/DoneButton/DoneButton.tsx | 22 ++++---- .../EditCancelButton.module.css | 1 + .../HyperLinkButton.module.css | 2 + .../HyperLinkButton/HyperLinkButton.tsx | 13 +++-- .../OverflowMenuButton.module.css | 2 + .../SpeakerButton/SpeakerButton.module.css | 1 + .../internal-components/Comment/index.tsx | 52 ++++++------------- .../CommentList/CommentList.module.css | 12 +++++ .../CommentList/CommentList.tsx | 42 ++++++++------- .../StoryViewer/Renderers/Image.tsx | 22 ++++---- .../Renderers/Renderers.module.css | 8 +++ .../StoryViewer/Renderers/Video.tsx | 1 + .../Wrappers/Header/Header.module.css | 4 ++ src/v4/social/pages/DraftsPage/DraftsPage.tsx | 1 + .../pages/StoryPage/CommunityFeedStory.tsx | 7 ++- 24 files changed, 151 insertions(+), 101 deletions(-) diff --git a/src/social/components/SocialSearch/styles.tsx b/src/social/components/SocialSearch/styles.tsx index 4cbadf1a1..78c5c7586 100644 --- a/src/social/components/SocialSearch/styles.tsx +++ b/src/social/components/SocialSearch/styles.tsx @@ -19,7 +19,6 @@ export const SocialSearchContainer = styled.div<{ sticky?: boolean }>` ${({ sticky }) => sticky && css` - z-index: 500; position: sticky; top: 0; box-shadow: 0 0 0.5rem rgba(0, 0, 0, 0.1); diff --git a/src/v4/core/components/ConfirmModal/index.tsx b/src/v4/core/components/ConfirmModal/index.tsx index b06aa3a73..0d39ae041 100644 --- a/src/v4/core/components/ConfirmModal/index.tsx +++ b/src/v4/core/components/ConfirmModal/index.tsx @@ -1,10 +1,12 @@ import React, { ReactNode } from 'react'; import Modal from '~/v4/core/components/Modal'; -import { Button } from '~/v4/core/components/Button'; +import { Button } from '~/v4/core/natives/Button'; import clsx from 'clsx'; -import styles from './styles.module.css'; import { ConfirmType, useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; -import { useAmityElement } from '~/v4/core/hooks/uikit/index'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; + +import styles from './styles.module.css'; +import { Typography } from '~/v4/core/components/Typography'; interface ConfirmProps extends ConfirmType { className?: string; @@ -27,6 +29,7 @@ const Confirm = ({ type = 'confirm', }: ConfirmProps) => { const { accessibilityId, themeStyles } = useAmityElement({ pageId, componentId, elementId }); + return ( <Modal className={clsx(className, styles.modal)} @@ -39,19 +42,21 @@ const Confirm = ({ <div className={styles.footer}> {type === 'confirm' && ( <Button + style={themeStyles} className={styles.cancelButton} data-qa-anchor="confirm-modal-cancel-button" - onClick={onCancel} + onPress={onCancel} > - {cancelText} + <Typography.BodyBold>{cancelText}</Typography.BodyBold> </Button> )} <Button + style={themeStyles} className={styles.okButton} data-qa-anchor={`confirm-modal-${accessibilityId}-ok-button`} - onClick={onOk} + onPress={onOk} > - {okText} + <Typography.BodyBold>{okText}</Typography.BodyBold> </Button> </div> } diff --git a/src/v4/core/components/ConfirmModal/styles.module.css b/src/v4/core/components/ConfirmModal/styles.module.css index f175e0b3b..d3da69b7e 100644 --- a/src/v4/core/components/ConfirmModal/styles.module.css +++ b/src/v4/core/components/ConfirmModal/styles.module.css @@ -9,18 +9,31 @@ .footer { display: flex; justify-content: flex-end; + gap: 0.5rem; } .okButton { + color: var(--asc-color-primary-shade4); + display: flex; + padding: 0.625rem 1rem; + justify-content: center; + align-items: center; + border-radius: var(--asc-border-radius-sm); background: var(--asc-color-alert); border: none; + cursor: pointer; } .cancelButton { - margin-right: var(--asc-spacing-s1); - background: transparent; color: var(--asc-color-secondary-default); - border: 1px solid var(--asc-color-base-shade1); + display: flex; + padding: 0.625rem 1rem; + justify-content: center; + align-items: center; + border-radius: var(--asc-border-radius-sm); + border: 1px solid var(--asc-color-secondary-shade3); + background: var(--asc-color-white); + cursor: pointer; } .cancelButton:hover { diff --git a/src/v4/social/components/CommentTray/CommentTray.tsx b/src/v4/social/components/CommentTray/CommentTray.tsx index 2940bcf6e..3a2a1494e 100644 --- a/src/v4/social/components/CommentTray/CommentTray.tsx +++ b/src/v4/social/components/CommentTray/CommentTray.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { useAmityComponent } from '~/v4/core/hooks/uikit/index'; +import { useAmityComponent } from '~/v4/core/hooks/uikit'; import { CommentList } from '~/v4/social/internal-components/CommentList'; import { StoryCommentComposeBar } from '~/v4/social/internal-components/StoryCommentComposeBar'; import styles from './CommentTray.module.css'; @@ -24,7 +24,7 @@ export const CommentTray = ({ shouldAllowCreation = true, }: CommentTrayProps) => { const componentId = 'comment_tray_component'; - const { config } = useAmityComponent({ + const { accessibilityId, themeStyles } = useAmityComponent({ pageId, componentId, }); @@ -43,7 +43,11 @@ export const CommentTray = ({ }; return ( - <div className={styles.commentTrayContainer}> + <div + style={themeStyles} + data-qa-anchor={accessibilityId} + className={styles.commentTrayContainer} + > <div className={styles.commentListContainer}> <CommentList pageId={pageId} diff --git a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx index b2f9ee187..f0687338f 100644 --- a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx +++ b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx @@ -171,7 +171,7 @@ export const HyperLinkConfig = ({ <DoneButton pageId={pageId} componentId={componentId} - onClick={handleSubmit(onSubmitForm)} + onPress={() => handleSubmit(onSubmitForm)} /> </div> <div className={styles.divider} /> diff --git a/src/v4/social/elements/AspectRatioButton/AspectRatioButton.module.css b/src/v4/social/elements/AspectRatioButton/AspectRatioButton.module.css index b82f214ed..d6099c5c0 100644 --- a/src/v4/social/elements/AspectRatioButton/AspectRatioButton.module.css +++ b/src/v4/social/elements/AspectRatioButton/AspectRatioButton.module.css @@ -1,5 +1,7 @@ .aspectRatioButton { + all: unset; fill: var(--asc-color-white); + cursor: pointer; } .aspectRatioButton__icon { diff --git a/src/v4/social/elements/BackButton/BackButton.module.css b/src/v4/social/elements/BackButton/BackButton.module.css index 1f04120c6..3e805e7de 100644 --- a/src/v4/social/elements/BackButton/BackButton.module.css +++ b/src/v4/social/elements/BackButton/BackButton.module.css @@ -1,3 +1,5 @@ .backButton { + all: unset; fill: var(--asc-color-white); + cursor: pointer; } diff --git a/src/v4/social/elements/BackButton/BackButton.tsx b/src/v4/social/elements/BackButton/BackButton.tsx index 65ed1a69a..a1245b030 100644 --- a/src/v4/social/elements/BackButton/BackButton.tsx +++ b/src/v4/social/elements/BackButton/BackButton.tsx @@ -48,7 +48,7 @@ export const BackButton = ({ if (isExcluded) return null; return ( - <Button className={styles.backButton} style={themeStyles} onPress={onPress}> + <Button style={themeStyles} className={styles.backButton} onPress={onPress}> <IconComponent data-qa-anchor={accessibilityId} defaultIcon={() => <BackButtonSvg className={defaultClassName} />} diff --git a/src/v4/social/elements/DoneButton/DoneButton.module.css b/src/v4/social/elements/DoneButton/DoneButton.module.css index bbf0c36cd..603a64669 100644 --- a/src/v4/social/elements/DoneButton/DoneButton.module.css +++ b/src/v4/social/elements/DoneButton/DoneButton.module.css @@ -1,3 +1,8 @@ +.doneButton { + all: unset; + cursor: pointer; +} + .doneButton_text { color: var(--asc-color-primary-default); } diff --git a/src/v4/social/elements/DoneButton/DoneButton.tsx b/src/v4/social/elements/DoneButton/DoneButton.tsx index f1c978a90..d19ba9407 100644 --- a/src/v4/social/elements/DoneButton/DoneButton.tsx +++ b/src/v4/social/elements/DoneButton/DoneButton.tsx @@ -1,30 +1,30 @@ import React from 'react'; +import { Button, ButtonProps } from '~/v4/core/natives/Button'; import { Typography } from '~/v4/core/components'; import { useAmityElement } from '~/v4/core/hooks/uikit'; import styles from './DoneButton.module.css'; -export interface ExploreButtonProps { +export interface DoneButtonProps { pageId?: string; componentId?: string; - onClick?: () => void; + onPress?: ButtonProps['onPress']; } -export function DoneButton({ pageId = '*', componentId = '*', onClick }: ExploreButtonProps) { +export function DoneButton({ pageId = '*', componentId = '*', onPress }: DoneButtonProps) { const elementId = 'done_button'; - const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = - useAmityElement({ - pageId, - componentId, - elementId, - }); + const { accessibilityId, config, isExcluded } = useAmityElement({ + pageId, + componentId, + elementId, + }); if (isExcluded) return null; return ( - <button data-qa-anchor={accessibilityId} style={themeStyles} onClick={onClick}> + <Button className={styles.doneButton} data-qa-anchor={accessibilityId} onPress={onPress}> <Typography.Body className={styles.doneButton_text}> {config.done_button_text} </Typography.Body> - </button> + </Button> ); } diff --git a/src/v4/social/elements/EditCancelButton/EditCancelButton.module.css b/src/v4/social/elements/EditCancelButton/EditCancelButton.module.css index c1012f9dd..07161eeb8 100644 --- a/src/v4/social/elements/EditCancelButton/EditCancelButton.module.css +++ b/src/v4/social/elements/EditCancelButton/EditCancelButton.module.css @@ -1,4 +1,5 @@ .editCancelButton { + all: unset; cursor: pointer; color: var(--asc-color-base-default); background-color: var(--asc-color-base-background); diff --git a/src/v4/social/elements/HyperLinkButton/HyperLinkButton.module.css b/src/v4/social/elements/HyperLinkButton/HyperLinkButton.module.css index caaaa6686..3c936b61e 100644 --- a/src/v4/social/elements/HyperLinkButton/HyperLinkButton.module.css +++ b/src/v4/social/elements/HyperLinkButton/HyperLinkButton.module.css @@ -1,3 +1,5 @@ .hyperLinkButton { + all: unset; fill: var(--asc-color-white); + cursor: pointer; } diff --git a/src/v4/social/elements/HyperLinkButton/HyperLinkButton.tsx b/src/v4/social/elements/HyperLinkButton/HyperLinkButton.tsx index e67c6b166..825e0b322 100644 --- a/src/v4/social/elements/HyperLinkButton/HyperLinkButton.tsx +++ b/src/v4/social/elements/HyperLinkButton/HyperLinkButton.tsx @@ -41,17 +41,16 @@ export const HyperLinkButton = ({ onPress = () => {}, }: HyperLinkButtonProps) => { const elementId = 'story_hyperlink_button'; - const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = - useAmityElement({ - pageId, - componentId, - elementId, - }); + const { accessibilityId, config, defaultConfig, isExcluded, uiReference } = useAmityElement({ + pageId, + componentId, + elementId, + }); if (isExcluded) return null; return ( - <Button onPress={onPress} style={themeStyles} data-qa-anchor={accessibilityId}> + <Button className={styles.hyperLinkButton} onPress={onPress} data-qa-anchor={accessibilityId}> <IconComponent defaultIcon={() => ( <HyperLinkButtonSvg className={clsx(styles.hyperLinkButton, defaultClassName)} /> diff --git a/src/v4/social/elements/OverflowMenuButton/OverflowMenuButton.module.css b/src/v4/social/elements/OverflowMenuButton/OverflowMenuButton.module.css index 61405faca..31dbf7b29 100644 --- a/src/v4/social/elements/OverflowMenuButton/OverflowMenuButton.module.css +++ b/src/v4/social/elements/OverflowMenuButton/OverflowMenuButton.module.css @@ -1,4 +1,6 @@ .overflowMenuButton { all: unset; cursor: pointer; + width: 1.5rem; + height: 1.5rem; } diff --git a/src/v4/social/elements/SpeakerButton/SpeakerButton.module.css b/src/v4/social/elements/SpeakerButton/SpeakerButton.module.css index 8e3218f31..753884e9c 100644 --- a/src/v4/social/elements/SpeakerButton/SpeakerButton.module.css +++ b/src/v4/social/elements/SpeakerButton/SpeakerButton.module.css @@ -1,4 +1,5 @@ .speakerButton { + all: unset; position: absolute; top: 96px; left: 24px; diff --git a/src/v4/social/internal-components/Comment/index.tsx b/src/v4/social/internal-components/Comment/index.tsx index a911aeb20..6ea2e1964 100644 --- a/src/v4/social/internal-components/Comment/index.tsx +++ b/src/v4/social/internal-components/Comment/index.tsx @@ -1,5 +1,4 @@ import React, { useState } from 'react'; -import { FormattedMessage, useIntl } from 'react-intl'; import useComment from '~/social/hooks/useComment'; import useMention from '~/v4/chat/hooks/useMention'; import { @@ -20,16 +19,16 @@ import useGetStoryByStoryId from '~/v4/social/hooks/useGetStoryByStoryId'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; import { useNotifications } from '~/v4/core/providers/NotificationProvider'; import { Button, BottomSheet, Typography } from '~/v4/core/components'; -import { TrashIcon, PenIcon, FlagIcon, MinusCircleIcon } from '~/v4/social/icons'; +import { TrashIcon, PenIcon, FlagIcon } from '~/v4/social/icons'; import { LoadingIndicator } from '~/v4/social/internal-components/LoadingIndicator'; import useCommunityMembersCollection from '~/v4/social/hooks/collections/useCommunityMembersCollection'; import { useCommentFlaggedByMe } from '~/v4/social/hooks'; -import { isModerator } from '~/helpers/permissions'; import useImage from '~/v4/core/hooks/useImage'; import useSDK from '~/v4/core/hooks/useSDK'; import styles from './Comment.module.css'; +import { isModerator } from '~/v4/utils/permissions'; const REPLIES_PER_PAGE = 5; @@ -89,7 +88,6 @@ export const Comment = ({ const { toggleFlagComment, isFlaggedByMe } = useCommentFlaggedByMe(commentId); const [isEditing, setIsEditing] = useState(false); - const { formatMessage } = useIntl(); const [isExpanded, setExpanded] = useState(false); const toggleBottomSheet = () => setBottomSheet((prev) => !prev); @@ -134,11 +132,11 @@ export const Comment = ({ await handleReportComment(); if (isFlaggedByMe) { notification.success({ - content: formatMessage({ id: 'report.unreportSent' }), + content: 'Unreport sent', }); } else { notification.success({ - content: formatMessage({ id: 'report.reportSent' }), + content: 'Report sent', }); } } catch (err) { @@ -183,15 +181,17 @@ export const Comment = ({ const isReplyComment = !!comment?.parentId; const deleteComment = () => { - const title = isReplyComment ? 'reply.delete' : 'comment.delete'; - const content = isReplyComment ? 'reply.deleteBody' : 'comment.deleteBody'; + const title = isReplyComment ? 'Delete reply' : 'Delete comment'; + const content = isReplyComment + ? 'This reply will be permanently deleted. Continue?' + : 'This comment will be permanently removed.'; confirm({ pageId, componentId, - title: <FormattedMessage id={title} />, - content: <FormattedMessage id={content} />, - cancelText: formatMessage({ id: 'comment.deleteConfirmCancelText' }), - okText: formatMessage({ id: 'comment.deleteConfirmOkText' }), + title, + content, + cancelText: 'Cancel', + okText: 'Delete', onOk: handleDeleteComment, }); }; @@ -202,27 +202,21 @@ export const Comment = ({ const options = [ canEdit ? { - name: isReplyComment - ? formatMessage({ id: 'reply.edit' }) - : formatMessage({ id: 'comment.edit' }), + name: isReplyComment ? 'Edit reply' : 'Edit comment', action: startEditing, icon: <PenIcon className={styles.actionIcon} />, } : null, canReport ? { - name: isFlaggedByMe - ? formatMessage({ id: 'report.undoReport' }) - : formatMessage({ id: 'report.doReport' }), + name: isFlaggedByMe ? 'Undo Report' : 'Report', action: handleReportComment, icon: <FlagIcon className={styles.actionIcon} />, } : null, canDelete ? { - name: isReplyComment - ? formatMessage({ id: 'reply.delete' }) - : formatMessage({ id: 'comment.delete' }), + name: isReplyComment ? 'Delete reply' : 'Delete comment', action: deleteComment, icon: <TrashIcon className={styles.actionIcon} />, } @@ -231,23 +225,10 @@ export const Comment = ({ if (comment == null) return null; - if (comment?.isDeleted) { - return isReplyComment ? null : ( - <div className={styles.deletedCommentBlock}> - <MinusCircleIcon /> - <FormattedMessage id="comment.deleted" /> - </div> - ); - } - const renderedComment = ( <UIComment commentId={comment?.commentId} - authorName={ - commentAuthor?.user?.displayName || - commentAuthor?.userId || - formatMessage({ id: 'anonymous' }) - } + authorName={commentAuthor?.user?.displayName || commentAuthor?.userId || 'Anonymous'} authorAvatar={commentAuthorAvatar} canDelete={canDelete} canEdit={canEdit} @@ -303,6 +284,7 @@ export const Comment = ({ <div data-qa-anchor="comment">{renderedComment}</div> {comment.children.length > 0 && ( <CommentList + componentId={componentId} parentId={comment.commentId} referenceType={comment.referenceType} referenceId={comment.referenceId} diff --git a/src/v4/social/internal-components/CommentList/CommentList.module.css b/src/v4/social/internal-components/CommentList/CommentList.module.css index 7c4fdba82..60de970b5 100644 --- a/src/v4/social/internal-components/CommentList/CommentList.module.css +++ b/src/v4/social/internal-components/CommentList/CommentList.module.css @@ -27,3 +27,15 @@ justify-content: center; height: 100%; } + +.deletedCommentBlock { + display: flex; + justify-content: flex-start; + align-items: center; + gap: var(--asc-spacing-s2); + padding: var(--asc-spacing-s2) var(--asc-spacing-m1); + border-radius: var(--asc-border-radius-sm); + text-align: center; + color: var(--asc-color-base-shade2); + background-color: var(--asc-color-base-background); +} diff --git a/src/v4/social/internal-components/CommentList/CommentList.tsx b/src/v4/social/internal-components/CommentList/CommentList.tsx index cd9cf1ca0..004453d20 100644 --- a/src/v4/social/internal-components/CommentList/CommentList.tsx +++ b/src/v4/social/internal-components/CommentList/CommentList.tsx @@ -2,7 +2,7 @@ import React, { memo } from 'react'; import { Comment } from '~/v4/social/internal-components/Comment/'; import styles from './CommentList.module.css'; -import { ExpandIcon } from '~/v4/social/icons'; +import { ExpandIcon, MinusCircleIcon } from '~/v4/social/icons'; import { LoadMoreWrapper } from '~/v4/core/components/LoadMoreWrapper/LoadMoreWrapper'; import useCommentsCollection from '~/v4/social/hooks/collections/useCommentsCollection'; import { CommentBubbleDeleted } from '~/v4/social/elements/CommentBubbleDeleted'; @@ -56,26 +56,28 @@ export const CommentList = ({ </div> ) : null; - if (comments.length === 0 && isReplyComment) { - return <CommentBubbleDeleted componentId="comment_tray_component" />; - } - - if (comments?.length === 0) { - return <div className={styles.noCommentsContainer}>No comments yet</div>; - } - const renderComments = () => { - return comments.map((comment) => ( - <Comment - key={comment.commentId} - pageId={pageId} - componentId={componentId} - commentId={comment.commentId} - readonly={readonly} - onClickReply={() => onClickReply?.(comment as Amity.Comment)} - shouldAllowInteraction={shouldAllowInteraction} - /> - )); + return comments.map((comment) => { + if (comment.isDeleted) { + return ( + <div className={styles.deletedCommentBlock}> + <MinusCircleIcon /> + This comment has been deleted + </div> + ); + } + return ( + <Comment + key={comment.commentId} + pageId={pageId} + componentId={componentId} + commentId={comment.commentId} + readonly={readonly} + onClickReply={() => onClickReply?.(comment as Amity.Comment)} + shouldAllowInteraction={shouldAllowInteraction} + /> + ); + }); }; return ( diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index 47d890175..587d050bd 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -24,14 +24,7 @@ import clsx from 'clsx'; import { StoryProgressBar } from '~/v4/social/elements/StoryProgressBar/StoryProgressBar'; -export const renderer: CustomRenderer = ({ - story, - action, - config, - onClose, - onSwipeDown, - onClickCommunity, -}) => { +export const renderer: CustomRenderer = ({ story, action, config, onClose, onClickCommunity }) => { const { formatMessage } = useIntl(); const [loaded, setLoaded] = useState(false); const [isOpenBottomSheet, setIsOpenBottomSheet] = useState(false); @@ -62,6 +55,7 @@ export const renderer: CustomRenderer = ({ increaseIndex, pageId, dragEventTarget, + data, } = story; const { members } = useCommunityMembersCollection({ @@ -237,10 +231,18 @@ export const renderer: CustomRenderer = ({ addStoryButton={addStoryButton} /> - <div className={clsx(styles.storyImageContainer)}> + <div + className={clsx(styles.storyImageContainer, { + [styles.imageFit]: data.imageDisplayMode === 'fit', + [styles.imageFill]: data.imageDisplayMode === 'fill', + })} + > <img ref={imageRef} - className={styles.storyImage} + className={clsx(styles.storyImage, { + [styles.imageFit]: data.imageDisplayMode === 'fit', + [styles.imageFill]: data.imageDisplayMode === 'fill', + })} data-qa-anchor="image_view" src={story.url} onLoad={imageLoaded} diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css b/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css index 3d4aed3e2..d773dd648 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css @@ -281,3 +281,11 @@ .rightOverlay { right: 0; } + +.imageFit { + object-fit: contain; +} + +.imageFill { + object-fit: cover; +} diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx index a0bacd083..4411a36a4 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx @@ -309,6 +309,7 @@ export const renderer: CustomRenderer = ({ </div> )} <Footer + pageId={pageId} storyId={storyId} syncState={syncState} reach={reach} diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/Header.module.css b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/Header.module.css index 5c6bf4bdb..34cbf1dc4 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/Header.module.css +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header/Header.module.css @@ -53,11 +53,15 @@ } .playStoryButton { + width: 1.5rem; + height: 1.5rem; fill: var(--asc-color-white); cursor: pointer; } .pauseStoryButton { + width: 1.5rem; + height: 1.5rem; fill: var(--asc-color-white); cursor: pointer; } diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index 07353631c..f0ff834cf 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -131,6 +131,7 @@ export const PlainDraftStoryPage = ({ const discardCreateStory = () => { confirm({ + pageId, title: 'Delete this story?', content: 'This story will be permanently deleted. You’ll no longer to see and find this story.', diff --git a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx index 67d74b1d1..48244f8ae 100644 --- a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx @@ -1,7 +1,5 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import useSDK from '~/core/hooks/useSDK'; - import { StoryRepository } from '@amityco/ts-sdk'; import { CreateNewStoryButton } from '~/v4/social/elements/CreateNewStoryButton'; import { Trash2Icon } from '~/icons'; @@ -29,6 +27,7 @@ import styles from './StoryPage.module.css'; import { useGetActiveStoriesByTarget } from '~/v4/social/hooks/useGetActiveStories'; import { useMotionValue, motion } from 'framer-motion'; import useCommunityStoriesSubscription from '~/v4/social/hooks/useCommunityStoriesSubscription'; +import useSDK from '~/v4/core/hooks/useSDK'; interface CommunityFeedStoryProps { pageId?: string; @@ -206,6 +205,10 @@ export const CommunityFeedStory = ({ ); const increaseIndex = () => { + if (currentIndex === stories.length - 1) { + onBack(); + return; + } setCurrentIndex((prevIndex) => prevIndex + 1); }; From 46ea97fe0b69416ba19667bb3dde72f68e8513a1 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Mon, 8 Jul 2024 15:04:46 +0700 Subject: [PATCH 205/300] fix: ASC-22508 - impression count condition (#492) * fix: story bugs * fix: button style * fix: image mode * fix: story bugs * fix: remove console.log * fix: onPress * fix: css * fix: impression condition only creator and community mod can see --- .../StoryViewer/Renderers/Image.tsx | 9 ++++----- .../StoryViewer/Renderers/Video.tsx | 10 ++++------ 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index 587d050bd..bdc5f630c 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -72,9 +72,9 @@ export const renderer: CustomRenderer = ({ story, action, config, onClose, onCli const isOfficial = community?.isOfficial || false; const isCreator = creator?.userId === user?.userId; const isGlobalAdmin = isAdmin(user?.roles); - const isCommunityModerator = isModerator(user?.roles); + const isModeratorUser = isModerator(user?.roles); const haveStoryPermission = - isGlobalAdmin || isCommunityModerator || checkStoryPermission(client, community?.communityId); + isGlobalAdmin || isModeratorUser || checkStoryPermission(client, community?.communityId); const heading = useMemo( () => <div data-qa-anchor="community_display_name">{community?.displayName}</div>, @@ -327,9 +327,8 @@ export const renderer: CustomRenderer = ({ story, action, config, onClose, onCli isLiked={isLiked} myReactions={myReactions} onClickComment={openCommentSheet} - showImpression={ - isCreator || isCommunityModerator || checkStoryPermission(client, community?.communityId) - } + // Only story-creator and moderator of the community should be able to see impression count. + showImpression={isCreator || checkStoryPermission(client, community?.communityId)} isMember={isMember} /> </div> diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx index 4411a36a4..b23a5e04d 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx @@ -84,9 +84,9 @@ export const renderer: CustomRenderer = ({ const isOfficial = community?.isOfficial || false; const isCreator = creator?.userId === user?.userId; const isGlobalAdmin = isAdmin(user?.roles); - const isCommunityModerator = isModerator(user?.roles); + const isModeratorUser = isModerator(user?.roles); const haveStoryPermission = - isGlobalAdmin || isCommunityModerator || checkStoryPermission(client, community?.communityId); + isGlobalAdmin || isModeratorUser || checkStoryPermission(client, community?.communityId); const vid = useRef<HTMLVideoElement>(null); @@ -318,10 +318,8 @@ export const renderer: CustomRenderer = ({ isLiked={isLiked} onClickComment={openCommentSheet} myReactions={myReactions} - // only show impression if user is creator, community moderator or has story permission - showImpression={ - isCreator || isCommunityModerator || checkStoryPermission(client, community?.communityId) - } + // Only story-creator and moderator of the community should be able to see impression count. + showImpression={isCreator || checkStoryPermission(client, community?.communityId)} isMember={isMember} /> </div> From fd923072425b3a001185d128edd0f3d361f1f9f6 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Mon, 8 Jul 2024 15:19:50 +0700 Subject: [PATCH 206/300] feat: ASC-23125 - global feed ad integration (#489) * feat: AdEngine * chore: AdEngineProvider * feat: integrate with Newsfeed * chore: refactoring * feat: update pagination logic * feat: update Paginator logic * feat: PostAd integration * chore: add data-qa-anchor * fix: avatarUrl and adImageUrl * chore: remove packageManager field * chore: remove unused code * chore: Update src/v4/social/internal-components/PostAd/PostAd.module.css * fix: update code by review --- pnpm-lock.yaml | 17 +- src/v4/core/AdEngine.ts | 104 +++++++++++ src/v4/core/AdSupplier.ts | 105 +++++++++++ src/v4/core/TimeWindowTracker.ts | 43 +++++ .../core/hooks/collections/useGlobalFeed.ts | 26 ++- src/v4/core/hooks/usePagination.ts | 161 +++++++++++++++++ src/v4/core/providers/AdEngineProvider.tsx | 164 ++++++++++++++++++ src/v4/core/providers/AmityUIKitProvider.tsx | 80 +++++---- .../components/GlobalFeed/GlobalFeed.tsx | 32 ++-- .../social/components/Newsfeed/Newsfeed.tsx | 20 +-- .../components/PostContent/PostContent.tsx | 3 + .../collections/useCommentsCollection.ts | 30 ++++ .../PostAd.module.css} | 34 +++- .../PostAd.stories.tsx} | 8 +- .../internal-components/PostAd/PostAd.tsx | 73 ++++++++ .../internal-components/PostAds/PostAds.tsx | 73 -------- .../internal-components/PostMenu/PostMenu.tsx | 3 + 17 files changed, 821 insertions(+), 155 deletions(-) create mode 100644 src/v4/core/AdEngine.ts create mode 100644 src/v4/core/AdSupplier.ts create mode 100644 src/v4/core/TimeWindowTracker.ts create mode 100644 src/v4/core/hooks/usePagination.ts create mode 100644 src/v4/core/providers/AdEngineProvider.tsx rename src/v4/social/internal-components/{PostAds/PostAds.module.css => PostAd/PostAd.module.css} (65%) rename src/v4/social/internal-components/{PostAds/PostAds.stories.tsx => PostAd/PostAd.stories.tsx} (50%) create mode 100644 src/v4/social/internal-components/PostAd/PostAd.tsx delete mode 100644 src/v4/social/internal-components/PostAds/PostAds.tsx diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f4472ea75..9db36036e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2947,6 +2947,9 @@ packages: '@types/node@20.14.4': resolution: {integrity: sha512-1ChboN+57suCT2t/f8lwtPY/k3qTpuD/qnqQuYoBg6OQOcPyaw7PiZVdGpaZYAvhDDtqrt0oAaM8+oSu1xsUGw==} + '@types/node@20.14.8': + resolution: {integrity: sha512-DO+2/jZinXfROG7j7WKFn/3C6nFwxy2lLpgLjEXJz+0XKphZlTLJ14mo8Vfg8X5BWN6XjyESXq+LcYdT7tR3bA==} + '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -4224,8 +4227,8 @@ packages: es-module-lexer@0.9.3: resolution: {integrity: sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==} - es-module-lexer@1.5.3: - resolution: {integrity: sha512-i1gCgmR9dCl6Vil6UKPI/trA69s08g/syhiDK9TG0Nf1RJjjFI+AzoWW7sPufzkgYAn861skuCwJa0pIIHYxvg==} + es-module-lexer@1.5.4: + resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} es-object-atoms@1.0.0: resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} @@ -11506,6 +11509,10 @@ snapshots: dependencies: undici-types: 5.26.5 + '@types/node@20.14.8': + dependencies: + undici-types: 5.26.5 + '@types/normalize-package-data@2.4.4': {} '@types/postcss-modules-local-by-default@4.0.2': @@ -12985,7 +12992,7 @@ snapshots: es-module-lexer@0.9.3: {} - es-module-lexer@1.5.3: {} + es-module-lexer@1.5.4: {} es-object-atoms@1.0.0: dependencies: @@ -14400,7 +14407,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 20.14.4 + '@types/node': 20.14.8 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -16937,7 +16944,7 @@ snapshots: browserslist: 4.23.1 chrome-trace-event: 1.0.4 enhanced-resolve: 5.17.0 - es-module-lexer: 1.5.3 + es-module-lexer: 1.5.4 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 diff --git a/src/v4/core/AdEngine.ts b/src/v4/core/AdEngine.ts new file mode 100644 index 000000000..4b3c57a6f --- /dev/null +++ b/src/v4/core/AdEngine.ts @@ -0,0 +1,104 @@ +import { Client as ASCClient, AdRepository } from '@amityco/ts-sdk'; +import { TimeWindowTracker } from './TimeWindowTracker'; + +class SeenRecencyCache { + static #instance: SeenRecencyCache; + #persistentCacheKey = 'amity.seenRecencyCache'; + + constructor() {} + + public static get instance(): SeenRecencyCache { + if (!SeenRecencyCache.#instance) { + SeenRecencyCache.#instance = new SeenRecencyCache(); + } + return SeenRecencyCache.#instance; + } + + #getSeenRecencyCache() { + return JSON.parse(window.localStorage.getItem(this.#persistentCacheKey) || '{}'); + } + + getSeenRecencyByAdId(adId: string): number { + return this.#getSeenRecencyCache()[adId]; + } + + setSeenRecencyCache(adId: string, value: number) { + const seenRecencyCache = this.#getSeenRecencyCache(); + seenRecencyCache[adId] = value; + window.localStorage.setItem(this.#persistentCacheKey, JSON.stringify(seenRecencyCache)); + } +} + +export class AdEngine { + static #instance: AdEngine; + + private isLoading = true; + private ads: Amity.Ad[] = []; + private settings: Amity.AdsSettings | null = null; + + private subscribers: Array<(networkAds: Amity.NetworkAds | null) => void> = []; + + private constructor() { + ASCClient.onSessionStateChange(async (state: Amity.SessionStates) => { + if (state === 'established') { + const networkAds = await AdRepository.getNetworkAds(); + this.ads = networkAds.ads; + this.settings = networkAds.settings; + this.subscribers.forEach((subscriber) => subscriber(networkAds)); + this.isLoading = false; + } else if (state === 'terminated') { + this.ads = []; + this.settings = null; + this.subscribers.forEach((subscriber) => subscriber(null)); + } + }); + } + + public static get instance(): AdEngine { + if (!AdEngine.#instance) { + AdEngine.#instance = new AdEngine(); + } + return AdEngine.#instance; + } + + onNetworkAdsData(callback: (networkAds: Amity.NetworkAds | null) => void) { + if (!this.isLoading && this.ads.length > 0 && this.settings) { + callback({ ads: this.ads, settings: this.settings }); + } + this.subscribers.push(callback); + } + + #getAdFrequency(placement: Amity.AdPlacement) { + if (!this.settings) return null; + switch (placement) { + case 'feed': + return this.settings.frequency.feed; + case 'comment': + return this.settings.frequency.comment; + case 'story': + return this.settings.frequency.story; + default: + return null; + } + } + + getLastSeen(adId: string) { + return SeenRecencyCache.instance.getSeenRecencyByAdId(adId); + } + + markSeen(ad: Amity.Ad, placement: Amity.AdPlacement) { + SeenRecencyCache.instance.setSeenRecencyCache(ad.adId, Date.now()); + if (this.#getAdFrequency(placement)?.type === 'time-window') { + TimeWindowTracker.instance.markSeen(placement); + } + ad.analytics.markAsSeen(placement); + } + + markClicked(ad: Amity.Ad, placement: Amity.AdPlacement) { + ad.analytics.markLinkAsClicked(placement); + } + + getAdFrequencyByPlacement(placement: Amity.AdPlacement) { + return this.#getAdFrequency(placement); + } +} diff --git a/src/v4/core/AdSupplier.ts b/src/v4/core/AdSupplier.ts new file mode 100644 index 000000000..7a6e0a9c3 --- /dev/null +++ b/src/v4/core/AdSupplier.ts @@ -0,0 +1,105 @@ +import { AdEngine } from './AdEngine'; + +export class AdSupplier { + static #instance: AdSupplier; + + private constructor() {} + + public static get instance(): AdSupplier { + if (!AdSupplier.#instance) { + AdSupplier.#instance = new AdSupplier(); + } + return AdSupplier.#instance; + } + + recommendedAds({ + ads, + placement, + count, + communityId, + }: { + ads: Amity.Ad[]; + placement: Amity.AdPlacement; + count: number; + communityId?: string; + }) { + // calculate impression age + const impressionAges = this.#calculateImpressionAges(ads); + + // calculate score for all ads + const scores = new Map<string, number>(); + ads.forEach((ad) => { + const relevancy = (ad.targets?.communityIds || []).includes(communityId || '') ? 1 : 0; + const impressionAge = impressionAges.get(ad.adId); + if (impressionAge == null) return; + const score = relevancy + Math.pow(Math.E, 2 * impressionAge); + scores.set(ad.adId, score); + }); + + return this.#selectAdsByWeightedRandomChoice({ ads, scores, count }); + } + + #calculateImpressionAges(ads: Amity.Ad[]) { + const recencySortedAds = ads.sort((ad1, ad2) => { + return AdEngine.instance.getLastSeen(ad2.adId) - AdEngine.instance.getLastSeen(ad1.adId); + }); + + const impressionAges = new Map<string, number>(); + const minLastSeen = AdEngine.instance.getLastSeen( + recencySortedAds[recencySortedAds.length - 1].adId, + ); + const maxLastSeen = AdEngine.instance.getLastSeen(recencySortedAds[0].adId); + + if (maxLastSeen === minLastSeen) { + recencySortedAds.forEach((ad) => { + impressionAges.set(ad.adId, 1); + }); + } else { + recencySortedAds.forEach((ad, index) => { + const impressionAge = index / recencySortedAds.length; + impressionAges.set(ad.adId, impressionAge); + }); + } + return impressionAges; + } + + #weightedRandomChoice(weights: number[]) { + let cumulativeWeight = 0; + const randomValue = Math.random(); + + for (let i = 0; i < weights.length; i++) { + cumulativeWeight += weights[i]; + if (randomValue < cumulativeWeight) { + return i; + } + } + + return weights.length - 1; + } + + #selectAdsByWeightedRandomChoice({ + ads: inputAds, + scores, + count, + }: { + ads: Amity.Ad[]; + scores: Map<string, number>; + count: number; + }) { + const ads = [...inputAds]; + const selectedAds = []; + + while (selectedAds.length < count && ads.length > 0) { + const totalScore = ads.reduce((acc, ad) => acc + (scores.get(ad.adId) || 0), 0); + const weights = ads.map((ad) => (scores.get(ad.adId) || 0) / totalScore); + + const likelihoods = weights.map((weight) => weight / weights.reduce((acc, w) => acc + w, 0)); + const selectedAdIndex = this.#weightedRandomChoice(likelihoods); + + selectedAds.push(ads[selectedAdIndex]); + ads.splice(selectedAdIndex, 1); + } + + return selectedAds; + } +} diff --git a/src/v4/core/TimeWindowTracker.ts b/src/v4/core/TimeWindowTracker.ts new file mode 100644 index 000000000..764f3a799 --- /dev/null +++ b/src/v4/core/TimeWindowTracker.ts @@ -0,0 +1,43 @@ +import dayjs from 'dayjs'; +import { AdEngine } from './AdEngine'; + +export class TimeWindowTracker { + #markedTimeWindow: Map<Amity.AdPlacement, string>; + static #instance: TimeWindowTracker; + + constructor() { + this.#markedTimeWindow = new Map(); + } + + public static get instance(): TimeWindowTracker { + if (!TimeWindowTracker.#instance) { + TimeWindowTracker.#instance = new TimeWindowTracker(); + } + return TimeWindowTracker.#instance; + } + + hasReachedLimit(placement: Amity.AdPlacement): boolean { + const currentWindowKey = this.#getCurrentWindowKey(placement); + return this.#markedTimeWindow.get(placement) === currentWindowKey; + } + + markSeen(placement: Amity.AdPlacement) { + this.#markedTimeWindow.set(placement, this.#getCurrentWindowKey(placement)); + } + + #getTimeWindowSettings(placement: Amity.AdPlacement): number { + const adFrequency = AdEngine.instance.getAdFrequencyByPlacement(placement); + + if (adFrequency == null || adFrequency.type !== 'time-window') { + return 0; + } + return adFrequency.value; + } + + #getCurrentWindowKey(placement: Amity.AdPlacement) { + const today = dayjs(); + const minuteSinceStartOfADay = today.startOf('day').diff(today, 'minute'); + const windowIndex = Math.ceil(minuteSinceStartOfADay / this.#getTimeWindowSettings(placement)); + return `${today.format('DD-MM-YYYY')}-${windowIndex}`; + } +} diff --git a/src/v4/core/hooks/collections/useGlobalFeed.ts b/src/v4/core/hooks/collections/useGlobalFeed.ts index c27fa7e9e..a392d1552 100644 --- a/src/v4/core/hooks/collections/useGlobalFeed.ts +++ b/src/v4/core/hooks/collections/useGlobalFeed.ts @@ -1,11 +1,20 @@ import { FeedRepository } from '@amityco/ts-sdk'; -import { useEffect, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { usePaginatorApi } from '../usePagination'; const useGlobalFeed = () => { - const [items, setItems] = useState<Amity.Post[]>([]); + const [items, setItems] = useState<Array<Amity.Post | Amity.Ad>>([]); const [isLoading, setIsLoading] = useState(false); const [queryToken, setQueryToken] = useState<string | null>(null); const [loadMoreHasBeenCalled, setLoadMoreHasBeenCalled] = useState(false); + const limit = 10; + + const { itemWithAds } = usePaginatorApi({ + items: items, + pageSize: limit, + placement: 'feed' as Amity.AdPlacement, + getItemId: (item) => item.postId, + }); async function fetchMore() { try { @@ -15,9 +24,11 @@ const useGlobalFeed = () => { queryToken: queryToken || undefined, }); setQueryToken(newPosts.paging.next || null); - setItems((prevItems) => [...prevItems, ...newPosts.data]); + setItems((prev) => [...prev, ...newPosts.data]); } finally { - setIsLoading(false); + setTimeout(() => { + setIsLoading(false); + }, 300); } } @@ -41,12 +52,13 @@ const useGlobalFeed = () => { const hasMore = useMemo(() => queryToken !== null, [queryToken]); - const loadMore = () => { + const loadMore = useCallback(() => { setLoadMoreHasBeenCalled(true); + if (isLoading) return; if (hasMore) { fetchMore(); } - }; + }, [isLoading, hasMore, fetchMore]); const refetch = () => { setItems([]); @@ -55,7 +67,7 @@ const useGlobalFeed = () => { }; return { - posts: items, + itemWithAds, isLoading, prependItem, removeItem, diff --git a/src/v4/core/hooks/usePagination.ts b/src/v4/core/hooks/usePagination.ts new file mode 100644 index 000000000..0cc165faa --- /dev/null +++ b/src/v4/core/hooks/usePagination.ts @@ -0,0 +1,161 @@ +import { useCallback, useMemo, useState } from 'react'; +import useLiveCollection from '~/v4/core/hooks/useLiveCollection'; +import { AdEngine } from '../AdEngine'; +import { useAdSettings, useRecommendAds } from '../providers/AdEngineProvider'; +import { isNonNullable } from '~/v4/helpers/utils'; + +const usePaginatorCore = <T>({ + placement, + pageSize, + communityId, + getItemId, +}: { + placement: Amity.AdPlacement; + pageSize: number; + communityId?: string; + getItemId: (item: T) => string; +}) => { + const adSettings = useAdSettings(); + + const [currentAdIndex, setCurrentAdIndex] = useState(0); + const [itemWithAds, setItemWithAds] = useState<Array<[T] | [T, Amity.Ad]>>([]); + const [currentIndex, setCurrentIndex] = useState<number>(0); + + const frequency = AdEngine.instance.getAdFrequencyByPlacement(placement); + + const count = (() => { + if (frequency?.type === 'fixed') { + return pageSize / frequency.value; + } + return 1; + })(); + + const recommendedAds = useRecommendAds({ count, placement, communityId }); + + const combineItemsWithAds = useCallback( + (newItems: T[]) => { + if (!adSettings?.enabled) { + return newItems; + } + if (frequency?.type === 'fixed') { + const newItemIds = new Set(newItems.map((item) => getItemId(item))); + + const prevItemWithAds = itemWithAds + .map((itemWithAd) => { + const itemId = getItemId(itemWithAd[0]); + + if (!newItemIds.has(itemId)) { + return null; + } + return itemWithAd; + }) + .filter(isNonNullable); + + const latestItem = prevItemWithAds[prevItemWithAds.length - 1]; + + const startIndex = latestItem + ? newItems.findIndex((newItem) => getItemId(newItem) === getItemId(latestItem[0])) + : 0; + + let runningAdIndex = currentAdIndex; + let runningIndex = currentIndex; + const suffixItems: Array<[T] | [T, Amity.Ad]> = newItems + .slice(startIndex + 1) + .map((newItem) => { + runningIndex = runningIndex + 1; + const shouldPlaceAd = runningIndex % frequency.value === 0; + + if (!shouldPlaceAd) return [newItem]; + + const ad = recommendedAds[runningAdIndex]; + runningAdIndex = + runningAdIndex + 1 > recommendedAds.length - 1 ? 0 : runningAdIndex + 1; + return [newItem, ad]; + }); + + setCurrentAdIndex(runningAdIndex); + setCurrentIndex(runningIndex); + setItemWithAds([...prevItemWithAds, ...suffixItems]); + return [...prevItemWithAds, ...suffixItems].flatMap((item) => item); + } else if (frequency?.type === 'time-window') { + return [...newItems.slice(0, 1), recommendedAds[0], ...newItems.slice(1)]; + } + return newItems; + }, + [currentAdIndex, currentIndex, frequency, itemWithAds, recommendedAds, adSettings?.enabled], + ); + + return { combineItemsWithAds }; +}; + +export const usePaginator = <TCallback, TParams>({ + fetcher, + params, + callback = () => {}, + config, + shouldCall = true, + placement, + communityId, + pageSize, + getItemId, +}: { + fetcher: ( + params: Amity.LiveCollectionParams<TParams>, + callback: Amity.LiveCollectionCallback<TCallback>, + config?: Amity.LiveCollectionConfig, + ) => Amity.Unsubscriber; + params: Amity.LiveCollectionParams<TParams>; + callback?: Amity.LiveCollectionCallback<TCallback | Amity.Ad>; + config?: Amity.LiveCollectionConfig; + shouldCall?: boolean; + placement: Amity.AdPlacement; + communityId?: string; + pageSize: number; + getItemId: (item: TCallback) => string; +}): { + items: Array<TCallback | Amity.Ad>; + isLoading: boolean; + hasMore: boolean; + loadMore: () => void; + error: Error | null; + loadMoreHasBeenCalled: boolean; +} => { + const { combineItemsWithAds } = usePaginatorCore({ + placement, + pageSize, + communityId, + getItemId, + }); + + const liveCollectionCallback = useCallback<Amity.LiveCollectionCallback<TCallback>>( + (response) => { + const newItems = combineItemsWithAds(response.data).flatMap((item) => item); + + callback({ ...response, data: newItems }); + }, + [combineItemsWithAds], + ); + + return useLiveCollection({ + fetcher, + params, + callback: liveCollectionCallback, + config, + shouldCall, + }); +}; + +export const usePaginatorApi = <T>(params: { + items: T[]; + placement: Amity.AdPlacement; + pageSize: number; + communityId?: string; + getItemId: (item: T) => string; +}) => { + const { items } = params; + const { combineItemsWithAds } = usePaginatorCore(params); + + const itemWithAds = useMemo(() => combineItemsWithAds(items), [items]); + + return { itemWithAds }; +}; diff --git a/src/v4/core/providers/AdEngineProvider.tsx b/src/v4/core/providers/AdEngineProvider.tsx new file mode 100644 index 000000000..c30fd5f6b --- /dev/null +++ b/src/v4/core/providers/AdEngineProvider.tsx @@ -0,0 +1,164 @@ +import React, { useContext, useEffect, useState, createContext } from 'react'; +import { AdEngine } from '../AdEngine'; +import { AdSupplier } from '../AdSupplier'; +import { TimeWindowTracker } from '../TimeWindowTracker'; + +export const AdEngineContext = createContext<{ + ads: Amity.Ad[]; + settings: Amity.AdsSettings | null; + isLoading: boolean; + saveItemsPagination: (data: { + targetId?: string; + placement: string; + frequencyType: string; + adId: string; + referenceId: string; + }) => void; + getItemsPaginationCache: (data: { + targetId?: string; + frequencyType: string; + placement: string; + }) => { adId: string; referenceId: string }[] | undefined; +}>({ + isLoading: true, + ads: [], + settings: null, + saveItemsPagination: () => {}, + getItemsPaginationCache: () => undefined, +}); + +export const AdEngineProvider: React.FC = ({ children }) => { + const [networkAds, setNetworkAds] = useState<Amity.NetworkAds | null>(null); + const [isLoading, setIsLoading] = useState(true); + + const [itemsPaginationCache, setItemsPaginationCache] = useState( + new Map<string, { adId: string; referenceId: string }[]>(), + ); + + const getCacheKey = ({ + targetId, + placement, + frequencyType, + }: { + targetId?: string; + placement: string; + frequencyType: string; + }) => { + if (targetId) { + return `${frequencyType}-${placement}-${frequencyType}`; + } else { + return `global-${placement}-${frequencyType}`; + } + }; + + const saveItemsPagination = ({ + targetId, + placement, + adId, + referenceId, + frequencyType, + }: { + targetId?: string; + placement: string; + adId: string; + referenceId: string; + frequencyType: string; + }) => { + setItemsPaginationCache((prev) => { + const cacheKey = getCacheKey({ targetId, placement, frequencyType }); + const prevValue = prev.get(cacheKey); + if (prevValue) { + prev.set(cacheKey, [...prevValue, { adId, referenceId }]); + } else { + prev.set(cacheKey, [{ adId, referenceId }]); + } + return new Map(prev); + }); + }; + + const getItemsPaginationCache = ({ + targetId, + placement, + frequencyType, + }: { + targetId?: string; + placement: string; + frequencyType: string; + }) => { + const cacheKey = getCacheKey({ targetId, placement, frequencyType }); + return itemsPaginationCache.get(cacheKey); + }; + + useEffect(() => { + async function init() { + AdEngine.instance.onNetworkAdsData((networkAds) => { + setNetworkAds(networkAds); + setIsLoading(false); + }); + } + init(); + }, []); + + return ( + <AdEngineContext.Provider + value={{ + isLoading, + ads: networkAds?.ads || [], + settings: networkAds?.settings || null, + saveItemsPagination, + getItemsPaginationCache, + }} + > + {children} + </AdEngineContext.Provider> + ); +}; + +export const useAds = () => { + const adContext = useContext(AdEngineContext); + return adContext.ads; +}; + +export const useAdSettings = () => { + const adContext = useContext(AdEngineContext); + return adContext.settings; +}; + +export const useRecommendAds = ({ + count, + placement, + communityId, +}: { + count: number; + placement: Amity.AdPlacement; + communityId?: string; +}) => { + const adContext = useContext(AdEngineContext); + const ads = adContext.ads; + const [recommendedAds, setRecommendedAds] = useState<Amity.Ad[]>([]); + const adSettings = useAdSettings(); + const adFrequency = AdEngine.instance.getAdFrequencyByPlacement(placement); + + useEffect(() => { + if (!adSettings?.enabled) { + return; + } + if ( + adFrequency?.type === 'time-window' && + TimeWindowTracker.instance.hasReachedLimit(placement) + ) { + return; + } + + setRecommendedAds( + AdSupplier.instance.recommendedAds({ + ads, + placement, + count, + communityId, + }), + ); + }, [ads, count, placement, communityId]); + + return recommendedAds; +}; diff --git a/src/v4/core/providers/AmityUIKitProvider.tsx b/src/v4/core/providers/AmityUIKitProvider.tsx index 1a128b15c..f6601018b 100644 --- a/src/v4/core/providers/AmityUIKitProvider.tsx +++ b/src/v4/core/providers/AmityUIKitProvider.tsx @@ -36,6 +36,8 @@ import { DrawerProvider } from '~/v4/core/providers/DrawerProvider'; import { NotificationProvider as LegacyNotificationProvider } from '~/core/providers/NotificationProvider'; import { CustomReactionProvider } from './CustomReactionProvider'; import { PostProvider } from '~/v4/social/providers/PostProvider'; +import { AdEngineProvider } from './AdEngineProvider'; +import { AdEngine } from '../AdEngine'; export type AmityUIKitConfig = Config; @@ -105,6 +107,8 @@ const AmityUIKitProvider: React.FC<AmityUIKitProviderProps> = ({ // Set up the AmityUIKitManager AmityUIKitManager.setup({ apiKey, apiRegion, apiEndpoint }); + AdEngine.instance; + // Register the device and get the client instance await AmityUIKitManager.registerDevice( userId, @@ -143,43 +147,45 @@ const AmityUIKitProvider: React.FC<AmityUIKitProviderProps> = ({ <StyledThemeProvider theme={buildGlobalTheme(theme)}> <ThemeProvider> <CustomReactionProvider> - <SDKContextV3.Provider value={sdkContextValue}> - <SDKContext.Provider value={sdkContextValue}> - <SDKConnectorProviderV3> - <SDKConnectorProvider> - <NotificationProvider> - <DrawerProvider> - <LegacyNotificationProvider> - <ConfirmProvider> - <LegacyConfirmProvider> - <ConfigProvider - config={{ - socialCommunityCreationButtonVisible: - socialCommunityCreationButtonVisible || true, - }} - > - <PostRendererProvider config={postRendererConfig}> - <NavigationProvider> - <PageBehaviorProvider pageBehavior={pageBehavior}> - <PostProvider>{children}</PostProvider> - </PageBehaviorProvider> - </NavigationProvider> - </PostRendererProvider> - </ConfigProvider> - <NotificationsContainer /> - <LegacyNotificationsContainer /> - <ConfirmComponent /> - <DrawerContainer /> - <LegacyConfirmComponent /> - </LegacyConfirmProvider> - </ConfirmProvider> - </LegacyNotificationProvider> - </DrawerProvider> - </NotificationProvider> - </SDKConnectorProvider> - </SDKConnectorProviderV3> - </SDKContext.Provider> - </SDKContextV3.Provider> + <AdEngineProvider> + <SDKContextV3.Provider value={sdkContextValue}> + <SDKContext.Provider value={sdkContextValue}> + <SDKConnectorProviderV3> + <SDKConnectorProvider> + <NotificationProvider> + <DrawerProvider> + <LegacyNotificationProvider> + <ConfirmProvider> + <LegacyConfirmProvider> + <ConfigProvider + config={{ + socialCommunityCreationButtonVisible: + socialCommunityCreationButtonVisible || true, + }} + > + <PostRendererProvider config={postRendererConfig}> + <NavigationProvider> + <PageBehaviorProvider pageBehavior={pageBehavior}> + <PostProvider>{children}</PostProvider> + </PageBehaviorProvider> + </NavigationProvider> + </PostRendererProvider> + </ConfigProvider> + <NotificationsContainer /> + <LegacyNotificationsContainer /> + <ConfirmComponent /> + <DrawerContainer /> + <LegacyConfirmComponent /> + </LegacyConfirmProvider> + </ConfirmProvider> + </LegacyNotificationProvider> + </DrawerProvider> + </NotificationProvider> + </SDKConnectorProvider> + </SDKConnectorProviderV3> + </SDKContext.Provider> + </SDKContextV3.Provider> + </AdEngineProvider> </CustomReactionProvider> </ThemeProvider> </StyledThemeProvider> diff --git a/src/v4/social/components/GlobalFeed/GlobalFeed.tsx b/src/v4/social/components/GlobalFeed/GlobalFeed.tsx index e230a7efc..6170b1bef 100644 --- a/src/v4/social/components/GlobalFeed/GlobalFeed.tsx +++ b/src/v4/social/components/GlobalFeed/GlobalFeed.tsx @@ -7,23 +7,28 @@ import { usePostContext } from '~/v4/social/providers/PostProvider'; import styles from './GlobalFeed.module.css'; import { useAmityComponent } from '~/v4/core/hooks/uikit'; -import { PostCommentComposer } from '../PostCommentComposer/PostCommentComposer'; -import { PostCommentList } from '../PostCommentList/PostCommentList'; +import { PostAd } from '~/v4/social/internal-components/PostAd/PostAd'; interface GlobalFeedProps { pageId?: string; componentId?: string; - posts: Amity.Post[]; + items: Array<Amity.Post | Amity.Ad>; isLoading: boolean; onFeedReachBottom: () => void; + onPostDeleted?: (post: Amity.Post) => void; } +const isAmityAd = (item: Amity.Post | Amity.Ad): item is Amity.Ad => { + return 'adId' in item; +}; + export const GlobalFeed = ({ pageId = '*', componentId = '*', - posts, + items, isLoading, onFeedReachBottom, + onPostDeleted, }: GlobalFeedProps) => { const { accessibilityId, themeStyles } = useAmityComponent({ pageId, @@ -42,7 +47,7 @@ export const GlobalFeed = ({ }, }); - if (posts.length === 0 && !isLoading) { + if (items.length === 0 && !isLoading) { return <EmptyNewsfeed pageId={pageId} />; } @@ -63,21 +68,24 @@ export const GlobalFeed = ({ <div className={styles.global_feed__divider} /> </> )} - {posts.map((post, index) => ( - <div key={post.postId}> + {items.map((item, index) => ( + <div key={item.postId}> {index !== 0 ? <div className={styles.global_feed__divider} /> : null} - <> + {isAmityAd(item) ? ( + <PostAd key={item.adId} ad={item} /> + ) : ( <div className={styles.global_feed__postContainer}> <PostContent pageId={pageId} - post={post} + post={item} type="feed" onClick={() => { - AmityGlobalFeedComponentBehavior?.goToPostDetailPage?.({ postId: post.postId }); + AmityGlobalFeedComponentBehavior?.goToPostDetailPage?.({ postId: item.postId }); }} + onPostDeleted={onPostDeleted} /> </div> - </> + )} </div> ))} {isLoading @@ -90,7 +98,7 @@ export const GlobalFeed = ({ </div> )) : null} - <div ref={intersectionRef} className={styles.global_feed__intersection} /> + {!isLoading && <div ref={intersectionRef} className={styles.global_feed__intersection} />} </div> ); }; diff --git a/src/v4/social/components/Newsfeed/Newsfeed.tsx b/src/v4/social/components/Newsfeed/Newsfeed.tsx index 8f0ff6487..a4d000e05 100644 --- a/src/v4/social/components/Newsfeed/Newsfeed.tsx +++ b/src/v4/social/components/Newsfeed/Newsfeed.tsx @@ -2,8 +2,6 @@ import React, { useRef, useState } from 'react'; import { StoryTab } from '~/v4/social/components/StoryTab'; import useGlobalFeed from '~/v4/core/hooks/collections/useGlobalFeed'; import { EmptyNewsfeed } from '~/v4/social/components/EmptyNewsFeed'; -import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; -import { useGenerateStylesShadeColors } from '~/v4/core/providers/ThemeProvider'; import { GlobalFeed } from '~/v4/social/components/GlobalFeed'; import styles from './Newsfeed.module.css'; @@ -49,22 +47,21 @@ interface NewsfeedProps { export const Newsfeed = ({ pageId = '*' }: NewsfeedProps) => { const componentId = 'newsfeed'; - const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = - useAmityComponent({ - pageId, - componentId, - }); + const { themeStyles } = useAmityComponent({ + pageId, + componentId, + }); const touchStartY = useRef(0); const [touchDiff, setTouchDiff] = useState(0); - const { posts, hasMore, isLoading, loadMore, refetch } = useGlobalFeed(); + const { itemWithAds, hasMore, isLoading, loadMore, refetch, removeItem } = useGlobalFeed(); const onFeedReachBottom = () => { if (hasMore && !isLoading) loadMore(); }; - if (posts.length === 0 && !isLoading) { + if (itemWithAds.length === 0 && !isLoading) { return <EmptyNewsfeed pageId={pageId} />; } @@ -107,13 +104,14 @@ export const Newsfeed = ({ pageId = '*' }: NewsfeedProps) => { </div> <div className={styles.newsfeed__divider} /> <StoryTab type="globalFeed" pageId={pageId} /> - {posts.length > 0 && <div className={styles.newsfeed__divider} />} + {itemWithAds.length > 0 && <div className={styles.newsfeed__divider} />} <GlobalFeed isLoading={isLoading} - posts={posts} + items={itemWithAds} pageId={pageId} componentId={componentId} onFeedReachBottom={() => onFeedReachBottom()} + onPostDeleted={(post) => removeItem(post.postId)} /> </div> ); diff --git a/src/v4/social/components/PostContent/PostContent.tsx b/src/v4/social/components/PostContent/PostContent.tsx index deb906684..f399c6851 100644 --- a/src/v4/social/components/PostContent/PostContent.tsx +++ b/src/v4/social/components/PostContent/PostContent.tsx @@ -138,6 +138,7 @@ interface PostContentProps { type: 'feed' | 'detail'; drawerRef?: React.RefObject<HTMLDivElement>; onClick?: () => void; + onPostDeleted?: (post: Amity.Post) => void; } export const PostContent = ({ @@ -146,6 +147,7 @@ export const PostContent = ({ type, drawerRef, onClick, + onPostDeleted, }: PostContentProps) => { const componentId = 'post_content'; const { themeStyles } = useAmityComponent({ @@ -299,6 +301,7 @@ export const PostContent = ({ onCloseMenu={() => removeDrawerData()} pageId={pageId} componentId={componentId} + onPostDeleted={onPostDeleted} /> ), }) diff --git a/src/v4/social/hooks/collections/useCommentsCollection.ts b/src/v4/social/hooks/collections/useCommentsCollection.ts index 190e39adb..65cd6e32e 100644 --- a/src/v4/social/hooks/collections/useCommentsCollection.ts +++ b/src/v4/social/hooks/collections/useCommentsCollection.ts @@ -1,5 +1,6 @@ import { CommentRepository } from '@amityco/ts-sdk'; import useLiveCollection from '~/v4/core/hooks/useLiveCollection'; +import { usePaginator } from '~/v4/core/hooks/usePagination'; type useCommentsParams = { parentId?: string | null; @@ -35,3 +36,32 @@ export default function useCommentsCollection({ ...rest, }; } + +export function useCommentsCollectionWithAds({ + parentId, + referenceId, + referenceType, + limit = 10, + shouldCall = true, + includeDeleted = false, +}: useCommentsParams) { + const { items, ...rest } = usePaginator({ + fetcher: CommentRepository.getComments, + params: { + parentId, + referenceId: referenceId as string, + referenceType, + limit, + includeDeleted, + }, + shouldCall: shouldCall && !!referenceId && !!referenceType, + getItemId: (item) => item.commentId, + pageSize: limit, + placement: 'comment' as Amity.AdPlacement, + }); + + return { + comments: items, + ...rest, + }; +} diff --git a/src/v4/social/internal-components/PostAds/PostAds.module.css b/src/v4/social/internal-components/PostAd/PostAd.module.css similarity index 65% rename from src/v4/social/internal-components/PostAds/PostAds.module.css rename to src/v4/social/internal-components/PostAd/PostAd.module.css index 7ab22bb1c..f0842564a 100644 --- a/src/v4/social/internal-components/PostAds/PostAds.module.css +++ b/src/v4/social/internal-components/PostAd/PostAd.module.css @@ -1,9 +1,6 @@ .container { position: relative; - background-color: var(asc-color-base-background); - border-radius: var(--asc-border-radius-md); - border: 1px solid var(--asc-color-base-shade4); - overflow: hidden; + background-color: var(--asc-color-base-background-sh); } .innerContainer { @@ -28,20 +25,34 @@ height: 3rem; } +.header__detail { + display: flex; + flex-direction: column; + justify-content: center; + align-items: flex-start; + gap: var(--asc-spacing-xxs1); +} + .content__text { color: var(--asc-color-base-default); margin: var(--asc-spacing-s1) 0; } +.content__imageContainer { + aspect-ratio: 1 / 1; +} + .content__image { margin: var(--asc-spacing-s1) 0; width: 100%; height: 100%; + object-fit: cover; border-radius: var(--asc-border-radius-md); } .footer { - background-color: var(--asc-color-base-shade4); + /* TODO: change it to css variable */ + background-color: #f6f7f8; padding: var(--asc-spacing-m1) var(--asc-spacing-s2); display: flex; flex-direction: row; @@ -50,6 +61,10 @@ margin-top: var(--asc-spacing-s1); } +.footer[data-has-url='true'] { + cursor: pointer; +} + .footer__content__title { color: var(--asc-color-base-shade1); } @@ -59,7 +74,14 @@ } .footer__content__button { - flex-shrink: 0; + background-color: var(--asc-color-primary-default); + color: var(--asc-color-white); + padding: 0.375rem 0.75rem; + border-radius: var(--asc-border-radius-md); +} + +.footer__content__button:hover:not(.disabled) { + background-color: var(--asc-color-primary-50); } .infoIcon { diff --git a/src/v4/social/internal-components/PostAds/PostAds.stories.tsx b/src/v4/social/internal-components/PostAd/PostAd.stories.tsx similarity index 50% rename from src/v4/social/internal-components/PostAds/PostAds.stories.tsx rename to src/v4/social/internal-components/PostAd/PostAd.stories.tsx index 90918ee65..65904d417 100644 --- a/src/v4/social/internal-components/PostAds/PostAds.stories.tsx +++ b/src/v4/social/internal-components/PostAd/PostAd.stories.tsx @@ -1,14 +1,14 @@ import React from 'react'; -import { PostAds } from './PostAds'; +import { PostAd } from './PostAd'; export default { - title: 'v4-social/internal-components/PostAds', + title: 'v4-social/internal-components/PostAd', }; -export const PostAdsStory = { +export const PostAdStory = { render: () => ( <div style={{ width: '80%', margin: 'auto' }}> - <PostAds /> + <PostAd /> </div> ), }; diff --git a/src/v4/social/internal-components/PostAd/PostAd.tsx b/src/v4/social/internal-components/PostAd/PostAd.tsx new file mode 100644 index 000000000..663fc6451 --- /dev/null +++ b/src/v4/social/internal-components/PostAd/PostAd.tsx @@ -0,0 +1,73 @@ +import React, { useMemo } from 'react'; +import styles from './PostAd.module.css'; +import { Avatar, Typography } from '~/v4/core/components'; +import { AdsBadge } from '../AdsBadge/AdsBadge'; +import Broadcast from '~/v4/icons/Broadcast'; +import InfoCircle from '~/v4/icons/InfoCircle'; +import { useAmityComponent } from '~/v4/core/hooks/uikit/index'; +import { Button } from '~/v4/core/natives/Button'; +import useImage from '~/v4/core/hooks/useImage'; + +interface PostAdProps { + pageId?: string; + ad: Amity.Ad; +} + +export const PostAd = ({ pageId = '*', ad }: PostAdProps) => { + const componentId = 'post_content'; + const { themeStyles } = useAmityComponent({ + pageId, + componentId, + }); + + const avatarFile = useImage({ fileId: ad.advertiser?.avatar?.fileId }); + const avatarUrl = avatarFile || ad.advertiser?.avatar?.fileUrl || ''; + + const adImageFile = useImage({ fileId: ad.image1_1?.fileId }); + const adImageUrl = adImageFile || ad.image1_1?.fileUrl || ''; + return ( + <div className={styles.container} style={themeStyles}> + <div className={styles.innerContainer}> + <div className={styles.header}> + <div className={styles.header__avatar}> + <Avatar avatarUrl={avatarUrl} defaultImage={<Broadcast />} /> + </div> + <div className={styles.header__detail}> + <Typography.BodyBold className={styles.header__title}> + {ad.advertiser?.companyName} + </Typography.BodyBold> + <AdsBadge /> + </div> + </div> + <div> + <Typography.Body className={styles.content__text}>{ad.body}</Typography.Body> + <div className={styles.content__imageContainer}> + <img className={styles.content__image} src={adImageUrl} /> + </div> + </div> + </div> + <InfoCircle className={styles.infoIcon} /> + + <div + className={styles.footer} + data-has-url={!!ad.callToActionUrl} + onClick={() => window?.open(ad?.callToActionUrl, '_blank')} + > + <div> + <Typography.Body className={styles.footer__content__title}>{ad.headline}</Typography.Body> + <Typography.BodyBold className={styles.footer__content__description}> + {ad.description} + </Typography.BodyBold> + </div> + {ad.callToActionUrl ? ( + <Button + className={styles.footer__content__button} + onPress={() => window?.open(ad?.callToActionUrl, '_blank')} + > + <Typography.CaptionBold>{ad.callToAction}</Typography.CaptionBold> + </Button> + ) : null} + </div> + </div> + ); +}; diff --git a/src/v4/social/internal-components/PostAds/PostAds.tsx b/src/v4/social/internal-components/PostAds/PostAds.tsx deleted file mode 100644 index f37f0090b..000000000 --- a/src/v4/social/internal-components/PostAds/PostAds.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import React from 'react'; -import styles from './PostAds.module.css'; -import { Avatar, Button, Typography } from '~/v4/core/components'; -import { AdsBadge } from '../AdsBadge/AdsBadge'; -import Broadcast from '~/v4/icons/Broadcast'; -import InfoCircle from '~/v4/icons/InfoCircle'; -import { useAmityComponent } from '~/v4/core/hooks/uikit/index'; - -export const PostAds = ({ pageId = '*' }: { pageId?: string }) => { - // TODO: add fetching the ads - // const ads = useAds(); - // const avatarUrl = useFile(avatarFileId); - const componentId = 'post_content'; - const { themeStyles } = useAmityComponent({ - pageId, - componentId, - }); - - return ( - // TODO: confirm the themeStyle is used from page level or component level - <div className={styles.container} style={themeStyles}> - <div className={styles.innerContainer}> - <div className={styles.header}> - <div className={styles.header__avatar}> - <Avatar - avatarUrl={ - 'https://s3-alpha-sig.figma.com/img/eef3/8e4e/780e841c018e87aafef8a9b6b6652024?Expires=1720396800&Key-Pair-Id=APKAQ4GOSFWCVNEHN3O4&Signature=kibPgi17krUs5Hm8fbQUs2K0BtGn6OYMm66m8eV70qjgUYZkY0I76K5KJqRA~gu-Zyi2Sex~KCizs5hQGBhBnb9ANNrUxfAKlXkS1taaZKt4fhxEgOWrHo8Hj6O7R5ZPuj7OOMHcd1fcE~1m9uLQrQGXsq-s8fwdCzLPTHBt~rNKq2sW4M4RoAiljCQNS5sITEe~e9Mu88PFj-9ygzlS4xR1KjNcSbki0tSEhMvY5Ma5R9si6DsOEFZv3s-X1YK-tsszAhe1elNSht7PxjfoygnC3Mtqye~S72Rj~tQbGGaNZwLqYgQwWr1EnJIk7v0JZctTZoVHtkOSAA4kZ5ASfg__' - } - defaultImage={<Broadcast />} - /> - </div> - <div> - <Typography.BodyBold className={styles.header__title}>{'Amity'}</Typography.BodyBold> - <AdsBadge /> - </div> - </div> - <div> - <Typography.Body className={styles.content__text}> - {/* TODO: change to use ads content */} - { - 'Social features are proven to drive engagement, boost retention, and increase revenue. Discover how you can grow your product with Amity Social Cloud, no matter which industry you’re in. 🥰 📱' - } - </Typography.Body> - - <img - className={styles.content__image} - // TODO: change to use ads content - src={ - 'https://s3-alpha-sig.figma.com/img/0074/9e2f/62f75af189ae259254a5a9c895b0a720?Expires=1720396800&Key-Pair-Id=APKAQ4GOSFWCVNEHN3O4&Signature=if15s1aRBec0sxOI1LWXKauQgN75CeJzfpalOXd-OxggaTyJHKmPJwFVldnz~RB~ICaboLN9xKtbrPnAZY~GodOnxyOGvmAHRbmtE39Cjw-QMUlPbZ-C1YB72fwA9eDvpDkI-1pbvPF2Vh~TPT8x7Li8MVk8ifeyAt1ddRbyY546wEQibRf9xK6ilpdK6UJWkOW~0Paj~8OwdGqRgm8suXDSpmmnDcbpTUE5ntQb4ibOLQq6Cw9R1fVitGaIrfyDaRjH7qD~GiOBBVfNqoAX4LdfuavKpyDJrWTKWTODzR20VlhbOjsHQxh7ZI4g92IkxvPs9pUQST-xzmrzZ8C5vA__' - } - /> - </div> - </div> - <InfoCircle className={styles.infoIcon} /> - - <div className={styles.footer}> - <div> - {/* TODO: change to use ads content */} - <Typography.Body className={styles.footer__content__title}> - {'Social media'} - </Typography.Body> - <Typography.BodyBold className={styles.footer__content__description}> - {'Powering the social networks of tomorrow!'} - </Typography.BodyBold> - </div> - {/* TODO: change to use ads content */} - <Button className={styles.footer__content__button} variant="primary"> - {'Talk to sales'} - </Button> - </div> - </div> - ); -}; diff --git a/src/v4/social/internal-components/PostMenu/PostMenu.tsx b/src/v4/social/internal-components/PostMenu/PostMenu.tsx index 3125723f6..19249b671 100644 --- a/src/v4/social/internal-components/PostMenu/PostMenu.tsx +++ b/src/v4/social/internal-components/PostMenu/PostMenu.tsx @@ -54,6 +54,7 @@ interface PostMenuProps { componentId?: string; elementId?: string; onCloseMenu: () => void; + onPostDeleted?: (post: Amity.Post) => void; } export const PostMenu = ({ @@ -62,6 +63,7 @@ export const PostMenu = ({ componentId = '*', elementId = '*', onCloseMenu, + onPostDeleted, }: PostMenuProps) => { const { success, error } = useNotifications(); @@ -132,6 +134,7 @@ export const PostMenu = ({ }, onSuccess: () => { success({ content: 'Post deleted' }); + onPostDeleted?.(post); }, onError: () => { error({ content: 'Failed to delete post' }); From ffc15d5abefd1b0e08100929fe334a23253e8bf3 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Mon, 8 Jul 2024 17:28:38 +0700 Subject: [PATCH 207/300] chore: add storybook users (#494) --- .storybook/decorators/UiKitDecorator.tsx | 33 ++++------------------ .storybook/decorators/UiKitV4Decorator.tsx | 33 ++++------------------ 2 files changed, 10 insertions(+), 56 deletions(-) diff --git a/.storybook/decorators/UiKitDecorator.tsx b/.storybook/decorators/UiKitDecorator.tsx index ad8bdd958..b030a36da 100644 --- a/.storybook/decorators/UiKitDecorator.tsx +++ b/.storybook/decorators/UiKitDecorator.tsx @@ -3,6 +3,8 @@ import UiKitProvider from '../../src/core/providers/UiKitProvider'; import { Preview } from '@storybook/react'; import amityConfig from '../../amity-uikit.config.json'; +const users = import.meta.env.STORYBOOK_USERS.split(','); + const GLOBAL_NAME = 'user'; const global = { [GLOBAL_NAME]: { @@ -13,34 +15,9 @@ const global = { icon: 'user', items: [ { value: 'Web-Test,Web-test', title: 'Web-Test' }, - { - value: import.meta.env.STORYBOOK_USER1, - title: import.meta.env.STORYBOOK_USER1?.split(',')[1], - }, - { - value: import.meta.env.STORYBOOK_USER2, - title: import.meta.env.STORYBOOK_USER2?.split(',')[1], - }, - { - value: import.meta.env.STORYBOOK_USER3, - title: import.meta.env.STORYBOOK_USER3?.split(',')[1], - }, - { - value: import.meta.env.STORYBOOK_USER4, - title: import.meta.env.STORYBOOK_USER4?.split(',')[1], - }, - { - value: import.meta.env.STORYBOOK_USER5, - title: import.meta.env.STORYBOOK_USER5?.split(',')[1], - }, - { - value: import.meta.env.STORYBOOK_USER6, - title: import.meta.env.STORYBOOK_USER6?.split(',')[1], - }, - { - value: import.meta.env.STORYBOOK_USER7, - title: import.meta.env.STORYBOOK_USER7?.split(',')[1], - }, + ...users.map((user) => { + return { value: `${user},${user}`, title: user }; + }), ], }, }, diff --git a/.storybook/decorators/UiKitV4Decorator.tsx b/.storybook/decorators/UiKitV4Decorator.tsx index 3f2bea832..0bc9887e5 100644 --- a/.storybook/decorators/UiKitV4Decorator.tsx +++ b/.storybook/decorators/UiKitV4Decorator.tsx @@ -4,6 +4,8 @@ import { Preview } from '@storybook/react'; import amityConfig from '../../amity-uikit.config.json'; import { Config } from '../../src/v4/core/providers/CustomizationProvider'; +const users = import.meta.env.STORYBOOK_USERS.split(','); + const GLOBAL_NAME = 'user'; const global = { [GLOBAL_NAME]: { @@ -14,34 +16,9 @@ const global = { icon: 'user', items: [ { value: 'Web-Test,Web-test', title: 'Web-Test' }, - { - value: import.meta.env.STORYBOOK_USER1, - title: import.meta.env.STORYBOOK_USER1?.split(',')[1], - }, - { - value: import.meta.env.STORYBOOK_USER2, - title: import.meta.env.STORYBOOK_USER2?.split(',')[1], - }, - { - value: import.meta.env.STORYBOOK_USER3, - title: import.meta.env.STORYBOOK_USER3?.split(',')[1], - }, - { - value: import.meta.env.STORYBOOK_USER4, - title: import.meta.env.STORYBOOK_USER4?.split(',')[1], - }, - { - value: import.meta.env.STORYBOOK_USER5, - title: import.meta.env.STORYBOOK_USER5?.split(',')[1], - }, - { - value: import.meta.env.STORYBOOK_USER6, - title: import.meta.env.STORYBOOK_USER6?.split(',')[1], - }, - { - value: import.meta.env.STORYBOOK_USER7, - title: import.meta.env.STORYBOOK_USER7?.split(',')[1], - }, + ...users.map((user) => { + return { value: `${user},${user}`, title: user }; + }), ], }, }, From 1869daeb0f784418341e59e50c2d572e4b62c8ba Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Mon, 8 Jul 2024 17:32:44 +0700 Subject: [PATCH 208/300] feat: comment ad (#493) --- .../core/hooks/collections/useGlobalFeed.ts | 4 +- src/v4/core/hooks/useLiveCollection.ts | 2 +- src/v4/core/hooks/usePagination.ts | 21 ++-- .../social/components/Newsfeed/Newsfeed.tsx | 2 +- .../PostComment/PostComment.module.css | 2 +- .../PostCommentList/PostCommentList.tsx | 44 ++++--- .../CommentAd/CommentAd.module.css | 111 ++++++++++++++++++ .../CommentAd/CommentAd.stories.tsx | 14 +++ .../CommentAd/CommentAd.tsx | 84 +++++++++++++ .../pages/PostDetailPage/PostDetailPage.tsx | 3 +- 10 files changed, 252 insertions(+), 35 deletions(-) create mode 100644 src/v4/social/internal-components/CommentAd/CommentAd.module.css create mode 100644 src/v4/social/internal-components/CommentAd/CommentAd.stories.tsx create mode 100644 src/v4/social/internal-components/CommentAd/CommentAd.tsx diff --git a/src/v4/core/hooks/collections/useGlobalFeed.ts b/src/v4/core/hooks/collections/useGlobalFeed.ts index a392d1552..6d3bea6d9 100644 --- a/src/v4/core/hooks/collections/useGlobalFeed.ts +++ b/src/v4/core/hooks/collections/useGlobalFeed.ts @@ -2,7 +2,7 @@ import { FeedRepository } from '@amityco/ts-sdk'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { usePaginatorApi } from '../usePagination'; -const useGlobalFeed = () => { +export const useGlobalFeed = () => { const [items, setItems] = useState<Array<Amity.Post | Amity.Ad>>([]); const [isLoading, setIsLoading] = useState(false); const [queryToken, setQueryToken] = useState<string | null>(null); @@ -77,5 +77,3 @@ const useGlobalFeed = () => { refetch, }; }; - -export default useGlobalFeed; diff --git a/src/v4/core/hooks/useLiveCollection.ts b/src/v4/core/hooks/useLiveCollection.ts index 4c1a38429..586e25bcc 100644 --- a/src/v4/core/hooks/useLiveCollection.ts +++ b/src/v4/core/hooks/useLiveCollection.ts @@ -50,7 +50,7 @@ function useLiveCollection<TCallback, TParams>({ loadMoreFnRef.current = response.onNextPage; callback(response); }, - [shouldCall, setItems, setIsLoading, setHasMore, loadMoreFnRef, callback], + [shouldCall, loadMoreFnRef], ); useEffect(() => { diff --git a/src/v4/core/hooks/usePagination.ts b/src/v4/core/hooks/usePagination.ts index 0cc165faa..228700fea 100644 --- a/src/v4/core/hooks/usePagination.ts +++ b/src/v4/core/hooks/usePagination.ts @@ -82,7 +82,7 @@ const usePaginatorCore = <T>({ } return newItems; }, - [currentAdIndex, currentIndex, frequency, itemWithAds, recommendedAds, adSettings?.enabled], + [frequency, recommendedAds, adSettings?.enabled, getItemId], ); return { combineItemsWithAds }; @@ -127,22 +127,19 @@ export const usePaginator = <TCallback, TParams>({ getItemId, }); - const liveCollectionCallback = useCallback<Amity.LiveCollectionCallback<TCallback>>( - (response) => { - const newItems = combineItemsWithAds(response.data).flatMap((item) => item); - - callback({ ...response, data: newItems }); - }, - [combineItemsWithAds], - ); - - return useLiveCollection({ + const { items, ...rest } = useLiveCollection({ fetcher, params, - callback: liveCollectionCallback, config, shouldCall, }); + + const itemWithAds = useMemo(() => combineItemsWithAds(items).flatMap((item) => item), [items]); + + return { + ...rest, + items: itemWithAds, + }; }; export const usePaginatorApi = <T>(params: { diff --git a/src/v4/social/components/Newsfeed/Newsfeed.tsx b/src/v4/social/components/Newsfeed/Newsfeed.tsx index a4d000e05..552ec37a5 100644 --- a/src/v4/social/components/Newsfeed/Newsfeed.tsx +++ b/src/v4/social/components/Newsfeed/Newsfeed.tsx @@ -1,6 +1,6 @@ import React, { useRef, useState } from 'react'; import { StoryTab } from '~/v4/social/components/StoryTab'; -import useGlobalFeed from '~/v4/core/hooks/collections/useGlobalFeed'; +import { useGlobalFeed } from '~/v4/core/hooks/collections/useGlobalFeed'; import { EmptyNewsfeed } from '~/v4/social/components/EmptyNewsFeed'; import { GlobalFeed } from '~/v4/social/components/GlobalFeed'; diff --git a/src/v4/social/components/PostComment/PostComment.module.css b/src/v4/social/components/PostComment/PostComment.module.css index 5080a3269..d30a25e8e 100644 --- a/src/v4/social/components/PostComment/PostComment.module.css +++ b/src/v4/social/components/PostComment/PostComment.module.css @@ -1,7 +1,7 @@ .postComment { width: 100%; display: grid; - grid-template-columns: min-content 1fr; + grid-template-columns: min-content minmax(0, 1fr); gap: 0.5rem; } diff --git a/src/v4/social/components/PostCommentList/PostCommentList.tsx b/src/v4/social/components/PostCommentList/PostCommentList.tsx index 07ed51b1c..4b486025a 100644 --- a/src/v4/social/components/PostCommentList/PostCommentList.tsx +++ b/src/v4/social/components/PostCommentList/PostCommentList.tsx @@ -5,8 +5,10 @@ import useIntersectionObserver from '~/v4/core/hooks/useIntersectionObserver'; import useCommentsCollection from '../../hooks/collections/useCommentsCollection'; import { useAmityComponent } from '~/v4/core/hooks/uikit/index'; import useUserSubscription from '~/v4/core/hooks/subscriptions/useUserSubscription'; -import { SubscriptionLevels } from '@amityco/ts-sdk'; +import { CommentRepository, SubscriptionLevels } from '@amityco/ts-sdk'; import useCommunitySubscription from '~/v4/core/hooks/subscriptions/useCommunitySubscription'; +import { usePaginator } from '~/v4/core/hooks/usePagination'; +import { CommentAd } from '../../internal-components/CommentAd/CommentAd'; type PostCommentListProps = { post: Amity.Post; @@ -14,6 +16,10 @@ type PostCommentListProps = { onClickReply: (comment: Amity.Comment) => void; }; +const isAmityAd = (item: Amity.Comment | Amity.InternalComment | Amity.Ad): item is Amity.Ad => { + return 'adId' in item; +}; + export const PostCommentList = ({ post, pageId = '*', onClickReply }: PostCommentListProps) => { const componentId = 'comment_tray_component'; @@ -22,14 +28,20 @@ export const PostCommentList = ({ post, pageId = '*', onClickReply }: PostCommen pageId, }); - const containerRef = React.useRef<HTMLDivElement>(null); + const containerRef = useRef<HTMLDivElement>(null); const intersectionRef = useRef<HTMLDivElement>(null); - const { comments, loadMore, hasMore, isLoading } = useCommentsCollection({ - referenceId: post.postId, - referenceType: 'post', - limit: 5, - includeDeleted: true, + const { items, loadMore, hasMore, isLoading } = usePaginator({ + fetcher: CommentRepository.getComments, + params: { + referenceId: post.postId, + referenceType: 'post', + limit: 5, + includeDeleted: true, + }, + placement: 'comment' as Amity.AdPlacement, + pageSize: 5, + getItemId: (item) => item.commentId, }); useIntersectionObserver({ @@ -53,8 +65,6 @@ export const PostCommentList = ({ post, pageId = '*', onClickReply }: PostCommen shouldSubscribe: post.targetType === 'community', }); - if (!comments) return null; - return ( <div className={styles.postCommentList__container} @@ -62,12 +72,14 @@ export const PostCommentList = ({ post, pageId = '*', onClickReply }: PostCommen ref={containerRef} data-qa-anchor={accessibilityId} > - {comments.map((comment) => { - return ( + {items.map((item) => { + return isAmityAd(item) ? ( + <CommentAd key={item.adId} ad={item} /> + ) : ( <PostComment - key={comment.commentId} - comment={comment as Amity.Comment} - onClickReply={onClickReply} + key={item.commentId} + comment={item as Amity.Comment} + onClickReply={(comment) => onClickReply?.(comment)} componentId={componentId} postTargetId={post.targetId} postTargetType={post.targetType} @@ -78,7 +90,9 @@ export const PostCommentList = ({ post, pageId = '*', onClickReply }: PostCommen {/* <div className={styles.postCommentList__viewAllComments__button}> <Typography.BodyBold>View all comments...</Typography.BodyBold> </div> */} - <div ref={intersectionRef} className={styles.postCommentList__container_intersection} /> + {!isLoading && ( + <div ref={intersectionRef} className={styles.postCommentList__container_intersection} /> + )} </div> ); }; diff --git a/src/v4/social/internal-components/CommentAd/CommentAd.module.css b/src/v4/social/internal-components/CommentAd/CommentAd.module.css new file mode 100644 index 000000000..8ae4d0d3d --- /dev/null +++ b/src/v4/social/internal-components/CommentAd/CommentAd.module.css @@ -0,0 +1,111 @@ +.commentAd { + width: 100%; + position: relative; + height: 100%; +} + +.commentAd__container { + display: grid; + grid-template-columns: min-content minmax(0, 1fr); + gap: 0.5rem; + width: 100%; +} + +.commentAd__details { + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.commentAd__avatar { + width: 2rem; + height: 2rem; +} + +.commentAd__content { + display: flex; + flex-direction: column; + gap: 0.25rem; + justify-content: center; + align-items: start; + background-color: var(--asc-color-base-shade4); + border-radius: 0 0.75rem 0.75rem; + padding: 0.75rem; + max-width: max-content; +} + +.commentAd__content__username { + color: var(--asc-color-base-default); +} + +.commentAd__content__text { + color: var(--asc-color-base-default); + margin: var(--asc-spacing-s1) 0; +} + +.commentAd__adCard { + display: flex; + border-radius: 0.5rem; + border: 1px solid var(--asc-color-base-shade4); + background-color: var(--asc-color-base-background); + height: 100%; + cursor: pointer; +} + +.commentAd__adCard__imageContainer { + aspect-ratio: 1 / 1; + overflow: hidden; + border-radius: 0.5rem 0 0 0.5rem; + flex-basis: 35%; + width: 35%; +} + +.commentAd__adCard__image { + width: 100%; + height: 100%; + object-fit: cover; +} + +.commentAd__adCard__detail { + padding: 0.75rem; + display: flex; + flex-direction: column; + justify-content: center; + align-items: flex-start; + gap: 0.5rem; + flex-basis: 65%; + width: 65%; + overflow: hidden; +} + +.commentAd__adCard__caption { + color: var(--asc-color-base-shade1); +} + +.commentAd__adCard__description { + color: var(--asc-color-base-default); +} + +.commentAd__adCard__button { + background-color: var(--asc-color-primary-default); + color: var(--asc-color-white); + padding: 0.375rem 0.75rem; + border-radius: var(--asc-border-radius-md); + box-sizing: border-box; +} + +.commentAd__adCard__button__text { + width: 100%; + word-wrap: break-word; + overflow: hidden; + text-overflow: ellipsis; +} + +.infoIcon { + position: absolute; + width: 1rem; + height: 1rem; + fill: var(--asc-color-base-shade3); + right: 0.25rem; + top: 0.25rem; +} diff --git a/src/v4/social/internal-components/CommentAd/CommentAd.stories.tsx b/src/v4/social/internal-components/CommentAd/CommentAd.stories.tsx new file mode 100644 index 000000000..792215d86 --- /dev/null +++ b/src/v4/social/internal-components/CommentAd/CommentAd.stories.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { CommentAd } from './CommentAd'; + +export default { + title: 'v4-social/internal-components/CommentAd', +}; + +export const CommentAdStory = { + render: () => ( + <div style={{ width: '80%', margin: 'auto' }}> + <CommentAd /> + </div> + ), +}; diff --git a/src/v4/social/internal-components/CommentAd/CommentAd.tsx b/src/v4/social/internal-components/CommentAd/CommentAd.tsx new file mode 100644 index 000000000..8a2aa1919 --- /dev/null +++ b/src/v4/social/internal-components/CommentAd/CommentAd.tsx @@ -0,0 +1,84 @@ +import React from 'react'; +import styles from './CommentAd.module.css'; +import { Avatar, Typography } from '~/v4/core/components'; +import { AdsBadge } from '../AdsBadge/AdsBadge'; +import Broadcast from '~/v4/icons/Broadcast'; +import InfoCircle from '~/v4/icons/InfoCircle'; +import { useAmityComponent } from '~/v4/core/hooks/uikit/index'; +import { Button } from '~/v4/core/natives/Button'; +import useImage from '~/v4/core/hooks/useImage'; + +interface CommentAdProps { + pageId?: string; + ad: Amity.Ad; +} + +export const CommentAd = ({ pageId = '*', ad }: CommentAdProps) => { + const componentId = 'comment_tray_component'; + const { themeStyles } = useAmityComponent({ + pageId, + componentId, + }); + + const avatarFile = useImage({ fileId: ad.advertiser?.avatar?.fileId }); + const avatarUrl = avatarFile || ad.advertiser?.avatar?.fileUrl || ''; + + const adImageFile = useImage({ fileId: ad.image1_1?.fileId }); + const adImageUrl = adImageFile || ad.image1_1?.fileUrl || ''; + + const handleCallToActionClick = () => { + window?.open(ad?.callToActionUrl, '_blank'); + }; + + return ( + <div className={styles.commentAd} style={themeStyles}> + <InfoCircle className={styles.infoIcon} /> + <div className={styles.commentAd__container}> + <div className={styles.commentAd__avatar}> + <Avatar avatarUrl={avatarUrl} defaultImage={<Broadcast />} /> + </div> + <div className={styles.commentAd__details}> + <div className={styles.commentAd__content}> + <Typography.BodyBold className={styles.commentAd__content__username}> + {ad.advertiser?.companyName} + </Typography.BodyBold> + + <AdsBadge /> + + <div> + <Typography.Body className={styles.commentAd__content__text}> + {ad.body} + </Typography.Body> + </div> + + <div className={styles.commentAd__adCard} onClick={handleCallToActionClick}> + <div className={styles.commentAd__adCard__imageContainer}> + <img className={styles.commentAd__adCard__image} src={adImageUrl} /> + </div> + <div className={styles.commentAd__adCard__detail}> + <div> + <Typography.Caption className={styles.commentAd__adCard__caption}> + {ad.headline} + </Typography.Caption> + <Typography.BodyBold className={styles.commentAd__adCard__description}> + {ad.description} + </Typography.BodyBold> + </div> + {ad.callToActionUrl ? ( + <Button + className={styles.commentAd__adCard__button} + onPress={handleCallToActionClick} + > + <Typography.CaptionBold className={styles.commentAd__adCard__button__text}> + {ad.callToAction} + </Typography.CaptionBold> + </Button> + ) : null} + </div> + </div> + </div> + </div> + </div> + </div> + ); +}; diff --git a/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx b/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx index 6ccff95ad..4d7915e2f 100644 --- a/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx +++ b/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx @@ -13,7 +13,6 @@ import styles from './PostDetailPage.module.css'; import { useDrawer } from '~/v4/core/providers/DrawerProvider'; import { PostCommentComposer } from '../../components/PostCommentComposer/PostCommentComposer'; import { PostCommentList } from '../../components/PostCommentList/PostCommentList'; -import useCommentsCollection from '../../hooks/collections/useCommentsCollection'; interface PostDetailPageProps { id: string; @@ -26,7 +25,7 @@ export function PostDetailPage({ id }: PostDetailPageProps) { pageId, }); const { onBack } = useNavigation(); - const [replyComment, setReplyComment] = useState<Amity.Comment>(); + const [replyComment, setReplyComment] = useState<Amity.Comment | undefined>(); const { setDrawerData, removeDrawerData } = useDrawer(); From a38684566fed2d7edbd811e9f80991b2c586638e Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Mon, 8 Jul 2024 17:41:47 +0700 Subject: [PATCH 209/300] chore: update ci (#495) --- .github/workflows/dev.yaml | 8 +------- .github/workflows/staging.yaml | 8 +------- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/.github/workflows/dev.yaml b/.github/workflows/dev.yaml index e8cc56112..a8dcad97a 100644 --- a/.github/workflows/dev.yaml +++ b/.github/workflows/dev.yaml @@ -18,13 +18,7 @@ jobs: NPM_FONT_AWESOME_TOKEN: ${{ secrets.NPM_FONT_AWESOME_TOKEN }} STORYBOOK_API_REGION: ${{ secrets.STORYBOOK_API_REGION }} STORYBOOK_API_KEY: ${{ secrets.STORYBOOK_API_KEY }} - STORYBOOK_USER1: ${{ secrets.STORYBOOK_USER1 }} - STORYBOOK_USER2: ${{ secrets.STORYBOOK_USER2 }} - STORYBOOK_USER3: ${{ secrets.STORYBOOK_USER3 }} - STORYBOOK_USER4: ${{ secrets.STORYBOOK_USER4 }} - STORYBOOK_USER5: ${{ secrets.STORYBOOK_USER5 }} - STORYBOOK_USER6: ${{ secrets.STORYBOOK_USER6 }} - STORYBOOK_USER7: ${{ secrets.STORYBOOK_USER7 }} + STORYBOOK_USERS: ${{ secrets.STORYBOOK_USERS }} steps: - name: git checkout diff --git a/.github/workflows/staging.yaml b/.github/workflows/staging.yaml index 3ecb19841..3d15d4011 100644 --- a/.github/workflows/staging.yaml +++ b/.github/workflows/staging.yaml @@ -17,13 +17,7 @@ jobs: NPM_FONT_AWESOME_TOKEN: ${{ secrets.NPM_FONT_AWESOME_TOKEN }} STORYBOOK_API_REGION: ${{ secrets.STORYBOOK_API_REGION }} STORYBOOK_API_KEY: ${{ secrets.STORYBOOK_API_KEY }} - STORYBOOK_USER1: ${{ secrets.STORYBOOK_USER1 }} - STORYBOOK_USER2: ${{ secrets.STORYBOOK_USER2 }} - STORYBOOK_USER3: ${{ secrets.STORYBOOK_USER3 }} - STORYBOOK_USER4: ${{ secrets.STORYBOOK_USER4 }} - STORYBOOK_USER5: ${{ secrets.STORYBOOK_USER5 }} - STORYBOOK_USER6: ${{ secrets.STORYBOOK_USER6 }} - STORYBOOK_USER7: ${{ secrets.STORYBOOK_USER7 }} + STORYBOOK_USERS: ${{ secrets.STORYBOOK_USERS }} steps: - name: git checkout From 8d0a67174d5768d63ed508307fb92896217d85a7 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Tue, 9 Jul 2024 13:00:37 +0700 Subject: [PATCH 210/300] fix: fix slice index (#498) --- src/v4/core/hooks/usePagination.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v4/core/hooks/usePagination.ts b/src/v4/core/hooks/usePagination.ts index 228700fea..716491ea2 100644 --- a/src/v4/core/hooks/usePagination.ts +++ b/src/v4/core/hooks/usePagination.ts @@ -60,7 +60,7 @@ const usePaginatorCore = <T>({ let runningAdIndex = currentAdIndex; let runningIndex = currentIndex; const suffixItems: Array<[T] | [T, Amity.Ad]> = newItems - .slice(startIndex + 1) + .slice(startIndex) .map((newItem) => { runningIndex = runningIndex + 1; const shouldPlaceAd = runningIndex % frequency.value === 0; From 9f9398e8013d65d6db3e7a12eea603659a8597a5 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Tue, 9 Jul 2024 15:21:29 +0700 Subject: [PATCH 211/300] fix: ad live collection integration (#503) --- src/v4/core/hooks/usePagination.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/v4/core/hooks/usePagination.ts b/src/v4/core/hooks/usePagination.ts index 716491ea2..916b366a5 100644 --- a/src/v4/core/hooks/usePagination.ts +++ b/src/v4/core/hooks/usePagination.ts @@ -51,12 +51,21 @@ const usePaginatorCore = <T>({ }) .filter(isNonNullable); + const startItem = prevItemWithAds[0]; const latestItem = prevItemWithAds[prevItemWithAds.length - 1]; const startIndex = latestItem ? newItems.findIndex((newItem) => getItemId(newItem) === getItemId(latestItem[0])) : 0; + const topIndex = startItem + ? newItems.findIndex((newItem) => getItemId(newItem) === getItemId(startItem[0])) + : 0; + + const newestItems: Array<[T]> = (newItems || []).slice(0, topIndex).map((item) => [item]); + + const prevItems = [...newestItems, ...prevItemWithAds]; + let runningAdIndex = currentAdIndex; let runningIndex = currentIndex; const suffixItems: Array<[T] | [T, Amity.Ad]> = newItems @@ -75,8 +84,8 @@ const usePaginatorCore = <T>({ setCurrentAdIndex(runningAdIndex); setCurrentIndex(runningIndex); - setItemWithAds([...prevItemWithAds, ...suffixItems]); - return [...prevItemWithAds, ...suffixItems].flatMap((item) => item); + setItemWithAds([...prevItems, ...suffixItems]); + return [...prevItems, ...suffixItems].flatMap((item) => item); } else if (frequency?.type === 'time-window') { return [...newItems.slice(0, 1), recommendedAds[0], ...newItems.slice(1)]; } From a0ec6a537eace69a44ed251d10d27476ceee0f9b Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Tue, 9 Jul 2024 15:29:21 +0700 Subject: [PATCH 212/300] fix: ASC-00000 - fix react error (#502) * fix: fix svg props * fix: fix react error * fix: svg props --- src/v4/icons/QuickReactionIcon.tsx | 6 ++-- src/v4/icons/Verified.tsx | 2 +- .../social/components/Newsfeed/Newsfeed.tsx | 8 ++--- .../social/components/PostContent/Crying.tsx | 2 +- src/v4/social/components/PostContent/Fire.tsx | 6 ++-- .../social/components/PostContent/Happy.tsx | 2 +- .../PostContent/ImageContent/ImageContent.tsx | 6 +++- src/v4/social/components/PostContent/Like.tsx | 4 +-- src/v4/social/components/PostContent/Love.tsx | 4 +-- .../PostContent/TextContent/TextContent.tsx | 32 +++++++++---------- .../PostContent/VideoContent/VideoContent.tsx | 2 +- .../elements/CameraButton/CameraButton.tsx | 4 +-- .../social/elements/FileButton/FileButton.tsx | 2 +- .../elements/ImageButton/ImageButton.tsx | 4 +-- .../social/elements/ReactionButton/Crying.tsx | 2 +- .../social/elements/ReactionButton/Fire.tsx | 6 ++-- .../social/elements/ReactionButton/Happy.tsx | 2 +- .../social/elements/ReactionButton/Like.tsx | 4 +-- .../social/elements/ReactionButton/Love.tsx | 4 +-- .../elements/VideoButton/VideoButton.tsx | 4 +-- 20 files changed, 54 insertions(+), 52 deletions(-) diff --git a/src/v4/icons/QuickReactionIcon.tsx b/src/v4/icons/QuickReactionIcon.tsx index 7fd55731f..11989d2c6 100644 --- a/src/v4/icons/QuickReactionIcon.tsx +++ b/src/v4/icons/QuickReactionIcon.tsx @@ -11,8 +11,8 @@ export const QuickReactionIcon = (props: React.SVGProps<SVGSVGElement>) => { {...props} > <path - fill-rule="evenodd" - clip-rule="evenodd" + fillRule="evenodd" + clipRule="evenodd" d="M11.0766 3.686C11.6827 3.45305 12.3021 3.33948 12.9061 3.3335C14.7179 3.31558 16.3937 4.2689 17.1729 5.89093L17.1895 5.92546L17.202 5.9624C17.6819 7.39004 17.5517 8.83248 17.0292 10.1008C16.7326 9.72289 16.3721 9.39755 15.964 9.14106C16.2254 8.26924 16.2483 7.32915 15.9455 6.39919C15.3774 5.24797 14.2332 4.57487 12.9278 4.60378C12.4981 4.61329 12.0515 4.70045 11.6019 4.86956C11.1731 5.08422 10.8198 5.36694 10.5238 5.73675L10.0093 6.37992L9.47729 5.74959C8.38507 4.45434 6.19511 4.17996 4.72103 5.44206C3.63692 6.5523 3.60343 8.44184 4.37082 10.0205C5.31606 11.6937 7.28176 13.3534 9.14794 14.7202L9.15794 14.7275C9.27155 14.8082 9.38012 14.8942 9.48451 14.9769C9.57334 15.0473 9.65915 15.1152 9.74247 15.1755C9.83249 15.2407 9.91925 15.2913 9.98761 15.3273C10.0527 15.3006 10.1319 15.2662 10.2153 15.2157C10.2662 15.1848 10.3205 15.1506 10.3776 15.1136C10.6388 15.4735 10.9562 15.7901 11.3169 16.0503C11.1818 16.1421 11.0508 16.227 10.9257 16.3029C10.8808 16.33 10.8388 16.3568 10.7982 16.3826C10.5856 16.5181 10.4104 16.6296 10.0526 16.6594L9.96091 16.6666L9.87002 16.6497C9.46411 16.575 9.1867 16.3802 8.94112 16.2025C8.80472 16.1038 8.67845 16.0042 8.57194 15.9202C8.48669 15.8529 8.4141 15.7957 8.3591 15.7569L8.35494 15.7536L8.34994 15.7496C6.42696 14.342 4.31681 12.6298 3.18676 10.6147L3.17842 10.5987L3.17008 10.5834C2.21058 8.62424 2.1731 6.14474 3.7738 4.53473L3.79047 4.51786L3.80882 4.50181C5.63352 2.91773 8.30066 3.019 9.97595 4.41428C10.2872 4.13498 10.6374 3.8993 11.0216 3.71089L11.0491 3.69724L11.0766 3.686Z" fill="currentColor" /> @@ -20,7 +20,7 @@ export const QuickReactionIcon = (props: React.SVGProps<SVGSVGElement>) => { d="M13.6839 10.0507L13.6839 10.0501L13.6725 10.0522C13.5717 10.0706 13.4807 10.1238 13.4151 10.2025C13.3496 10.2811 13.3137 10.3803 13.3137 10.4827C13.3137 10.4827 13.3137 10.4827 13.3137 10.4827V12.2327H11.5637V12.2322L11.5527 12.2332L11.4964 12.2382L11.4964 12.2376L11.485 12.2397C11.3842 12.2581 11.2932 12.3113 11.2276 12.39C11.1621 12.4686 11.1262 12.5678 11.1262 12.6702H11.1257L11.1267 12.6812L11.1317 12.7375L11.1312 12.7375L11.1333 12.7489C11.1517 12.8496 11.2049 12.9407 11.2835 13.0063C11.3622 13.0718 11.4613 13.1077 11.5637 13.1077C11.5637 13.1077 11.5637 13.1077 11.5638 13.1077H13.3137V14.8577H13.3132L13.3142 14.8687L13.3192 14.925L13.3187 14.925L13.3208 14.9364C13.3392 15.0371 13.3924 15.1282 13.471 15.1938L13.5511 15.0977L13.471 15.1938C13.5497 15.2593 13.6489 15.2952 13.7513 15.2952L13.7513 15.2957L13.7623 15.2947L13.8185 15.2897L13.8186 15.2902L13.83 15.2881C13.9307 15.2697 14.0218 15.2165 14.0873 15.1379C14.1529 15.0592 14.1887 14.96 14.1887 14.8577C14.1887 14.8577 14.1887 14.8576 14.1887 14.8576V13.1077H15.9387V13.1082L15.9498 13.1072L16.006 13.1022L16.0061 13.1027L16.0175 13.1006C16.1182 13.0822 16.2093 13.029 16.2748 12.9504C16.3404 12.8717 16.3763 12.7725 16.3762 12.6701L16.3767 12.6701L16.3757 12.6591L16.3707 12.6028L16.3713 12.6028L16.3692 12.5914C16.3507 12.4907 16.2976 12.3996 16.2189 12.3341C16.1403 12.2685 16.0411 12.2326 15.9387 12.2327C15.9387 12.2327 15.9387 12.2327 15.9387 12.2327H14.1887V10.4827H14.1892L14.1882 10.4716L14.1832 10.4153L14.1838 10.4153L14.1817 10.4039C14.1632 10.3032 14.1101 10.2121 14.0314 10.1466C13.9527 10.081 13.8536 10.0451 13.7512 10.0452V10.0447L13.7402 10.0457L13.6839 10.0507Z" fill="currentColor" stroke="currentColor" - stroke-width="0.25" + strokeWidth="0.25" /> </svg> ); diff --git a/src/v4/icons/Verified.tsx b/src/v4/icons/Verified.tsx index 7fd75ef2b..c3024f083 100644 --- a/src/v4/icons/Verified.tsx +++ b/src/v4/icons/Verified.tsx @@ -13,7 +13,7 @@ const Svg = (props: React.SVGProps<SVGSVGElement>) => ( d="M20.4187 17.2429V15.4838L21.6578 14.2365C21.6585 14.2358 21.6591 14.2351 21.6598 14.2345C22.3179 13.5807 22.7516 12.8355 22.75 11.9942C22.7484 11.1537 22.3123 10.4094 21.6598 9.75682L20.4187 8.51578V6.7571C20.4187 5.82907 20.1982 4.99346 19.6024 4.39762C19.0065 3.80179 18.1709 3.58128 17.2429 3.58128H15.4842L14.2432 2.34024L13.7265 2.85697L14.2432 2.34024C13.5864 1.68348 12.8389 1.24622 11.9953 1.24784C11.1522 1.24946 10.4079 1.68897 9.75583 2.34991L8.51619 3.58128H6.75709C5.8332 3.58128 4.99652 3.79543 4.39906 4.38966C3.80081 4.98466 3.58127 5.82245 3.58127 6.7571V8.51578L2.34222 9.75484C2.34186 9.75519 2.3415 9.75555 2.34114 9.75591C1.68254 10.4099 1.24839 11.1554 1.25 11.9971C1.25162 12.8372 1.68721 13.5812 2.33924 14.2335C2.33957 14.2338 2.3399 14.2342 2.34023 14.2345L3.58127 15.4838V17.2429C3.58127 18.1709 3.80178 19.0066 4.39761 19.6024C4.99345 20.1982 5.82907 20.4187 6.75709 20.4187H8.51577L9.75483 21.6578C9.75525 21.6582 9.75567 21.6586 9.75609 21.6591C10.4102 22.3177 11.1557 22.7516 11.9982 22.75C12.8389 22.7484 13.5848 22.3133 14.2412 21.6618L14.2432 21.6598L15.4842 20.4187H17.2429C18.1709 20.4187 19.0065 20.1982 19.6024 19.6024C20.1982 19.0066 20.4187 18.1709 20.4187 17.2429ZM11.1362 14.6127L15.3255 8.63293C15.3257 8.63263 15.3259 8.63232 15.3261 8.63202C15.3555 8.59049 15.3769 8.56736 15.3897 8.55517C15.3961 8.54919 15.4003 8.54586 15.4025 8.54426L15.4048 8.54269L15.4054 8.54234L15.4054 8.54232L15.4062 8.542C15.4072 8.54166 15.4101 8.54065 15.4157 8.53942C15.4272 8.53692 15.4517 8.53308 15.4953 8.53308C15.5882 8.53308 15.6273 8.563 15.6388 8.57409C15.649 8.58379 15.6664 8.60504 15.6669 8.65903C15.6668 8.65902 15.6656 8.67471 15.6559 8.70306C15.6448 8.73553 15.6277 8.7697 15.6066 8.79916L15.6062 8.79975L10.738 15.6292C10.7079 15.6666 10.6872 15.6779 10.6764 15.6829C10.6626 15.6894 10.6288 15.7017 10.5567 15.7017C10.4668 15.7017 10.4177 15.6786 10.3664 15.6272L7.5945 12.5262L7.58765 12.5185L7.58058 12.511C7.58023 12.5106 7.57454 12.5043 7.5688 12.489C7.56299 12.4736 7.55926 12.4545 7.55926 12.4347C7.55926 12.3583 7.58347 12.332 7.59029 12.3253C7.59747 12.3182 7.63028 12.2892 7.72221 12.2892C7.78716 12.2892 7.81058 12.2992 7.8149 12.3011L7.81503 12.3012C7.81882 12.3028 7.8367 12.3105 7.87134 12.3478L9.9633 14.6828L10.5939 15.3867L11.1362 14.6127Z" fill="#1054DE" stroke="white" - stroke-width="1.5" + strokeWidth="1.5" /> </svg> ); diff --git a/src/v4/social/components/Newsfeed/Newsfeed.tsx b/src/v4/social/components/Newsfeed/Newsfeed.tsx index 552ec37a5..dde75c597 100644 --- a/src/v4/social/components/Newsfeed/Newsfeed.tsx +++ b/src/v4/social/components/Newsfeed/Newsfeed.tsx @@ -18,8 +18,8 @@ const Spinner = (props: React.SVGProps<SVGSVGElement>) => { fill="none" > <path - fill-rule="evenodd" - clip-rule="evenodd" + fillRule="evenodd" + clipRule="evenodd" d="M11.1122 5C11.1122 5.39449 10.7924 5.71429 10.3979 5.71429C10.0035 5.71429 9.68366 5.39449 9.68366 5V0.714286C9.68366 0.319797 10.0035 0 10.3979 0C10.7924 0 11.1122 0.319797 11.1122 0.714286V5ZM8.25509 6.28846C8.59673 6.09122 8.71378 5.65437 8.51654 5.31273L6.37368 1.60119C6.17644 1.25955 5.73959 1.1425 5.39795 1.33975C5.05631 1.53699 4.93926 1.97384 5.1365 2.31548L7.27936 6.02702C7.4766 6.36865 7.91346 6.48571 8.25509 6.28846ZM6.42496 6.88141C6.7666 7.07865 6.88366 7.51551 6.68641 7.85714C6.48917 8.19878 6.05232 8.31583 5.71068 8.11859L1.99914 5.97573C1.6575 5.77849 1.54045 5.34164 1.7377 5C1.93494 4.65836 2.37179 4.54131 2.71343 4.73855L6.42496 6.88141ZM6.11224 10C6.11224 9.60551 5.79244 9.28571 5.39795 9.28571H1.11223C0.717746 9.28571 0.397949 9.60551 0.397949 10C0.397949 10.3945 0.717746 10.7143 1.11224 10.7143H5.39795C5.79244 10.7143 6.11224 10.3945 6.11224 10ZM5.71068 11.8814C6.05232 11.6842 6.48917 11.8012 6.68641 12.1429C6.88366 12.4845 6.7666 12.9213 6.42497 13.1186L2.71343 15.2614C2.37179 15.4587 1.93494 15.3416 1.7377 15C1.54045 14.6584 1.6575 14.2215 1.99914 14.0243L5.71068 11.8814ZM8.25509 13.7115C7.91345 13.5143 7.4766 13.6313 7.27936 13.973L5.1365 17.6845C4.93926 18.0262 5.05631 18.463 5.39795 18.6603C5.73959 18.8575 6.17644 18.7404 6.37368 18.3988L8.51654 14.6873C8.71378 14.3456 8.59673 13.9088 8.25509 13.7115ZM10.3979 14.2857C10.0035 14.2857 9.68366 14.6055 9.68366 15V19.2857C9.68366 19.6802 10.0035 20 10.3979 20C10.7924 20 11.1122 19.6802 11.1122 19.2857V15C11.1122 14.6055 10.7924 14.2857 10.3979 14.2857ZM12.5408 6.28846C12.8824 6.48571 13.3193 6.36865 13.5165 6.02702L15.6594 2.31548C15.8566 1.97384 15.7396 1.53699 15.3979 1.33975C15.0563 1.1425 14.6195 1.25956 14.4222 1.60119L12.2794 5.31273C12.0821 5.65437 12.1992 6.09122 12.5408 6.28846ZM15.0852 8.11859C14.7436 8.31583 14.3067 8.19878 14.1095 7.85714C13.9122 7.51551 14.0293 7.07866 14.3709 6.88141L18.0825 4.73855C18.4241 4.54131 18.861 4.65836 19.0582 5C19.2554 5.34164 19.1384 5.77849 18.7968 5.97573L15.0852 8.11859ZM14.6837 10C14.6837 10.3945 15.0035 10.7143 15.3979 10.7143H19.6837C20.0782 10.7143 20.3979 10.3945 20.3979 10C20.3979 9.60551 20.0782 9.28571 19.6837 9.28571H15.3979C15.0035 9.28571 14.6837 9.60551 14.6837 10ZM14.3709 13.1186C14.0293 12.9213 13.9122 12.4845 14.1095 12.1429C14.3067 11.8012 14.7436 11.6842 15.0852 11.8814L18.7968 14.0243C19.1384 14.2215 19.2554 14.6584 19.0582 15C18.861 15.3416 18.4241 15.4587 18.0825 15.2614L14.3709 13.1186ZM12.5408 13.7115C12.1992 13.9088 12.0821 14.3456 12.2794 14.6873L14.4222 18.3988C14.6195 18.7404 15.0563 18.8575 15.3979 18.6603C15.7396 18.463 15.8566 18.0262 15.6594 17.6845L13.5165 13.973C13.3193 13.6313 12.8824 13.5143 12.5408 13.7115Z" fill="url(#paint0_angular_1709_10374)" /> @@ -32,8 +32,8 @@ const Spinner = (props: React.SVGProps<SVGSVGElement>) => { gradientUnits="userSpaceOnUse" gradientTransform="translate(10.3979 10) scale(10)" > - <stop offset="0.669733" stop-color="#595F67" /> - <stop offset="0.716307" stop-color="#262626" stop-opacity="0.01" /> + <stop offset="0.669733" stopColor="#595F67" /> + <stop offset="0.716307" stopColor="#262626" stopOpacity="0.01" /> </radialGradient> </defs> </svg> diff --git a/src/v4/social/components/PostContent/Crying.tsx b/src/v4/social/components/PostContent/Crying.tsx index 72597127f..14127beb6 100644 --- a/src/v4/social/components/PostContent/Crying.tsx +++ b/src/v4/social/components/PostContent/Crying.tsx @@ -9,7 +9,7 @@ const Crying = ({ ...props }: React.SVGProps<SVGSVGElement>) => ( xmlns="http://www.w3.org/2000/svg" {...props} > - <g clip-path="url(#clip0_1709_7904)"> + <g clipPath="url(#clip0_1709_7904)"> <path d="M16 32.5C24.8366 32.5 32 25.3366 32 16.5C32 7.66344 24.8366 0.5 16 0.5C7.16344 0.5 0 7.66344 0 16.5C0 25.3366 7.16344 32.5 16 32.5Z" fill="#FFD54F" diff --git a/src/v4/social/components/PostContent/Fire.tsx b/src/v4/social/components/PostContent/Fire.tsx index a93727cd2..66ce909f4 100644 --- a/src/v4/social/components/PostContent/Fire.tsx +++ b/src/v4/social/components/PostContent/Fire.tsx @@ -10,7 +10,7 @@ const Fire = ({ ...props }: React.SVGProps<SVGSVGElement>) => ( {...props} > <circle cx="16" cy="16.5" r="16" fill="url(#paint0_linear_1709_7888)" /> - <g clip-path="url(#clip0_1709_7888)"> + <g clipPath="url(#clip0_1709_7888)"> <path d="M21.8752 10.2903C21.3417 9.70788 20.5123 8.92173 20.1595 10.6968C20.0395 11.3004 19.7591 11.7656 19.5189 12.1402C19.2599 10.8296 18.4808 9.47308 17.7467 8.55843C17.472 8.21623 16.4973 6.85588 16.3101 4.58008C16.2821 4.24043 15.8787 4.08093 15.6263 4.31003C13.2147 6.49923 11.891 9.51013 11.8419 12.8998C11.8419 12.8998 10.8377 12.0533 10.2921 10.4786C10.1452 10.0546 9.57671 9.97368 9.33006 10.3486C9.28271 10.4206 9.23881 10.4927 9.19921 10.5627C7.33856 13.8527 6.44351 17.848 7.27096 21.565C8.65446 27.7898 17.8062 29.5301 22.6511 25.6177C27.3914 21.7899 25.8846 14.6654 21.8752 10.2903Z" fill="#ED694A" @@ -41,8 +41,8 @@ const Fire = ({ ...props }: React.SVGProps<SVGSVGElement>) => ( y2="29.3" gradientUnits="userSpaceOnUse" > - <stop stop-color="#FFF09F" /> - <stop offset="1" stop-color="#FCCF5A" /> + <stop stopColor="#FFF09F" /> + <stop offset="1" stopColor="#FCCF5A" /> </linearGradient> <clipPath id="clip0_1709_7888"> <rect width="25.6" height="25.6" fill="white" transform="translate(3.2002 2.09998)" /> diff --git a/src/v4/social/components/PostContent/Happy.tsx b/src/v4/social/components/PostContent/Happy.tsx index 9ff7a58cf..60805c768 100644 --- a/src/v4/social/components/PostContent/Happy.tsx +++ b/src/v4/social/components/PostContent/Happy.tsx @@ -9,7 +9,7 @@ const Happy = ({ ...props }: React.SVGProps<SVGSVGElement>) => ( xmlns="http://www.w3.org/2000/svg" {...props} > - <g id="Reactions" clip-path="url(#clip0_1709_1751)"> + <g id="Reactions" clipPath="url(#clip0_1709_1751)"> <path id="Vector" d="M16 32C24.8366 32 32 24.8366 32 16C32 7.16344 24.8366 0 16 0C7.16344 0 0 7.16344 0 16C0 24.8366 7.16344 32 16 32Z" diff --git a/src/v4/social/components/PostContent/ImageContent/ImageContent.tsx b/src/v4/social/components/PostContent/ImageContent/ImageContent.tsx index 90c63cf44..708ad50ad 100644 --- a/src/v4/social/components/PostContent/ImageContent/ImageContent.tsx +++ b/src/v4/social/components/PostContent/ImageContent/ImageContent.tsx @@ -54,7 +54,11 @@ export const ImageContent = ({ data-images-amount={Math.min(post.children.length, 4)} > {imagePosts.map((post, index) => ( - <div className={styles.imageContent__imgContainer} onClick={() => onImageClick(index)}> + <div + key={post.postId} + className={styles.imageContent__imgContainer} + onClick={() => onImageClick(index)} + > <Image fileId={post.data.fileId} /> {imageLeftCount > 0 && index === posts.length - 1 && ( <Typography.Heading className={styles.imageContent__imgCover}> diff --git a/src/v4/social/components/PostContent/Like.tsx b/src/v4/social/components/PostContent/Like.tsx index 7bbb9fa9f..e02a87e5b 100644 --- a/src/v4/social/components/PostContent/Like.tsx +++ b/src/v4/social/components/PostContent/Like.tsx @@ -23,8 +23,8 @@ const Like = ({ ...props }: React.SVGProps<SVGSVGElement>) => ( y2="39.2" gradientUnits="userSpaceOnUse" > - <stop stop-color="#63A1FF" /> - <stop offset="1" stop-color="#0041BE" /> + <stop stopColor="#63A1FF" /> + <stop offset="1" stopColor="#0041BE" /> </linearGradient> </defs> </svg> diff --git a/src/v4/social/components/PostContent/Love.tsx b/src/v4/social/components/PostContent/Love.tsx index 83b621c52..b72712707 100644 --- a/src/v4/social/components/PostContent/Love.tsx +++ b/src/v4/social/components/PostContent/Love.tsx @@ -26,8 +26,8 @@ const Love = ({ ...props }: React.SVGProps<SVGSVGElement>) => ( y2="39.2" gradientUnits="userSpaceOnUse" > - <stop stop-color="#EE5C91" /> - <stop offset="0.833333" stop-color="#E02222" /> + <stop stopColor="#EE5C91" /> + <stop offset="0.833333" stopColor="#E02222" /> </linearGradient> </defs> </svg> diff --git a/src/v4/social/components/PostContent/TextContent/TextContent.tsx b/src/v4/social/components/PostContent/TextContent/TextContent.tsx index d79ece110..794994ebb 100644 --- a/src/v4/social/components/PostContent/TextContent/TextContent.tsx +++ b/src/v4/social/components/PostContent/TextContent/TextContent.tsx @@ -46,24 +46,22 @@ export const TextContent = ({ text = '', mentionees }: TextContentProps) => { return ( <> <Typography.Body className={styles.postContent}> - <> - {chunks.map((chunk) => { - const key = `${text}-${chunk.start}-${chunk.end}`; - const sub = text.substring(chunk.start, chunk.end); - if (chunk.highlight) { - const mentionee = mentionees?.find((m) => m.index === chunk.start); - if (mentionee) { - return ( - <MentionHighlightTag key={key} mentionee={mentionee}> - {sub} - </MentionHighlightTag> - ); - } - return <span key={key}>{sub}</span>; + {chunks.map((chunk) => { + const key = `${text}-${chunk.start}-${chunk.end}`; + const sub = text.substring(chunk.start, chunk.end); + if (chunk.highlight) { + const mentionee = mentionees?.find((m) => m.index === chunk.start); + if (mentionee) { + return ( + <MentionHighlightTag key={key} mentionee={mentionee}> + {sub} + </MentionHighlightTag> + ); } - return <Linkify key={key}>{sub}</Linkify>; - })} - </> + return <span key={key}>{sub}</span>; + } + return <Linkify key={key}>{sub}</Linkify>; + })} {isShowReadMore ? ( <span className={styles.postContent__readmore} onClick={() => setIsReadMoreClick(true)}> ...Read more diff --git a/src/v4/social/components/PostContent/VideoContent/VideoContent.tsx b/src/v4/social/components/PostContent/VideoContent/VideoContent.tsx index dabe79de4..ec18a759d 100644 --- a/src/v4/social/components/PostContent/VideoContent/VideoContent.tsx +++ b/src/v4/social/components/PostContent/VideoContent/VideoContent.tsx @@ -14,7 +14,7 @@ const PlayButtonSvg = (props: React.SVGProps<SVGSVGElement>) => ( xmlns="http://www.w3.org/2000/svg" {...props} > - <g id="Icon " clip-path="url(#clip0_1936_14348)"> + <g id="Icon " clipPath="url(#clip0_1936_14348)"> <path id="Vector" d="M8.95117 5V19L19.9512 12L8.95117 5Z" /> </g> <defs> diff --git a/src/v4/social/elements/CameraButton/CameraButton.tsx b/src/v4/social/elements/CameraButton/CameraButton.tsx index 04bd86048..40021cc6b 100644 --- a/src/v4/social/elements/CameraButton/CameraButton.tsx +++ b/src/v4/social/elements/CameraButton/CameraButton.tsx @@ -26,14 +26,14 @@ const CameraSvg = (props: React.SVGProps<SVGSVGElement>) => { <path d="M19.5 19.5H4.5C4.10218 19.5 3.72064 19.342 3.43934 19.0607C3.15804 18.7794 3 18.3978 3 18V7.5C3 7.10218 3.15804 6.72064 3.43934 6.43934C3.72064 6.15804 4.10218 6 4.5 6H7.49945L8.99945 3.75H14.9995L16.4995 6H19.5C19.8978 6 20.2794 6.15804 20.5607 6.43934C20.842 6.72064 21 7.10218 21 7.5V18C21 18.3978 20.842 18.7794 20.5607 19.0607C20.2794 19.342 19.8978 19.5 19.5 19.5Z" stroke={props.stroke} - stroke-width="1.3" + strokeWidth="1.3" stroke-linecap="round" stroke-linejoin="round" /> <path d="M12 15.75C13.864 15.75 15.375 14.239 15.375 12.375C15.375 10.511 13.864 9 12 9C10.136 9 8.625 10.511 8.625 12.375C8.625 14.239 10.136 15.75 12 15.75Z" stroke={props.stroke} - stroke-width="1.3" + strokeWidth="1.3" stroke-linecap="round" stroke-linejoin="round" /> diff --git a/src/v4/social/elements/FileButton/FileButton.tsx b/src/v4/social/elements/FileButton/FileButton.tsx index 4267e0e86..4cbef5f2b 100644 --- a/src/v4/social/elements/FileButton/FileButton.tsx +++ b/src/v4/social/elements/FileButton/FileButton.tsx @@ -26,7 +26,7 @@ const FileButtonSvg = (props: React.SVGProps<SVGSVGElement>) => { <path d="M14.9995 7.49951L7.18934 15.4393C6.90804 15.7206 6.75 16.1021 6.75 16.5C6.75 16.8978 6.90804 17.2793 7.18934 17.5606C7.47064 17.8419 7.85218 18 8.25 18C8.64783 18 9.02936 17.8419 9.31066 17.5606L18.6208 8.12083C18.8993 7.84226 19.1203 7.51154 19.2711 7.14756C19.4219 6.78359 19.4995 6.39348 19.4995 5.99951C19.4995 5.60555 19.4219 5.21544 19.2711 4.85146C19.1203 4.48748 18.8993 4.15677 18.6208 3.87819C18.3422 3.59962 18.0115 3.37864 17.6475 3.22787C17.2835 3.07711 16.8934 2.99951 16.4995 2.99951C16.1055 2.99951 15.7154 3.07711 15.3514 3.22787C14.9874 3.37864 14.6567 3.59962 14.3781 3.87819L5.06802 13.318C4.22411 14.1619 3.75 15.3065 3.75 16.5C3.75 17.6934 4.22411 18.838 5.06802 19.682C5.91193 20.5259 7.05653 21 8.25 21C9.44348 21 10.5881 20.5259 11.432 19.682L19.1245 11.9995" stroke={props.stroke} - stroke-width="1.3" + strokeWidth="1.3" stroke-linecap="round" stroke-linejoin="round" /> diff --git a/src/v4/social/elements/ImageButton/ImageButton.tsx b/src/v4/social/elements/ImageButton/ImageButton.tsx index 04bd0f1c7..c26c04d3f 100644 --- a/src/v4/social/elements/ImageButton/ImageButton.tsx +++ b/src/v4/social/elements/ImageButton/ImageButton.tsx @@ -26,14 +26,14 @@ const ImageButtonSvg = (props: React.SVGProps<SVGSVGElement>) => { <path d="M20.25 4.5H3.75C3.33579 4.5 3 4.83579 3 5.25V18.75C3 19.1642 3.33579 19.5 3.75 19.5H20.25C20.6642 19.5 21 19.1642 21 18.75V5.25C21 4.83579 20.6642 4.5 20.25 4.5Z" stroke={props.stroke} - stroke-width="1.3" + strokeWidth="1.3" stroke-linecap="round" stroke-linejoin="round" /> <path d="M3 15.7499L7.71966 11.0302C7.7893 10.9606 7.87198 10.9053 7.96297 10.8676C8.05397 10.8299 8.1515 10.8105 8.24999 10.8105C8.34848 10.8105 8.44601 10.8299 8.537 10.8676C8.62799 10.9053 8.71067 10.9606 8.78032 11.0302L12.9697 15.2196C13.0393 15.2892 13.122 15.3444 13.213 15.3821C13.304 15.4198 13.4015 15.4392 13.5 15.4392C13.5985 15.4392 13.696 15.4198 13.787 15.3821C13.878 15.3444 13.9607 15.2892 14.0303 15.2196L15.9697 13.2802C16.0393 13.2106 16.122 13.1553 16.213 13.1176C16.304 13.0799 16.4015 13.0605 16.5 13.0605C16.5985 13.0605 16.696 13.0799 16.787 13.1176C16.878 13.1553 16.9607 13.2106 17.0303 13.2802L21 17.2499" stroke={props.stroke} - stroke-width="1.3" + strokeWidth="1.3" stroke-linecap="round" stroke-linejoin="round" /> diff --git a/src/v4/social/elements/ReactionButton/Crying.tsx b/src/v4/social/elements/ReactionButton/Crying.tsx index 72597127f..14127beb6 100644 --- a/src/v4/social/elements/ReactionButton/Crying.tsx +++ b/src/v4/social/elements/ReactionButton/Crying.tsx @@ -9,7 +9,7 @@ const Crying = ({ ...props }: React.SVGProps<SVGSVGElement>) => ( xmlns="http://www.w3.org/2000/svg" {...props} > - <g clip-path="url(#clip0_1709_7904)"> + <g clipPath="url(#clip0_1709_7904)"> <path d="M16 32.5C24.8366 32.5 32 25.3366 32 16.5C32 7.66344 24.8366 0.5 16 0.5C7.16344 0.5 0 7.66344 0 16.5C0 25.3366 7.16344 32.5 16 32.5Z" fill="#FFD54F" diff --git a/src/v4/social/elements/ReactionButton/Fire.tsx b/src/v4/social/elements/ReactionButton/Fire.tsx index a93727cd2..66ce909f4 100644 --- a/src/v4/social/elements/ReactionButton/Fire.tsx +++ b/src/v4/social/elements/ReactionButton/Fire.tsx @@ -10,7 +10,7 @@ const Fire = ({ ...props }: React.SVGProps<SVGSVGElement>) => ( {...props} > <circle cx="16" cy="16.5" r="16" fill="url(#paint0_linear_1709_7888)" /> - <g clip-path="url(#clip0_1709_7888)"> + <g clipPath="url(#clip0_1709_7888)"> <path d="M21.8752 10.2903C21.3417 9.70788 20.5123 8.92173 20.1595 10.6968C20.0395 11.3004 19.7591 11.7656 19.5189 12.1402C19.2599 10.8296 18.4808 9.47308 17.7467 8.55843C17.472 8.21623 16.4973 6.85588 16.3101 4.58008C16.2821 4.24043 15.8787 4.08093 15.6263 4.31003C13.2147 6.49923 11.891 9.51013 11.8419 12.8998C11.8419 12.8998 10.8377 12.0533 10.2921 10.4786C10.1452 10.0546 9.57671 9.97368 9.33006 10.3486C9.28271 10.4206 9.23881 10.4927 9.19921 10.5627C7.33856 13.8527 6.44351 17.848 7.27096 21.565C8.65446 27.7898 17.8062 29.5301 22.6511 25.6177C27.3914 21.7899 25.8846 14.6654 21.8752 10.2903Z" fill="#ED694A" @@ -41,8 +41,8 @@ const Fire = ({ ...props }: React.SVGProps<SVGSVGElement>) => ( y2="29.3" gradientUnits="userSpaceOnUse" > - <stop stop-color="#FFF09F" /> - <stop offset="1" stop-color="#FCCF5A" /> + <stop stopColor="#FFF09F" /> + <stop offset="1" stopColor="#FCCF5A" /> </linearGradient> <clipPath id="clip0_1709_7888"> <rect width="25.6" height="25.6" fill="white" transform="translate(3.2002 2.09998)" /> diff --git a/src/v4/social/elements/ReactionButton/Happy.tsx b/src/v4/social/elements/ReactionButton/Happy.tsx index 9ff7a58cf..60805c768 100644 --- a/src/v4/social/elements/ReactionButton/Happy.tsx +++ b/src/v4/social/elements/ReactionButton/Happy.tsx @@ -9,7 +9,7 @@ const Happy = ({ ...props }: React.SVGProps<SVGSVGElement>) => ( xmlns="http://www.w3.org/2000/svg" {...props} > - <g id="Reactions" clip-path="url(#clip0_1709_1751)"> + <g id="Reactions" clipPath="url(#clip0_1709_1751)"> <path id="Vector" d="M16 32C24.8366 32 32 24.8366 32 16C32 7.16344 24.8366 0 16 0C7.16344 0 0 7.16344 0 16C0 24.8366 7.16344 32 16 32Z" diff --git a/src/v4/social/elements/ReactionButton/Like.tsx b/src/v4/social/elements/ReactionButton/Like.tsx index 7bbb9fa9f..e02a87e5b 100644 --- a/src/v4/social/elements/ReactionButton/Like.tsx +++ b/src/v4/social/elements/ReactionButton/Like.tsx @@ -23,8 +23,8 @@ const Like = ({ ...props }: React.SVGProps<SVGSVGElement>) => ( y2="39.2" gradientUnits="userSpaceOnUse" > - <stop stop-color="#63A1FF" /> - <stop offset="1" stop-color="#0041BE" /> + <stop stopColor="#63A1FF" /> + <stop offset="1" stopColor="#0041BE" /> </linearGradient> </defs> </svg> diff --git a/src/v4/social/elements/ReactionButton/Love.tsx b/src/v4/social/elements/ReactionButton/Love.tsx index 83b621c52..b72712707 100644 --- a/src/v4/social/elements/ReactionButton/Love.tsx +++ b/src/v4/social/elements/ReactionButton/Love.tsx @@ -26,8 +26,8 @@ const Love = ({ ...props }: React.SVGProps<SVGSVGElement>) => ( y2="39.2" gradientUnits="userSpaceOnUse" > - <stop stop-color="#EE5C91" /> - <stop offset="0.833333" stop-color="#E02222" /> + <stop stopColor="#EE5C91" /> + <stop offset="0.833333" stopColor="#E02222" /> </linearGradient> </defs> </svg> diff --git a/src/v4/social/elements/VideoButton/VideoButton.tsx b/src/v4/social/elements/VideoButton/VideoButton.tsx index ad2588017..b126e1227 100644 --- a/src/v4/social/elements/VideoButton/VideoButton.tsx +++ b/src/v4/social/elements/VideoButton/VideoButton.tsx @@ -26,13 +26,13 @@ const VideoButtonSvg = (props: React.SVGProps<SVGSVGElement>) => { <path d="M12 21C16.9706 21 21 16.9706 21 12C21 7.02944 16.9706 3 12 3C7.02944 3 3 7.02944 3 12C3 16.9706 7.02944 21 12 21Z" stroke={props.stroke} - stroke-width="1.3" + strokeWidth="1.3" stroke-miterlimit="10" /> <path d="M16 12L10 8V16L16 12Z" stroke={props.stroke} - stroke-width="1.3" + strokeWidth="1.3" stroke-linecap="round" stroke-linejoin="round" /> From 6803df720245163402ae2f0422db839cf04cb0d3 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Tue, 9 Jul 2024 15:41:52 +0700 Subject: [PATCH 213/300] fix: ASC-00000 - done button (#496) * fix: done button * fix: add todo comment for type * fix: submit form function * fix: remove console.log * fix: apply themeStyle * fix: type * fix: formId prop * fix: formId to be optional type * fix: done button type --- .../components/BottomSheet/BottomSheet.tsx | 1 + .../HyperLinkConfig/HyperLinkConfig.tsx | 36 ++++++++----------- .../social/elements/DoneButton/DoneButton.tsx | 17 ++++++--- 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/src/v4/core/components/BottomSheet/BottomSheet.tsx b/src/v4/core/components/BottomSheet/BottomSheet.tsx index 42e1b1316..6553d815e 100644 --- a/src/v4/core/components/BottomSheet/BottomSheet.tsx +++ b/src/v4/core/components/BottomSheet/BottomSheet.tsx @@ -14,6 +14,7 @@ interface BottomSheetProps { headerTitle?: string; cancelText?: string; okText?: string; + style: React.CSSProperties; className?: string; } diff --git a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx index f0687338f..c8c50f422 100644 --- a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx +++ b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx @@ -4,24 +4,23 @@ import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; import clsx from 'clsx'; - import { BottomSheet, Typography } from '~/v4/core/components'; - -import styles from './HyperLinkConfig.module.css'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; import { Button } from '~/v4/core/components/Button'; import useSDK from '~/v4/core/hooks/useSDK'; import Trash from '~/v4/social/icons/trash'; import { useAmityComponent } from '~/v4/core/hooks/uikit'; import { DoneButton } from '~/v4/social/elements/DoneButton'; -import { EditCancelButton } from '../../elements/EditCancelButton/EditCancelButton'; +import { EditCancelButton } from '~/v4/social/elements/EditCancelButton'; + +import styles from './HyperLinkConfig.module.css'; interface HyperLinkConfigProps { pageId: string; isHaveHyperLink: boolean; isOpen: boolean; onClose: () => void; - onSubmit: (data: any) => void; + onSubmit: (data: { url: string; customText?: string }) => void; onRemove: () => void; } @@ -37,11 +36,10 @@ export const HyperLinkConfig = ({ }: HyperLinkConfigProps) => { const componentId = 'hyper_link_config_component'; const { confirm } = useConfirmContext(); - const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = - useAmityComponent({ - pageId, - componentId, - }); + const { accessibilityId, config, isExcluded, themeStyles } = useAmityComponent({ + pageId, + componentId, + }); if (isExcluded) return null; @@ -49,6 +47,8 @@ export const HyperLinkConfig = ({ const { client } = useSDK(); const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); + const formId = 'asc-story-hyperlink-form'; + const schema = z.object({ url: z .string() @@ -116,7 +116,7 @@ export const HyperLinkConfig = ({ setHasUnsavedChanges(url !== '' || customText !== ''); }, [url, customText]); - const onSubmitForm = async (data: HyperLinkFormInputs) => { + const onSubmitForm = (data: HyperLinkFormInputs) => { onSubmit(data); onClose(); }; @@ -156,31 +156,25 @@ export const HyperLinkConfig = ({ return ( <BottomSheet + data-qa-anchor={accessibilityId} detent="full-height" mountPoint={document.getElementById('asc-uikit-create-story') as HTMLElement} rootId="asc-uikit-create-story" isOpen={isOpen} onClose={handleClose} className={styles.bottomSheet} + style={themeStyles} > <div className={styles.headerContainer}> <EditCancelButton pageId={pageId} componentId={componentId} onPress={handleClose} /> <Typography.Title> {formatMessage({ id: 'storyCreation.hyperlink.bottomSheet.title' })} </Typography.Title> - <DoneButton - pageId={pageId} - componentId={componentId} - onPress={() => handleSubmit(onSubmitForm)} - /> + <DoneButton pageId={pageId} componentId={componentId} formId={formId} /> </div> <div className={styles.divider} /> <div className={styles.hyperlinkFormContainer}> - <form - id="asc-story-hyperlink-form" - onSubmit={handleSubmit(onSubmitForm)} - className={styles.form} - > + <form onSubmit={handleSubmit(onSubmitForm)} id={formId} className={styles.form}> <div className={styles.inputContainer}> <Typography.Title> <label diff --git a/src/v4/social/elements/DoneButton/DoneButton.tsx b/src/v4/social/elements/DoneButton/DoneButton.tsx index d19ba9407..fb0b2009c 100644 --- a/src/v4/social/elements/DoneButton/DoneButton.tsx +++ b/src/v4/social/elements/DoneButton/DoneButton.tsx @@ -4,13 +4,18 @@ import { Typography } from '~/v4/core/components'; import { useAmityElement } from '~/v4/core/hooks/uikit'; import styles from './DoneButton.module.css'; -export interface DoneButtonProps { +export interface DoneButtonProps extends ButtonProps { pageId?: string; componentId?: string; - onPress?: ButtonProps['onPress']; + formId?: string; } -export function DoneButton({ pageId = '*', componentId = '*', onPress }: DoneButtonProps) { +export function DoneButton({ + pageId = '*', + componentId = '*', + className, + ...buttonProps +}: DoneButtonProps) { const elementId = 'done_button'; const { accessibilityId, config, isExcluded } = useAmityElement({ pageId, @@ -21,7 +26,11 @@ export function DoneButton({ pageId = '*', componentId = '*', onPress }: DoneBut if (isExcluded) return null; return ( - <Button className={styles.doneButton} data-qa-anchor={accessibilityId} onPress={onPress}> + <Button + className={`${styles.doneButton} ${className || ''}`} + data-qa-anchor={accessibilityId} + {...buttonProps} + > <Typography.Body className={styles.doneButton_text}> {config.done_button_text} </Typography.Body> From ee0e87cdc4b45e5bd7ba43c4f95fda7160eedc39 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Tue, 9 Jul 2024 15:47:47 +0700 Subject: [PATCH 214/300] fix: ASC-00000 - modal overlay (#497) * fix: done button * fix: add todo comment for type * fix: submit form function * fix: remove console.log * fix: apply themeStyle * fix: type * fix: modal overlay * fix: remove console.log --- src/v4/core/components/BottomSheet/BottomSheet.tsx | 2 +- src/v4/social/elements/DoneButton/DoneButton.tsx | 1 + .../internal-components/StoryViewer/Renderers/Image.tsx | 8 ++------ .../StoryViewer/Renderers/Renderers.module.css | 9 ++++++++- .../internal-components/StoryViewer/Renderers/Video.tsx | 9 +++------ src/v4/social/pages/StoryPage/CommunityFeedStory.tsx | 2 +- src/v4/social/pages/StoryPage/GlobalFeedStory.tsx | 2 +- 7 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/v4/core/components/BottomSheet/BottomSheet.tsx b/src/v4/core/components/BottomSheet/BottomSheet.tsx index 6553d815e..c836e57db 100644 --- a/src/v4/core/components/BottomSheet/BottomSheet.tsx +++ b/src/v4/core/components/BottomSheet/BottomSheet.tsx @@ -14,7 +14,7 @@ interface BottomSheetProps { headerTitle?: string; cancelText?: string; okText?: string; - style: React.CSSProperties; + style?: React.CSSProperties; className?: string; } diff --git a/src/v4/social/elements/DoneButton/DoneButton.tsx b/src/v4/social/elements/DoneButton/DoneButton.tsx index fb0b2009c..1d1070d5f 100644 --- a/src/v4/social/elements/DoneButton/DoneButton.tsx +++ b/src/v4/social/elements/DoneButton/DoneButton.tsx @@ -13,6 +13,7 @@ export interface DoneButtonProps extends ButtonProps { export function DoneButton({ pageId = '*', componentId = '*', + formId, className, ...buttonProps }: DoneButtonProps) { diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index bdc5f630c..7ab2a7187 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -12,12 +12,12 @@ import Footer from '~/v4/social/internal-components/StoryViewer/Renderers/Wrappe import Header from '~/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header'; import { BottomSheet } from '~/v4/core/components/BottomSheet'; import { Typography } from '~/v4/core/components'; -import { Button } from '~/v4/core/components/Button'; import useCommunityMembersCollection from '~/v4/social/hooks/collections/useCommunityMembersCollection'; import useSDK from '~/v4/core/hooks/useSDK'; import useUser from '~/v4/core/hooks/objects/useUser'; import { LIKE_REACTION_KEY } from '~/v4/social/constants/reactions'; import { checkStoryPermission, formatTimeAgo, isAdmin, isModerator } from '~/v4/social/utils'; +import { Button } from '~/v4/core/natives/Button'; import styles from './Renderers.module.css'; import clsx from 'clsx'; @@ -264,11 +264,7 @@ export const renderer: CustomRenderer = ({ story, action, config, onClose, onCli <Button key={bottomSheetAction.name} className={styles.actionButton} - onClick={() => { - bottomSheetAction.action(); - closeBottomSheet(); - }} - variant="secondary" + onPress={() => bottomSheetAction?.action()} > {bottomSheetAction?.icon && bottomSheetAction.icon} <Typography.BodyBold> diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css b/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css index d773dd648..64dc1feee 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css @@ -256,14 +256,21 @@ } .actionButton { - width: 100%; display: flex; justify-content: flex-start; + align-items: center; text-align: left; gap: var(--asc-spacing-s1); border: none; color: var(--asc-color-base-default); background-color: var(--asc-color-base-background); + padding: var(--asc-spacing-m1) var(--asc-spacing-m2); + cursor: pointer; + border-radius: var(--asc-border-radius-sm); +} + +.actionButton:hover { + background-color: var(--asc-color-base-shade4); } .navigationOverlay { diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx index b23a5e04d..9793d7a92 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx @@ -7,7 +7,8 @@ import { } from '~/v4/social/internal-components/StoryViewer/Renderers/types'; import { SpeakerButton } from '~/v4/social/elements'; -import { BottomSheet, Button, Typography } from '~/v4/core/components'; +import { BottomSheet, Typography } from '~/v4/core/components'; +import { Button } from '~/v4/core/natives/Button'; import { CommentTray } from '~/v4/social/components'; import { HyperLink } from '~/v4/social/elements/HyperLink'; @@ -261,11 +262,7 @@ export const renderer: CustomRenderer = ({ {actions?.map((bottomSheetAction) => ( <Button className={clsx(rendererStyles.actionButton)} - onClick={() => { - bottomSheetAction.action(); - closeBottomSheet(); - }} - variant="secondary" + onPress={() => bottomSheetAction?.action()} > {bottomSheetAction?.icon && bottomSheetAction.icon} <Typography.BodyBold>{bottomSheetAction.name}</Typography.BodyBold> diff --git a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx index 48244f8ae..c13558182 100644 --- a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx @@ -223,7 +223,7 @@ export const CommunityFeedStory = ({ actions: [ isStoryCreator || isModerator ? { - name: 'delete', + name: 'Delete', action: () => deleteStory(story?.storyId as string), icon: ( <Trash2Icon diff --git a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx index 73280b9da..9cb050167 100644 --- a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx @@ -228,7 +228,7 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ actions: [ isStoryCreator || isModerator ? { - name: 'delete', + name: 'Delete', action: () => deleteStory(story?.storyId as string), icon: <TrashIcon className={styles.deleteIcon} />, } From 4ddaba07c19f1109c2f5eaeef3afa5d01413c2e6 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Tue, 9 Jul 2024 17:34:28 +0700 Subject: [PATCH 215/300] fix: ad information drawer (#500) --- .../AdInformation.module.css | 80 +++++++++++++++++++ .../AdInformation.tsx/AdInformation.tsx | 54 +++++++++++++ .../CommentAd/CommentAd.module.css | 8 +- .../CommentAd/CommentAd.tsx | 14 +++- .../PostAd/PostAd.module.css | 8 +- .../internal-components/PostAd/PostAd.tsx | 14 +++- 6 files changed, 170 insertions(+), 8 deletions(-) create mode 100644 src/v4/social/internal-components/AdInformation.tsx/AdInformation.module.css create mode 100644 src/v4/social/internal-components/AdInformation.tsx/AdInformation.tsx diff --git a/src/v4/social/internal-components/AdInformation.tsx/AdInformation.module.css b/src/v4/social/internal-components/AdInformation.tsx/AdInformation.module.css new file mode 100644 index 000000000..cb7ca9ad7 --- /dev/null +++ b/src/v4/social/internal-components/AdInformation.tsx/AdInformation.module.css @@ -0,0 +1,80 @@ +.drawer__content { + padding: 1rem; + background-color: var(--asc-color-base-background); + max-height: 50%; + position: fixed; + bottom: 0; + left: 0; + right: 0; + display: flex; + flex-direction: column; + border-top-left-radius: 1.25rem; + border-top-right-radius: 1.25rem; + margin-top: 6rem; +} + +.drawer__content:focus { + outline: none; +} + +.drawer__innerContent { + background-color: var(--asc-color-base-background); + border-top-left-radius: 1.25rem; + border-top-right-radius: 1.25rem; + flex: 1; +} + +.drawer__placeholder { + margin: auto; + width: 3rem; + height: 0.375rem; + flex-shrink: 0; + border-radius: 9999px; + background-color: var(--asc-color-secondary-shade2); + margin-bottom: 2rem; +} + +.drawer__overlay { + background-color: color(from var(--asc-color-black) srgb r g b / 70%); + inset: 0; + position: fixed; +} + +.drawer__title { + padding: 0.75rem 1rem; + border-bottom: 0.0313rem solid var(--asc-color-base-shade3); +} + +.drawer__content__data { + padding: 1.5rem 1rem 0; + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.drawer__content__data__title { + color: var(--asc-color-base-default); +} + +.drawer__content__data__text { + display: flex; + padding: 0.5rem; + align-items: flex-start; + gap: 0.5rem; + background-color: var(--asc-color-base-shade4); + border-radius: 0.25rem; +} + +.drawer__content__data__caption { + color: var(--asc-color-base-shade1); +} + +.drawer__content__data__infoIcon { + height: 1rem; + width: 1rem; + fill: var(--asc-color-secondary-shade2); +} + +.drawer__content__emptySpace { + min-height: 15rem; +} diff --git a/src/v4/social/internal-components/AdInformation.tsx/AdInformation.tsx b/src/v4/social/internal-components/AdInformation.tsx/AdInformation.tsx new file mode 100644 index 000000000..af0799be7 --- /dev/null +++ b/src/v4/social/internal-components/AdInformation.tsx/AdInformation.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import { Typography } from '~/v4/core/components'; +import InfoCircle from '~/v4/icons/InfoCircle'; +import { Drawer } from 'vaul'; + +import styles from './AdInformation.module.css'; + +interface AdInformationProps { + isOpen: boolean; + onOpenChange: (open: boolean) => void; + ad: Amity.Ad; +} + +export const AdInformation = ({ isOpen, onOpenChange, ad }: AdInformationProps) => { + return ( + <Drawer.Root open={isOpen} onOpenChange={onOpenChange}> + <Drawer.Portal> + <Drawer.Overlay className={styles.drawer__overlay} /> + <Drawer.Content className={styles.drawer__content}> + <div className={styles.drawer__innerContent}> + <div className={styles.drawer__placeholder} /> + <Drawer.Title className={styles.drawer__title}> + <Typography.Title>About this advertisement</Typography.Title> + </Drawer.Title> + <div className={styles.drawer__content__data}> + <Typography.BodyBold className={styles.drawer__content__data__title}> + Why this advertisement? + </Typography.BodyBold> + <div className={styles.drawer__content__data__text}> + <InfoCircle className={styles.drawer__content__data__infoIcon} /> + <Typography.Caption className={styles.drawer__content__data__caption}> + You're seeing this advertisement because it was displayed to all users in the + system. + </Typography.Caption> + </div> + </div> + <div className={styles.drawer__content__data}> + <Typography.BodyBold className={styles.drawer__content__data__title}> + About this advertiser + </Typography.BodyBold> + <div className={styles.drawer__content__data__text}> + <InfoCircle className={styles.drawer__content__data__infoIcon} /> + <Typography.Caption className={styles.drawer__content__data__caption}> + Advertiser name: {ad.advertiser?.companyName} + </Typography.Caption> + </div> + </div> + <div className={styles.drawer__content__emptySpace}></div> + </div> + </Drawer.Content> + </Drawer.Portal> + </Drawer.Root> + ); +}; diff --git a/src/v4/social/internal-components/CommentAd/CommentAd.module.css b/src/v4/social/internal-components/CommentAd/CommentAd.module.css index 8ae4d0d3d..b52c626f4 100644 --- a/src/v4/social/internal-components/CommentAd/CommentAd.module.css +++ b/src/v4/social/internal-components/CommentAd/CommentAd.module.css @@ -101,11 +101,15 @@ text-overflow: ellipsis; } -.infoIcon { +.infoIcon__button { position: absolute; width: 1rem; height: 1rem; - fill: var(--asc-color-base-shade3); right: 0.25rem; top: 0.25rem; + cursor: pointer; +} + +.infoIcon { + fill: var(--asc-color-base-shade3); } diff --git a/src/v4/social/internal-components/CommentAd/CommentAd.tsx b/src/v4/social/internal-components/CommentAd/CommentAd.tsx index 8a2aa1919..47af67e36 100644 --- a/src/v4/social/internal-components/CommentAd/CommentAd.tsx +++ b/src/v4/social/internal-components/CommentAd/CommentAd.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import styles from './CommentAd.module.css'; import { Avatar, Typography } from '~/v4/core/components'; import { AdsBadge } from '../AdsBadge/AdsBadge'; @@ -7,6 +7,7 @@ import InfoCircle from '~/v4/icons/InfoCircle'; import { useAmityComponent } from '~/v4/core/hooks/uikit/index'; import { Button } from '~/v4/core/natives/Button'; import useImage from '~/v4/core/hooks/useImage'; +import { AdInformation } from '../AdInformation.tsx/AdInformation'; interface CommentAdProps { pageId?: string; @@ -20,6 +21,8 @@ export const CommentAd = ({ pageId = '*', ad }: CommentAdProps) => { componentId, }); + const [isAdvertisementInfoOpen, setIsAdvertisementInfoOpen] = useState(false); + const avatarFile = useImage({ fileId: ad.advertiser?.avatar?.fileId }); const avatarUrl = avatarFile || ad.advertiser?.avatar?.fileUrl || ''; @@ -32,7 +35,9 @@ export const CommentAd = ({ pageId = '*', ad }: CommentAdProps) => { return ( <div className={styles.commentAd} style={themeStyles}> - <InfoCircle className={styles.infoIcon} /> + <Button className={styles.infoIcon__button} onPress={() => setIsAdvertisementInfoOpen(true)}> + <InfoCircle className={styles.infoIcon} /> + </Button> <div className={styles.commentAd__container}> <div className={styles.commentAd__avatar}> <Avatar avatarUrl={avatarUrl} defaultImage={<Broadcast />} /> @@ -79,6 +84,11 @@ export const CommentAd = ({ pageId = '*', ad }: CommentAdProps) => { </div> </div> </div> + <AdInformation + ad={ad} + isOpen={isAdvertisementInfoOpen} + onOpenChange={(open) => setIsAdvertisementInfoOpen(open)} + /> </div> ); }; diff --git a/src/v4/social/internal-components/PostAd/PostAd.module.css b/src/v4/social/internal-components/PostAd/PostAd.module.css index f0842564a..c8276b5f7 100644 --- a/src/v4/social/internal-components/PostAd/PostAd.module.css +++ b/src/v4/social/internal-components/PostAd/PostAd.module.css @@ -84,11 +84,15 @@ background-color: var(--asc-color-primary-50); } -.infoIcon { +.infoIcon__button { position: absolute; width: 1rem; height: 1rem; - fill: var(--asc-color-base-shade3); right: 0.25rem; top: 0.25rem; + cursor: pointer; +} + +.infoIcon { + fill: var(--asc-color-base-shade3); } diff --git a/src/v4/social/internal-components/PostAd/PostAd.tsx b/src/v4/social/internal-components/PostAd/PostAd.tsx index 663fc6451..633586683 100644 --- a/src/v4/social/internal-components/PostAd/PostAd.tsx +++ b/src/v4/social/internal-components/PostAd/PostAd.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react'; +import React, { useState } from 'react'; import styles from './PostAd.module.css'; import { Avatar, Typography } from '~/v4/core/components'; import { AdsBadge } from '../AdsBadge/AdsBadge'; @@ -7,6 +7,7 @@ import InfoCircle from '~/v4/icons/InfoCircle'; import { useAmityComponent } from '~/v4/core/hooks/uikit/index'; import { Button } from '~/v4/core/natives/Button'; import useImage from '~/v4/core/hooks/useImage'; +import { AdInformation } from '../AdInformation.tsx/AdInformation'; interface PostAdProps { pageId?: string; @@ -20,6 +21,8 @@ export const PostAd = ({ pageId = '*', ad }: PostAdProps) => { componentId, }); + const [isAdvertisementInfoOpen, setIsAdvertisementInfoOpen] = useState(false); + const avatarFile = useImage({ fileId: ad.advertiser?.avatar?.fileId }); const avatarUrl = avatarFile || ad.advertiser?.avatar?.fileUrl || ''; @@ -46,7 +49,9 @@ export const PostAd = ({ pageId = '*', ad }: PostAdProps) => { </div> </div> </div> - <InfoCircle className={styles.infoIcon} /> + <Button className={styles.infoIcon__button} onPress={() => setIsAdvertisementInfoOpen(true)}> + <InfoCircle className={styles.infoIcon} /> + </Button> <div className={styles.footer} @@ -68,6 +73,11 @@ export const PostAd = ({ pageId = '*', ad }: PostAdProps) => { </Button> ) : null} </div> + <AdInformation + ad={ad} + isOpen={isAdvertisementInfoOpen} + onOpenChange={(open) => setIsAdvertisementInfoOpen(open)} + /> </div> ); }; From 6e2cba8203a5f95a476c6fde7d5c01b0ab59d01e Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Tue, 9 Jul 2024 17:34:47 +0700 Subject: [PATCH 216/300] fix: ASC-24074 - comment ad style (#504) * fix: comment ad style * feat: change sponsor badge text --- .../internal-components/AdsBadge/AdsBadge.tsx | 2 +- .../CommentAd/CommentAd.module.css | 16 +++++++++++++++- .../internal-components/CommentAd/CommentAd.tsx | 4 ++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/v4/social/internal-components/AdsBadge/AdsBadge.tsx b/src/v4/social/internal-components/AdsBadge/AdsBadge.tsx index 01f81594b..e104a8c2d 100644 --- a/src/v4/social/internal-components/AdsBadge/AdsBadge.tsx +++ b/src/v4/social/internal-components/AdsBadge/AdsBadge.tsx @@ -7,7 +7,7 @@ export const AdsBadge = () => { <div className={styles.badge}> <div className={styles.badge__child}> <Star className={styles.badge__icon} /> - <div className={styles.badge__text}>Premium Sponser</div> + <div className={styles.badge__text}>Sponsored</div> </div> </div> ); diff --git a/src/v4/social/internal-components/CommentAd/CommentAd.module.css b/src/v4/social/internal-components/CommentAd/CommentAd.module.css index b52c626f4..6ece0abc3 100644 --- a/src/v4/social/internal-components/CommentAd/CommentAd.module.css +++ b/src/v4/social/internal-components/CommentAd/CommentAd.module.css @@ -49,6 +49,7 @@ border: 1px solid var(--asc-color-base-shade4); background-color: var(--asc-color-base-background); height: 100%; + width: 100%; cursor: pointer; } @@ -78,12 +79,24 @@ overflow: hidden; } -.commentAd__adCard__caption { +.commentAd__adCard__textContainer { + width: 100%; +} + +.commentAd__adCard__headline { color: var(--asc-color-base-shade1); + width: 100%; + word-wrap: break-word; + overflow: hidden; + text-overflow: ellipsis; } .commentAd__adCard__description { color: var(--asc-color-base-default); + width: 100%; + word-wrap: break-word; + overflow: hidden; + text-overflow: ellipsis; } .commentAd__adCard__button { @@ -92,6 +105,7 @@ padding: 0.375rem 0.75rem; border-radius: var(--asc-border-radius-md); box-sizing: border-box; + max-width: 100%; } .commentAd__adCard__button__text { diff --git a/src/v4/social/internal-components/CommentAd/CommentAd.tsx b/src/v4/social/internal-components/CommentAd/CommentAd.tsx index 47af67e36..10e09b608 100644 --- a/src/v4/social/internal-components/CommentAd/CommentAd.tsx +++ b/src/v4/social/internal-components/CommentAd/CommentAd.tsx @@ -61,8 +61,8 @@ export const CommentAd = ({ pageId = '*', ad }: CommentAdProps) => { <img className={styles.commentAd__adCard__image} src={adImageUrl} /> </div> <div className={styles.commentAd__adCard__detail}> - <div> - <Typography.Caption className={styles.commentAd__adCard__caption}> + <div className={styles.commentAd__adCard__textContainer}> + <Typography.Caption className={styles.commentAd__adCard__headline}> {ad.headline} </Typography.Caption> <Typography.BodyBold className={styles.commentAd__adCard__description}> From 271fc728cc0f4688e18aeaf4a1c21d16571423f9 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Tue, 9 Jul 2024 17:36:31 +0700 Subject: [PATCH 217/300] fix: ASC-00000 - theme (#501) * fix: configuration background not change in livechat compose bar * fix: cannot use theme configuration * fix: theme logic * feat: update a customization key ordering * fix: update css variable names to align with a design * css var * fix: theme --------- Co-authored-by: ptchaya_p <pitchaya@amity.co> --- .../styles.module.css | 2 +- .../MessageAction/styles.module.css | 4 +- .../LiveChatMessageContent/styles.module.css | 2 +- .../ChatContainer/styles.module.css | 10 +- .../pages/AmityLiveChatPage/styles.module.css | 4 +- .../BottomSheet/BottomSheet.module.css | 6 +- .../components/ConfirmModal/styles.module.css | 4 +- .../core/components/Drawer/Drawer.module.css | 6 +- .../components/InputText/styles.module.css | 3 +- .../LoadMoreWrapper.module.css | 2 +- .../core/components/Modal/styles.module.css | 2 +- .../SocialMentionItem/styles.module.css | 2 +- .../core/providers/CustomizationProvider.tsx | 206 +++++++++--------- src/v4/core/providers/ThemeProvider.tsx | 133 ++++------- .../CommunityItemSkeleton.module.css | 2 +- .../CommunitySearchResult.module.css | 2 +- .../CreatePostMenu/CreatePostMenu.module.css | 2 +- .../DetailedMediaAttachment.module.css | 2 +- .../EmptyNewsFeed/EmptyNewsFeed.module.css | 2 +- .../HyperLinkConfig.module.css | 16 +- .../MediaAttachment.module.css | 2 +- .../MyCommunities/MyCommunities.module.css | 2 +- .../components/Newsfeed/Newsfeed.module.css | 2 +- .../PostContent/PostContent.module.css | 2 +- .../PostContentSkeleton.module.css | 2 +- .../ReactionList/ReactionList.module.css | 4 +- .../StoryTab/StoryTabCommunity.module.css | 2 +- .../StoryTab/StoryTabGlobalFeed.module.css | 2 +- .../TopNavigation/TopNavigation.module.css | 2 +- .../TopSearchBar/TopSearchBar.module.css | 2 +- .../UserSearchItemSkeleton.module.css | 2 +- .../UserSearchResult.module.css | 2 +- .../CancelButton/CancelButton.module.css | 2 +- .../CreateStoryButtons/CreateStoryButton.tsx | 4 +- .../EditCancelButton.module.css | 2 +- .../PostTextField/PostTextField.module.css | 2 +- .../ReactionButton/ReactionButton.module.css | 2 +- .../elements/StoryRing/StoryRing.module.css | 2 +- .../social/elements/StoryRing/StoryRing.tsx | 4 +- .../Comment/Comment.module.css | 4 +- .../Comment/UIComment.module.css | 2 +- .../CommentComposeBar.module.css | 2 +- .../ImageViewer/ImageViewer.module.css | 2 +- .../PostMenu/PostMenu.module.css | 4 +- .../Renderers/Renderers.module.css | 2 +- .../Wrappers/Footer/Footer.module.css | 2 +- .../TabButton/TabButton.module.css | 2 +- .../TabsBar/TabsBar.module.css | 2 +- .../VideoViewer/VideoViewer.module.css | 2 +- .../MyCommunitiesSearchPage.module.css | 2 +- .../PostComposerPage.module.css | 6 +- .../PostDetailPage/PostDetailPage.module.css | 4 +- .../SelectPostTargetPage.module.css | 4 +- .../SocialGlobalSearchPage.module.css | 2 +- .../SocialHomePage/SocialHomePage.module.css | 4 +- src/v4/styles/global.css | 38 +++- 56 files changed, 253 insertions(+), 287 deletions(-) diff --git a/src/v4/chat/components/AmityLiveChatMessageComposeBar/styles.module.css b/src/v4/chat/components/AmityLiveChatMessageComposeBar/styles.module.css index 61c896bb0..5c24547a1 100644 --- a/src/v4/chat/components/AmityLiveChatMessageComposeBar/styles.module.css +++ b/src/v4/chat/components/AmityLiveChatMessageComposeBar/styles.module.css @@ -25,7 +25,7 @@ .composeBarContainer { min-height: 3.5rem; z-index: 99; - background-color: inherit; + background-color: var(--asc-color-background-default); } .composeBar { diff --git a/src/v4/chat/components/LiveChatMessageContent/MessageAction/styles.module.css b/src/v4/chat/components/LiveChatMessageContent/MessageAction/styles.module.css index 01331658a..54c62dda7 100644 --- a/src/v4/chat/components/LiveChatMessageContent/MessageAction/styles.module.css +++ b/src/v4/chat/components/LiveChatMessageContent/MessageAction/styles.module.css @@ -39,7 +39,7 @@ } .messageDangerActionButtonText { - color: var(--asc-color-alert); + color: var(--asc-color-alert-default); } .copyIcon { @@ -53,7 +53,7 @@ .binIcon { width: 1.25rem; height: 1.25rem; - fill: var(--asc-color-alert); + fill: var(--asc-color-alert-default); } .mentionIcon { diff --git a/src/v4/chat/components/LiveChatMessageContent/styles.module.css b/src/v4/chat/components/LiveChatMessageContent/styles.module.css index 0cf5143fd..1d97abb0a 100644 --- a/src/v4/chat/components/LiveChatMessageContent/styles.module.css +++ b/src/v4/chat/components/LiveChatMessageContent/styles.module.css @@ -77,7 +77,7 @@ .reactionListContainer { height: 100% !important; - background-color: var(--asc-color-base-background) !important; + background-color: var(--asc-color-background-default) !important; } .reactionListBackdrop { diff --git a/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/styles.module.css b/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/styles.module.css index d0692b235..d212f1b30 100644 --- a/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/styles.module.css +++ b/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/styles.module.css @@ -1,7 +1,7 @@ .composeBarContainer { min-height: 3.5rem; z-index: 99; - background-color: inherit; + background-color: var(--asc-color-background-default); } .composeBar { @@ -16,11 +16,12 @@ .textInputContainer { display: flex; flex-grow: 1; + background: var(--asc-color-background-default); & > div { width: 100%; border-radius: var(--asc-border-radius-xxl); - border: 1px solid var(--asc-color-base-background); + border: 1px solid var(--asc-color-background-default); background: var(--asc-color-secondary-shade4); textarea { @@ -36,10 +37,7 @@ padding: var(--asc-spacing-s1) var(--asc-spacing-s2); border-radius: var(--asc-border-radius-sm); border: var(--asc-border-radius-none); - background-color: var( - --live-chat-asc-color-secondary-default, - var(--asc-color-secondary-default) - ); + background-color: var(--asc-color-secondary-default); color: var(--asc-color-base-inverse); display: flex; align-items: center; diff --git a/src/v4/chat/pages/AmityLiveChatPage/styles.module.css b/src/v4/chat/pages/AmityLiveChatPage/styles.module.css index 0bb535a12..176a8d68b 100644 --- a/src/v4/chat/pages/AmityLiveChatPage/styles.module.css +++ b/src/v4/chat/pages/AmityLiveChatPage/styles.module.css @@ -7,7 +7,7 @@ .messageListSheetContainer { height: 100% !important; - background-color: var(--asc-color-base-background) !important; + background-color: var(--asc-color-background-default) !important; } } @@ -18,7 +18,7 @@ .amtiyLivechatPage { display: flex; flex-direction: column; - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); height: 100%; overflow-y: hidden; } diff --git a/src/v4/core/components/BottomSheet/BottomSheet.module.css b/src/v4/core/components/BottomSheet/BottomSheet.module.css index 18f070685..91eb82254 100644 --- a/src/v4/core/components/BottomSheet/BottomSheet.module.css +++ b/src/v4/core/components/BottomSheet/BottomSheet.module.css @@ -5,11 +5,11 @@ which have higher specificity. */ .bottomSheet__container { - background-color: var(--asc-color-base-background) !important; + background-color: var(--asc-color-background-default) !important; } .bottomSheet__header { - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); border-top-left-radius: var(--asc-border-radius-xl); border-top-right-radius: var(--asc-border-radius-xl); text-align: center; @@ -19,7 +19,7 @@ which have higher specificity. } .bottomSheet__content { - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); padding: var(--asc-spacing-m1); } diff --git a/src/v4/core/components/ConfirmModal/styles.module.css b/src/v4/core/components/ConfirmModal/styles.module.css index d3da69b7e..2ae88cc25 100644 --- a/src/v4/core/components/ConfirmModal/styles.module.css +++ b/src/v4/core/components/ConfirmModal/styles.module.css @@ -3,7 +3,7 @@ } .background { - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); } .footer { @@ -19,7 +19,7 @@ justify-content: center; align-items: center; border-radius: var(--asc-border-radius-sm); - background: var(--asc-color-alert); + background: var(--asc-color-alert-default); border: none; cursor: pointer; } diff --git a/src/v4/core/components/Drawer/Drawer.module.css b/src/v4/core/components/Drawer/Drawer.module.css index ad4a3fd73..ce963b8e1 100644 --- a/src/v4/core/components/Drawer/Drawer.module.css +++ b/src/v4/core/components/Drawer/Drawer.module.css @@ -1,6 +1,6 @@ .drawer__content { padding: 1rem; - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); max-height: 50%; position: fixed; bottom: 0; @@ -18,7 +18,7 @@ } .drawer__innerContent { - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); border-top-left-radius: 0.625rem; border-top-right-radius: 0.625rem; flex: 1; @@ -35,7 +35,7 @@ } .drawer__overlay { - background-color: color(from var(--asc-color-base-background) srgb r g b / 50%); + background-color: color(from var(--asc-color-background-default) srgb r g b / 50%); inset: 0; position: fixed; } diff --git a/src/v4/core/components/InputText/styles.module.css b/src/v4/core/components/InputText/styles.module.css index ba0a542d6..883ab69ec 100644 --- a/src/v4/core/components/InputText/styles.module.css +++ b/src/v4/core/components/InputText/styles.module.css @@ -9,7 +9,7 @@ transition: background 0.2s, border-color 0.2s; &.invalid { - border-color: var(--asc-color-alert); + border-color: var(--asc-color-alert-default); } &.disabled { @@ -61,6 +61,7 @@ .live-chat-mention-input { padding: var(--asc-spacing-s1); width: calc(100% - 1rem); + background-color: var(--asc-color-background-default); > div:first-child { > div:first-child { diff --git a/src/v4/core/components/LoadMoreWrapper/LoadMoreWrapper.module.css b/src/v4/core/components/LoadMoreWrapper/LoadMoreWrapper.module.css index 14fdd88c0..47aaf2013 100644 --- a/src/v4/core/components/LoadMoreWrapper/LoadMoreWrapper.module.css +++ b/src/v4/core/components/LoadMoreWrapper/LoadMoreWrapper.module.css @@ -1,7 +1,7 @@ .loadMoreButton { display: inline-flex; justify-items: center; - background: var(--asc-color-base-background); + background: var(--asc-color-background-default); color: var(--asc-color-base-shade2); border: 1px solid var(--asc-color-base-shade4); padding: 0.3125rem 0.75rem 0.3125rem 0.5rem; diff --git a/src/v4/core/components/Modal/styles.module.css b/src/v4/core/components/Modal/styles.module.css index b4cc637a3..4dc218ae8 100644 --- a/src/v4/core/components/Modal/styles.module.css +++ b/src/v4/core/components/Modal/styles.module.css @@ -41,7 +41,7 @@ max-width: 32.5rem; min-width: 20rem; padding: 1.5rem; - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); } .modalWindow:focus { diff --git a/src/v4/core/components/SocialMentionItem/styles.module.css b/src/v4/core/components/SocialMentionItem/styles.module.css index 01dcbaa6d..825a51a9e 100644 --- a/src/v4/core/components/SocialMentionItem/styles.module.css +++ b/src/v4/core/components/SocialMentionItem/styles.module.css @@ -12,7 +12,7 @@ } .mentionItem:hover { - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); } .userDisplayName { diff --git a/src/v4/core/providers/CustomizationProvider.tsx b/src/v4/core/providers/CustomizationProvider.tsx index 77fa6a208..543545ff6 100644 --- a/src/v4/core/providers/CustomizationProvider.tsx +++ b/src/v4/core/providers/CustomizationProvider.tsx @@ -1,5 +1,34 @@ import React, { createContext, useContext, useState, useEffect } from 'react'; import { AmityReactionType } from './CustomReactionProvider'; +import { useTheme } from './ThemeProvider'; + +export const getCustomizationKeys = ({ + page, + component, + element, +}: { + page: string; + component: string; + element: string; +}) => { + if (element !== '*') { + return [ + `${page}/${component}/${element}`, + `*/${component}/${element}`, + `${page}/*/${element}`, + `*/*/${element}`, + `${page}/${component}/*`, + `*/${component}/*`, + `${page}/*/*`, + ]; + } else if (component !== '*') { + return [`${page}/${component}/*`, `${page}/*/*`, `*/${component}/*`]; + } else if (page !== '*') { + return [`${page}/*/*`]; + } + + return []; +}; export type GetConfigReturnValue = IconConfiguration & TextConfiguration & @@ -15,38 +44,34 @@ interface CustomizationContextValue { ) => IconConfiguration & TextConfiguration & ThemeConfiguration & CustomConfiguration; } +type ThemeValue = { + primary_color: string; + secondary_color: string; + secondary_shade1_color: string; + secondary_shade2_color: string; + secondary_shade3_color: string; + secondary_shade4_color: string; + base_color: string; + base_shade1_color: string; + base_shade2_color: string; + base_shade3_color: string; + base_shade4_color: string; + base_shade5_color: string; + alert_color: string; + background_color: string; + base_inverse_color: string; +}; + export type Theme = { - light: { - primary_color: string; - secondary_color: string; - base_color: string; - base_shade1_color: string; - base_shade2_color: string; - base_shade3_color: string; - base_shade4_color: string; - alert_color: string; - background_color: string; - base_inverse_color: string; - }; - dark: { - primary_color: string; - secondary_color: string; - base_color: string; - base_shade1_color: string; - base_shade2_color: string; - base_shade3_color: string; - base_shade4_color: string; - alert_color: string; - background_color: string; - base_inverse_color: string; - }; + light: ThemeValue; + dark: ThemeValue; }; type ThemeConfiguration = { preferred_theme?: 'light' | 'dark' | 'default'; theme?: { - light?: Partial<Pick<Theme['light'], 'primary_color' | 'secondary_color'>>; - dark?: Partial<Pick<Theme['dark'], 'primary_color' | 'secondary_color'>>; + light?: Partial<Theme['light']>; + dark?: Partial<Theme['dark']>; }; }; @@ -110,25 +135,35 @@ export const defaultConfig: DefaultConfig = { preferred_theme: 'default', theme: { light: { - primary_color: '#1054DE', - secondary_color: '#292B32', + primary_color: '#36486c', + secondary_color: '#292b32', + secondary_shade1_color: '#636878', + secondary_shade2_color: '#898e9e', + secondary_shade3_color: '#a5a9b5', + secondary_shade4_color: '#ebecef', base_color: '#292b32', base_shade1_color: '#636878', base_shade2_color: '#898e9e', base_shade3_color: '#a5a9b5', base_shade4_color: '#ebecef', + base_shade5_color: '#F9F9FA', alert_color: '#FA4D30', background_color: '#FFFFFF', base_inverse_color: '#000000', }, dark: { primary_color: '#1054DE', - secondary_color: '#292B32', + secondary_color: '#ebecef', + secondary_shade1_color: '#a5a9b5', + secondary_shade2_color: '#898e9e', + secondary_shade3_color: '#40434e', + secondary_shade4_color: '#292b32', base_color: '#ebecef', base_shade1_color: '#a5a9b5', base_shade2_color: '#6e7487', base_shade3_color: '#40434e', base_shade4_color: '#292b32', + base_shade5_color: '#f9f9fa', alert_color: '#FA4D30', background_color: '#191919', base_inverse_color: '#FFFFFF', @@ -455,26 +490,7 @@ export const defaultConfig: DefaultConfig = { export const getDefaultConfig: CustomizationContextValue['getConfig'] = (path: string) => { const [page, component, element] = path.split('/'); - const customizationKeys = (() => { - if (element !== '*') { - return [ - `${page}/${component}/${element}`, - `${page}/*/${element}`, - `${page}/${component}/*`, - `${page}/*/*`, - `*/${component}/${element}`, - `*/*/${element}`, - `*/${component}/*`, - `*/*/*`, - ]; - } else if (component !== '*') { - return [`${page}/${component}/*`, `${page}/*/*`, `*/${component}/*`, `*/*/*`]; - } else if (page !== '*') { - return [`${page}/*/*`, `*/*/*`]; - } - - return []; - })(); + const customizationKeys = getCustomizationKeys({ page, component, element }); return new Proxy< IconConfiguration & TextConfiguration & { theme?: Partial<Theme> } & CustomConfiguration @@ -498,6 +514,8 @@ export const CustomizationProvider: React.FC<CustomizationProviderProps> = ({ }) => { const [config, setConfig] = useState<Config | null>(null); + const currentTheme = useTheme(); + useEffect(() => { if (validateConfig(initialConfig)) { parseConfig(initialConfig); @@ -517,26 +535,7 @@ export const CustomizationProvider: React.FC<CustomizationProviderProps> = ({ const isExcluded = (path: string) => { const [page, component, element] = path.split('/'); - const customizationKeys = (() => { - if (element !== '*') { - return [ - `${page}/${component}/${element}`, - `${page}/*/${element}`, - `${page}/${component}/*`, - `${page}/*/*`, - `*/${component}/${element}`, - `*/*/${element}`, - `*/${component}/*`, - `*/*/*`, - ]; - } else if (component !== '*') { - return [`${page}/${component}/*`, `${page}/*/*`, `*/${component}/*`, `*/*/*`]; - } else if (page !== '*') { - return [`${page}/*/*`, `*/*/*`]; - } - - return []; - })(); + const customizationKeys = getCustomizationKeys({ page, component, element }); return ( config?.excludes?.some((excludedPath) => { @@ -548,26 +547,33 @@ export const CustomizationProvider: React.FC<CustomizationProviderProps> = ({ const getConfig: CustomizationContextValue['getConfig'] = (path: string) => { const [page, component, element] = path.split('/'); - const customizationKeys = (() => { - if (element !== '*') { - return [ - `${page}/${component}/${element}`, - `${page}/*/${element}`, - `${page}/${component}/*`, - `${page}/*/*`, - `*/${component}/${element}`, - `*/*/${element}`, - `*/${component}/*`, - `*/*/*`, - ]; - } else if (component !== '*') { - return [`${page}/${component}/*`, `${page}/*/*`, `*/${component}/*`, `*/*/*`]; - } else if (page !== '*') { - return [`${page}/*/*`, `*/*/*`]; - } - - return []; - })(); + const customizationKeys = getCustomizationKeys({ page, component, element }); + + const buildThemeProxyHandler = ( + themeName: 'light' | 'dark', + ): ProxyHandler< + IconConfiguration & TextConfiguration & { theme?: Partial<Theme> } & CustomConfiguration + > => ({ + get(_, prop: keyof ThemeValue) { + for (const key of customizationKeys) { + if (config?.customizations?.[key]?.theme?.[themeName]?.[prop]) { + return config.customizations[key].theme?.[themeName]?.[prop]; + } + } + + if (config?.theme?.[themeName]?.[prop]) { + return config.theme[themeName]?.[prop]; + } + + for (const key of customizationKeys) { + if (defaultConfig.customizations?.[key]?.theme?.[themeName]?.[prop]) { + return defaultConfig.customizations[key].theme?.[themeName]?.[prop]; + } + } + + return defaultConfig.theme[themeName][prop]; + }, + }); return new Proxy< IconConfiguration & TextConfiguration & { theme?: Partial<Theme> } & CustomConfiguration @@ -575,28 +581,28 @@ export const CustomizationProvider: React.FC<CustomizationProviderProps> = ({ {}, { get(target, prop: string) { + if (prop === 'theme') { + return { + light: new Proxy({}, buildThemeProxyHandler('light')), + dark: new Proxy({}, buildThemeProxyHandler('dark')), + }; + } + + if (prop === 'preferred_theme') { + return config?.preferred_theme ?? defaultConfig.preferred_theme; + } + for (const key of customizationKeys) { if (config?.customizations?.[key]?.[prop]) { return config.customizations[key][prop]; } } - if (prop === 'theme' && !!config?.theme) { - return config.theme; - } - for (const key of customizationKeys) { if (defaultConfig?.customizations?.[key]?.[prop]) { return defaultConfig.customizations[key][prop]; } } - - if (prop === 'theme') { - return defaultConfig.theme; - } - if (prop === 'preferred_theme') { - return defaultConfig.preferred_theme; - } }, }, ); diff --git a/src/v4/core/providers/ThemeProvider.tsx b/src/v4/core/providers/ThemeProvider.tsx index 80f568bdb..dbc25382e 100644 --- a/src/v4/core/providers/ThemeProvider.tsx +++ b/src/v4/core/providers/ThemeProvider.tsx @@ -15,14 +15,6 @@ const generateShades = (hexColor?: string, isDarkMode = false): string[] => { return ['#4a82f2', '#a0bdf8', '#d9e5fc', '#ffffff']; } - if (isDarkMode === true && hexColor === defaultConfig.theme.dark.secondary_color) { - return ['#a5a9b5', '#6e7487', '#40434e', '#292b32']; - } - - if (isDarkMode === false && hexColor === defaultConfig.theme.light.secondary_color) { - return ['#636878', '#898e9e', '#a5a9b5', '#ebecef']; - } - const hslColor = parseToHsl(hexColor); const shades = SHADE_PERCENTAGES.map((percentage) => { @@ -36,102 +28,53 @@ const generateShades = (hexColor?: string, isDarkMode = false): string[] => { return shades; }; -export function useGenerateStylesShadeColors(inputConfig?: GetConfigReturnValue) { +export function useGenerateStylesShadeColors(config: GetConfigReturnValue) { const currentTheme = useTheme(); - const inputThemeConfig = inputConfig?.theme; - const preferredTheme = useMemo(() => { - if (inputConfig?.preferred_theme && inputConfig?.preferred_theme !== 'default') { - return inputConfig.preferred_theme; + if (config?.preferred_theme && config?.preferred_theme !== 'default') { + return config.preferred_theme; } return 'default'; - }, [inputConfig?.preferred_theme, currentTheme]); - - const generatedLightColors = (() => { - if (inputThemeConfig?.light) { - const lightThemeConfig = inputThemeConfig?.light || defaultConfig.theme.light; - - const lightPrimary = generateShades(lightThemeConfig.primary_color); - const lightSecondary = generateShades(lightThemeConfig.secondary_color); - return { - '--asc-color-primary-default': lightThemeConfig.primary_color, - '--asc-color-primary-shade1': lightPrimary[0], - '--asc-color-primary-shade2': lightPrimary[1], - '--asc-color-primary-shade3': lightPrimary[2], - '--asc-color-primary-shade4': lightPrimary[3], - - '--asc-color-secondary-default': lightThemeConfig.secondary_color, - '--asc-color-secondary-shade1': lightSecondary[0], - '--asc-color-secondary-shade2': lightSecondary[1], - '--asc-color-secondary-shade3': lightSecondary[2], - '--asc-color-secondary-shade4': lightSecondary[3], - '--asc-color-secondary-shade5': '#f9f9fa', - - '--asc-color-alert': '#fa4d30', - '--asc-color-black': '#000000', - '--asc-color-white': '#ffffff', - - '--asc-color-base-inverse': '#000000', - - '--asc-color-base-default': '#292b32', - '--asc-color-base-shade1': '#636878', - '--asc-color-base-shade2': '#898e9e', - '--asc-color-base-shade3': '#a5a9b5', - '--asc-color-base-shade4': '#ebecef', - '--asc-color-base-shade5': '#f9f9fa', - - '--asc-color-base-background': defaultConfig.theme.light.background_color, - }; - } - - return {}; - })(); - - const generatedDarkColors = (() => { - if (inputThemeConfig?.dark) { - const darkThemeConfig = inputThemeConfig?.dark || defaultConfig.theme.dark; - const darkPrimary = generateShades(darkThemeConfig.primary_color, true); - const darkSecondary = generateShades(darkThemeConfig.secondary_color, true); - return { - '--asc-color-primary-default': darkThemeConfig.primary_color, - '--asc-color-primary-shade1': darkPrimary[0], - '--asc-color-primary-shade2': darkPrimary[1], - '--asc-color-primary-shade3': darkPrimary[2], - '--asc-color-primary-shade4': darkPrimary[3], - - '--asc-color-secondary-default': darkThemeConfig.secondary_color, - '--asc-color-secondary-shade1': darkSecondary[0], - '--asc-color-secondary-shade2': darkSecondary[1], - '--asc-color-secondary-shade3': darkSecondary[2], - '--asc-color-secondary-shade4': darkSecondary[3], - '--asc-color-secondary-shade5': '#f9f9fa', - - '--asc-color-alert': '#fa4d30', - '--asc-color-black': '#000000', - '--asc-color-white': '#ffffff', - - '--asc-color-base-inverse': '#ffffff', - - '--asc-color-base-default': '#ebecef', - '--asc-color-base-shade1': '#a5a9b5', - '--asc-color-base-shade2': '#6e7487', - '--asc-color-base-shade3': '#40434e', - '--asc-color-base-shade4': '#292b32', - '--asc-color-base-shade5': '#f9f9fa', - - '--asc-color-base-background': defaultConfig.theme.dark.background_color, - }; - } - return {}; - })(); + }, [config?.preferred_theme, currentTheme]); const computedTheme = preferredTheme === 'default' ? currentTheme : preferredTheme; - return { - ...(computedTheme === 'light' ? generatedLightColors : generatedDarkColors), - } as React.CSSProperties; + const generatedColors = useMemo(() => { + const themeConfig = config?.theme?.[computedTheme] || {}; + + const primary = generateShades(themeConfig.primary_color); + + return { + '--asc-color-primary-default': themeConfig.primary_color, + '--asc-color-primary-shade1': primary[0], + '--asc-color-primary-shade2': primary[1], + '--asc-color-primary-shade3': primary[2], + '--asc-color-primary-shade4': primary[3], + + '--asc-color-secondary-default': themeConfig.secondary_color, + '--asc-color-secondary-shade1': themeConfig.secondary_shade1_color, + '--asc-color-secondary-shade2': themeConfig.secondary_shade2_color, + '--asc-color-secondary-shade3': themeConfig.secondary_shade3_color, + '--asc-color-secondary-shade4': themeConfig.secondary_shade4_color, + + '--asc-color-alert-default': themeConfig.alert_color, + + '--asc-color-base-inverse': themeConfig.base_inverse_color, + + '--asc-color-base-default': themeConfig.base_color, + '--asc-color-base-shade1': themeConfig.base_shade1_color, + '--asc-color-base-shade2': themeConfig.base_shade2_color, + '--asc-color-base-shade3': themeConfig.base_shade3_color, + '--asc-color-base-shade4': themeConfig.base_shade4_color, + '--asc-color-base-shade5': themeConfig.base_shade5_color, + + '--asc-color-background-default': themeConfig.background_color, + }; + }, [config, computedTheme]); + + return generatedColors as React.CSSProperties; } export const ThemeContext = createContext<{ diff --git a/src/v4/social/components/CommunitySearchResult/CommunityItemSkeleton.module.css b/src/v4/social/components/CommunitySearchResult/CommunityItemSkeleton.module.css index 276625c95..a80d13a1d 100644 --- a/src/v4/social/components/CommunitySearchResult/CommunityItemSkeleton.module.css +++ b/src/v4/social/components/CommunitySearchResult/CommunityItemSkeleton.module.css @@ -5,7 +5,7 @@ padding-top: 1rem; margin-bottom: 1.5rem; height: 3.5rem; - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); width: 100%; } diff --git a/src/v4/social/components/CommunitySearchResult/CommunitySearchResult.module.css b/src/v4/social/components/CommunitySearchResult/CommunitySearchResult.module.css index 82ca0dfb2..d299fa3e1 100644 --- a/src/v4/social/components/CommunitySearchResult/CommunitySearchResult.module.css +++ b/src/v4/social/components/CommunitySearchResult/CommunitySearchResult.module.css @@ -4,7 +4,7 @@ justify-content: center; align-items: start; width: 100%; - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); overflow-x: hidden; } diff --git a/src/v4/social/components/CreatePostMenu/CreatePostMenu.module.css b/src/v4/social/components/CreatePostMenu/CreatePostMenu.module.css index e2bbd90ef..1e3742a27 100644 --- a/src/v4/social/components/CreatePostMenu/CreatePostMenu.module.css +++ b/src/v4/social/components/CreatePostMenu/CreatePostMenu.module.css @@ -1,5 +1,5 @@ .createPostMenu { - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); padding: 0.75rem 1rem; border-radius: 0.75rem; position: absolute; diff --git a/src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.module.css b/src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.module.css index 8c038d48a..11d9e5107 100644 --- a/src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.module.css +++ b/src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.module.css @@ -1,7 +1,7 @@ .detailedMediaAttachment { display: block; width: 100%; - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); border-top-left-radius: 1.25rem; border-top-right-radius: 1.25rem; box-shadow: var(--asc-box-shadow-04); diff --git a/src/v4/social/components/EmptyNewsFeed/EmptyNewsFeed.module.css b/src/v4/social/components/EmptyNewsFeed/EmptyNewsFeed.module.css index 497bbd9d0..202520acb 100644 --- a/src/v4/social/components/EmptyNewsFeed/EmptyNewsFeed.module.css +++ b/src/v4/social/components/EmptyNewsFeed/EmptyNewsFeed.module.css @@ -5,7 +5,7 @@ justify-content: center; width: 100%; height: 100%; - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); } .emptyNewsfeed__text { diff --git a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css index ad2c8065f..2ec73b91a 100644 --- a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css +++ b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.module.css @@ -21,7 +21,7 @@ } .bottomSheet .react-modal-sheet-content { - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); padding: 16px; } @@ -57,8 +57,8 @@ } .input.hasError { - border-bottom-color: var(--asc-color-alert); - color: var(--asc-color-alert); + border-bottom-color: var(--asc-color-alert-default); + color: var(--asc-color-alert-default); } .label { @@ -67,12 +67,12 @@ .label::after { content: none; - color: var(--asc-color-alert); + color: var(--asc-color-alert-default); } .label.required::after { content: '*'; - color: var(--asc-color-alert); + color: var(--asc-color-alert-default); } .description { @@ -80,7 +80,7 @@ } .errorText { - color: var(--asc-color-alert); + color: var(--asc-color-alert-default); } .characterCount { @@ -122,7 +122,7 @@ .removeIcon { width: 1.5rem; height: 1.5rem; - fill: var(--asc-color-alert); + fill: var(--asc-color-alert-default); } .removeLinkButton { @@ -130,7 +130,7 @@ justify-content: flex-start; align-items: center; gap: var(--asc-spacing-s1); - color: var(--asc-color-alert); + color: var(--asc-color-alert-default); border-radius: 0; background-color: transparent; transition: color 0.3s ease; diff --git a/src/v4/social/components/MediaAttachment/MediaAttachment.module.css b/src/v4/social/components/MediaAttachment/MediaAttachment.module.css index 9a69e2307..96fb48dc7 100644 --- a/src/v4/social/components/MediaAttachment/MediaAttachment.module.css +++ b/src/v4/social/components/MediaAttachment/MediaAttachment.module.css @@ -4,7 +4,7 @@ justify-content: space-between; width: 100%; padding: 0.5rem 1rem; - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); border-top-left-radius: 1.25rem; border-top-right-radius: 1.25rem; box-shadow: var(--asc-box-shadow-04); diff --git a/src/v4/social/components/MyCommunities/MyCommunities.module.css b/src/v4/social/components/MyCommunities/MyCommunities.module.css index d055c9016..144b47ccd 100644 --- a/src/v4/social/components/MyCommunities/MyCommunities.module.css +++ b/src/v4/social/components/MyCommunities/MyCommunities.module.css @@ -2,5 +2,5 @@ height: 100%; width: 100%; padding: 0 1rem; - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); } diff --git a/src/v4/social/components/Newsfeed/Newsfeed.module.css b/src/v4/social/components/Newsfeed/Newsfeed.module.css index 4b0053de0..215bf7297 100644 --- a/src/v4/social/components/Newsfeed/Newsfeed.module.css +++ b/src/v4/social/components/Newsfeed/Newsfeed.module.css @@ -2,7 +2,7 @@ display: flex; flex-direction: column; width: 100%; - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); } .newsfeed__postList { diff --git a/src/v4/social/components/PostContent/PostContent.module.css b/src/v4/social/components/PostContent/PostContent.module.css index 6a7d0ee07..34380c8f7 100644 --- a/src/v4/social/components/PostContent/PostContent.module.css +++ b/src/v4/social/components/PostContent/PostContent.module.css @@ -1,5 +1,5 @@ .postContent { - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); } .postContent__bar { diff --git a/src/v4/social/components/PostContent/PostContentSkeleton.module.css b/src/v4/social/components/PostContent/PostContentSkeleton.module.css index bf8d440aa..ca55a5d52 100644 --- a/src/v4/social/components/PostContent/PostContentSkeleton.module.css +++ b/src/v4/social/components/PostContent/PostContentSkeleton.module.css @@ -3,7 +3,7 @@ flex-direction: column; gap: 1rem; height: 14rem; - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); } .postContentSkeleton__bar { diff --git a/src/v4/social/components/ReactionList/ReactionList.module.css b/src/v4/social/components/ReactionList/ReactionList.module.css index 808dd8a36..0c14eedd4 100644 --- a/src/v4/social/components/ReactionList/ReactionList.module.css +++ b/src/v4/social/components/ReactionList/ReactionList.module.css @@ -30,7 +30,7 @@ .tabItem { cursor: pointer; padding: var(--asc-spacing-xxs2) var(--asc-spacing-s1); - background: var(--asc-color-base-background); + background: var(--asc-color-background-default); color: var(--asc-color-base-shade6); padding-bottom: var(--asc-spacing-s1); border-bottom: transparent; @@ -58,7 +58,7 @@ display: flex; align-items: center; gap: var(--asc-spacing-s1); - background: var(--asc-color-base-background); + background: var(--asc-color-background-default); border-radius: var(--asc-border-radius-sm); width: 100%; border-bottom: 1px solid var(--asc-color-base-shade4); diff --git a/src/v4/social/components/StoryTab/StoryTabCommunity.module.css b/src/v4/social/components/StoryTab/StoryTabCommunity.module.css index 3378a1b08..eb292cd27 100644 --- a/src/v4/social/components/StoryTab/StoryTabCommunity.module.css +++ b/src/v4/social/components/StoryTab/StoryTabCommunity.module.css @@ -26,7 +26,7 @@ } .storyTabContainer { - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); position: relative; width: 3rem; display: flex; diff --git a/src/v4/social/components/StoryTab/StoryTabGlobalFeed.module.css b/src/v4/social/components/StoryTab/StoryTabGlobalFeed.module.css index e89f4f79f..83c3add76 100644 --- a/src/v4/social/components/StoryTab/StoryTabGlobalFeed.module.css +++ b/src/v4/social/components/StoryTab/StoryTabGlobalFeed.module.css @@ -2,7 +2,7 @@ display: flex; overflow: auto; padding: 0.625rem; - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); gap: var(--asc-spacing-s1); } diff --git a/src/v4/social/components/TopNavigation/TopNavigation.module.css b/src/v4/social/components/TopNavigation/TopNavigation.module.css index 80f794e10..707c57fb2 100644 --- a/src/v4/social/components/TopNavigation/TopNavigation.module.css +++ b/src/v4/social/components/TopNavigation/TopNavigation.module.css @@ -2,7 +2,7 @@ display: flex; justify-content: space-between; align-items: center; - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); height: 3.625rem; } diff --git a/src/v4/social/components/TopSearchBar/TopSearchBar.module.css b/src/v4/social/components/TopSearchBar/TopSearchBar.module.css index bee5bc93d..d08fcbe81 100644 --- a/src/v4/social/components/TopSearchBar/TopSearchBar.module.css +++ b/src/v4/social/components/TopSearchBar/TopSearchBar.module.css @@ -3,7 +3,7 @@ grid-template-columns: minmax(0, 1fr) min-content; align-items: center; gap: 0.5rem; - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); } .topSearchBar__inputBar { diff --git a/src/v4/social/components/UserSearchResult/UserSearchItemSkeleton.module.css b/src/v4/social/components/UserSearchResult/UserSearchItemSkeleton.module.css index 77035e40b..55b3fd90e 100644 --- a/src/v4/social/components/UserSearchResult/UserSearchItemSkeleton.module.css +++ b/src/v4/social/components/UserSearchResult/UserSearchItemSkeleton.module.css @@ -5,7 +5,7 @@ padding-top: 1rem; margin-bottom: 1.5rem; height: 3.5rem; - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); width: 100%; } diff --git a/src/v4/social/components/UserSearchResult/UserSearchResult.module.css b/src/v4/social/components/UserSearchResult/UserSearchResult.module.css index c1001919d..a28c7da42 100644 --- a/src/v4/social/components/UserSearchResult/UserSearchResult.module.css +++ b/src/v4/social/components/UserSearchResult/UserSearchResult.module.css @@ -4,6 +4,6 @@ justify-content: center; align-items: start; width: 100%; - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); overflow-x: hidden; } diff --git a/src/v4/social/elements/CancelButton/CancelButton.module.css b/src/v4/social/elements/CancelButton/CancelButton.module.css index 108d73af5..071ccd3aa 100644 --- a/src/v4/social/elements/CancelButton/CancelButton.module.css +++ b/src/v4/social/elements/CancelButton/CancelButton.module.css @@ -1,5 +1,5 @@ .cancelButton { cursor: pointer; color: var(--asc-color-primary-default); - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); } diff --git a/src/v4/social/elements/CreateStoryButtons/CreateStoryButton.tsx b/src/v4/social/elements/CreateStoryButtons/CreateStoryButton.tsx index ec5d1803d..1078a7a76 100644 --- a/src/v4/social/elements/CreateStoryButtons/CreateStoryButton.tsx +++ b/src/v4/social/elements/CreateStoryButtons/CreateStoryButton.tsx @@ -8,8 +8,8 @@ import clsx from 'clsx'; const CreateStoryButtonSvg = (props: React.SVGProps<SVGSVGElement>) => ( <svg width="17" height="18" viewBox="0 0 17 18" xmlns="http://www.w3.org/2000/svg" {...props}> <path - fill-rule="evenodd" - clip-rule="evenodd" + fillRule="evenodd" + clipRule="evenodd" d="M0.771159 12.2248C1.00906 12.7861 1.30474 13.3088 1.65819 13.7927C1.85468 14.0618 2.2428 14.0749 2.47778 13.8387L2.85238 13.4622C3.05585 13.2578 3.07242 12.9352 2.91359 12.6944C2.68511 12.3479 2.4918 11.9785 2.33366 11.5862C2.17162 11.1841 2.04651 10.7654 1.95831 10.33C1.90156 10.0497 1.66209 9.83785 1.37619 9.83785H0.7981C0.460143 9.83785 0.194237 10.1301 0.252477 10.463C0.359316 11.0737 0.53221 11.661 0.771159 12.2248ZM0.791992 5.77587C0.565717 6.33406 0.392758 6.91522 0.273115 7.51935C0.206005 7.85821 0.473794 8.1628 0.819238 8.1628H1.37619C1.66209 8.1628 1.90156 7.95091 1.95831 7.6707C2.04651 7.23526 2.17162 6.81652 2.33366 6.41448C2.4918 6.0221 2.68511 5.6527 2.91359 5.30628C3.07242 5.06546 3.05585 4.7429 2.85238 4.53841L2.47778 4.16192C2.2428 3.92576 1.85456 3.93884 1.65906 4.2086C1.30847 4.69235 1.01944 5.21477 0.791992 5.77587ZM5.26074 16.7265C5.81526 16.966 6.39491 17.1396 6.9997 17.247C7.33267 17.3061 7.62533 17.0401 7.62533 16.7019V16.1571C7.62533 15.8715 7.41372 15.6325 7.13436 15.5731C6.70488 15.4817 6.29562 15.3497 5.90658 15.177C5.52127 15.0061 5.14859 14.8025 4.78852 14.5662C4.5475 14.4081 4.22544 14.4327 4.02574 14.6406L3.64108 15.041C3.40868 15.2829 3.43286 15.6735 3.7084 15.8649C4.19354 16.2017 4.71098 16.4889 5.26074 16.7265ZM3.71752 2.12947C3.43846 2.32272 3.41731 2.71924 3.65673 2.95986L4.05715 3.36229C4.26214 3.56831 4.58771 3.58533 4.82847 3.42255C5.1704 3.19137 5.53324 2.99173 5.91699 2.82361C6.3137 2.64981 6.73277 2.5172 7.17418 2.42577C7.45449 2.3677 7.66699 2.12833 7.66699 1.84207V1.29669C7.66699 0.959243 7.37557 0.693429 7.04294 0.750218C6.41529 0.857371 5.82122 1.03203 5.26074 1.27419C4.71443 1.51024 4.20002 1.79533 3.71752 2.12947ZM9.82271 15.5735C9.54395 15.6315 9.33366 15.8703 9.33366 16.155V16.6953C9.33366 17.0359 9.63023 17.3024 9.96541 17.2422C11.8264 16.908 13.4005 16.0311 14.6878 14.6117C16.1184 13.0344 16.8337 11.1639 16.8337 9.00033C16.8337 6.83673 16.1184 4.96627 14.6878 3.38894C13.4005 1.96952 11.8264 1.09268 9.96541 0.758409C9.63023 0.698204 9.33366 0.964784 9.33366 1.30532V1.84562C9.33366 2.13035 9.54395 2.36916 9.82271 2.42716C11.2862 2.73166 12.5155 3.45008 13.5107 4.5824C14.6149 5.83868 15.167 7.31132 15.167 9.00033C15.167 10.6893 14.6149 12.162 13.5107 13.4182C12.5155 14.5506 11.2862 15.269 9.82271 15.5735Z" /> <path d="M12.7189 8.47933C12.9012 8.47933 13.0835 8.66162 13.0835 8.84391V9.57308C13.0835 9.77816 12.9012 9.93766 12.7189 9.93766H9.43766V13.2189C9.43766 13.424 9.25537 13.5835 9.07308 13.5835H8.34391C8.13883 13.5835 7.97933 13.424 7.97933 13.2189V9.93766H4.69808C4.493 9.93766 4.3335 9.77816 4.3335 9.57308V8.84391C4.3335 8.66162 4.493 8.47933 4.69808 8.47933H7.97933V5.19808C7.97933 5.01579 8.13883 4.8335 8.34391 4.8335H9.07308C9.25537 4.8335 9.43766 5.01579 9.43766 5.19808V8.47933H12.7189Z" /> diff --git a/src/v4/social/elements/EditCancelButton/EditCancelButton.module.css b/src/v4/social/elements/EditCancelButton/EditCancelButton.module.css index 07161eeb8..b45028730 100644 --- a/src/v4/social/elements/EditCancelButton/EditCancelButton.module.css +++ b/src/v4/social/elements/EditCancelButton/EditCancelButton.module.css @@ -2,5 +2,5 @@ all: unset; cursor: pointer; color: var(--asc-color-base-default); - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); } diff --git a/src/v4/social/elements/PostTextField/PostTextField.module.css b/src/v4/social/elements/PostTextField/PostTextField.module.css index c04978472..f292e725d 100644 --- a/src/v4/social/elements/PostTextField/PostTextField.module.css +++ b/src/v4/social/elements/PostTextField/PostTextField.module.css @@ -24,7 +24,7 @@ height: 100%; font-size: var(--asc-text-font-size-md); color: var(--asc-color-base-default); - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); padding: 0 1rem; } diff --git a/src/v4/social/elements/ReactionButton/ReactionButton.module.css b/src/v4/social/elements/ReactionButton/ReactionButton.module.css index a4516b4ba..1b5153c8f 100644 --- a/src/v4/social/elements/ReactionButton/ReactionButton.module.css +++ b/src/v4/social/elements/ReactionButton/ReactionButton.module.css @@ -46,7 +46,7 @@ gap: 0.25rem; padding: 0.25rem 0.375rem; border-radius: 1.75rem; - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); box-shadow: var(--asc-box-shadow-03); } diff --git a/src/v4/social/elements/StoryRing/StoryRing.module.css b/src/v4/social/elements/StoryRing/StoryRing.module.css index 778241587..5099ce071 100644 --- a/src/v4/social/elements/StoryRing/StoryRing.module.css +++ b/src/v4/social/elements/StoryRing/StoryRing.module.css @@ -5,7 +5,7 @@ } .errorRing { - stroke: var(--asc-color-alert); + stroke: var(--asc-color-alert-default); } .uploadingProgressRing { diff --git a/src/v4/social/elements/StoryRing/StoryRing.tsx b/src/v4/social/elements/StoryRing/StoryRing.tsx index 741a902d2..04cf0e345 100644 --- a/src/v4/social/elements/StoryRing/StoryRing.tsx +++ b/src/v4/social/elements/StoryRing/StoryRing.tsx @@ -198,7 +198,9 @@ export const StoryRing = ({ cx={size / 2} cy={size / 2} r={size / 2 - 1} - stroke={getComputedStyle(document.documentElement).getPropertyValue('--asc-color-alert')} + stroke={getComputedStyle(document.documentElement).getPropertyValue( + '--asc-color-alert-default', + )} strokeWidth="2" ></circle> </svg> diff --git a/src/v4/social/internal-components/Comment/Comment.module.css b/src/v4/social/internal-components/Comment/Comment.module.css index 46a8e9a08..d672e0396 100644 --- a/src/v4/social/internal-components/Comment/Comment.module.css +++ b/src/v4/social/internal-components/Comment/Comment.module.css @@ -5,7 +5,7 @@ gap: 0.25rem; border: none; color: var(--asc-color-base-default); - background: var(--asc-color-base-background); + background: var(--asc-color-background-default); text-align: start; } @@ -29,7 +29,7 @@ border-radius: var(--asc-border-radius-sm); text-align: center; color: var(--asc-color-base-shade2); - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); } .deletedCommentBlock:first-child { diff --git a/src/v4/social/internal-components/Comment/UIComment.module.css b/src/v4/social/internal-components/Comment/UIComment.module.css index 78786861c..c7d4c6dad 100644 --- a/src/v4/social/internal-components/Comment/UIComment.module.css +++ b/src/v4/social/internal-components/Comment/UIComment.module.css @@ -149,7 +149,7 @@ .deletedCommentBlock { padding: var(--asc-spacing-m1); - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); border-radius: var(--asc-border-radius-sm); text-align: center; color: var(--asc-color-base-shade2); diff --git a/src/v4/social/internal-components/CommentComposeBar/CommentComposeBar.module.css b/src/v4/social/internal-components/CommentComposeBar/CommentComposeBar.module.css index 37f2e2f6e..bfb00c2db 100644 --- a/src/v4/social/internal-components/CommentComposeBar/CommentComposeBar.module.css +++ b/src/v4/social/internal-components/CommentComposeBar/CommentComposeBar.module.css @@ -35,7 +35,7 @@ .commentComposeBarContainer { width: 100%; - background: var(--asc-color-base-background); + background: var(--asc-color-background-default); display: flex; gap: var(--asc-spacing-s1); align-items: center; diff --git a/src/v4/social/internal-components/ImageViewer/ImageViewer.module.css b/src/v4/social/internal-components/ImageViewer/ImageViewer.module.css index 1a37cfa07..c78b22120 100644 --- a/src/v4/social/internal-components/ImageViewer/ImageViewer.module.css +++ b/src/v4/social/internal-components/ImageViewer/ImageViewer.module.css @@ -9,7 +9,7 @@ } .modalContent { - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); position: relative; display: flex; justify-content: center; diff --git a/src/v4/social/internal-components/PostMenu/PostMenu.module.css b/src/v4/social/internal-components/PostMenu/PostMenu.module.css index 98973f662..35f307011 100644 --- a/src/v4/social/internal-components/PostMenu/PostMenu.module.css +++ b/src/v4/social/internal-components/PostMenu/PostMenu.module.css @@ -43,11 +43,11 @@ } .postMenu__deletePost__text { - color: var(--asc-color-alert); + color: var(--asc-color-alert-default); } .postMenu__deletePost__icon { - fill: var(--asc-color-alert); + fill: var(--asc-color-alert-default); width: 1.5rem; height: 1.25rem; } diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css b/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css index 64dc1feee..fb22e8c72 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Renderers.module.css @@ -263,7 +263,7 @@ gap: var(--asc-spacing-s1); border: none; color: var(--asc-color-base-default); - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); padding: var(--asc-spacing-m1) var(--asc-spacing-m2); cursor: pointer; border-radius: var(--asc-border-radius-sm); diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/Footer.module.css b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/Footer.module.css index 86df665b9..55fdbdb2f 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/Footer.module.css +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/Footer.module.css @@ -20,7 +20,7 @@ width: 100%; height: var(--asc-spacing-l4); padding: var(--asc-spacing-s2); - background-color: var(--asc-color-alert); + background-color: var(--asc-color-alert-default); color: var(--asc-color-white); z-index: 0; } diff --git a/src/v4/social/internal-components/TabButton/TabButton.module.css b/src/v4/social/internal-components/TabButton/TabButton.module.css index e058e951c..610df7b10 100644 --- a/src/v4/social/internal-components/TabButton/TabButton.module.css +++ b/src/v4/social/internal-components/TabButton/TabButton.module.css @@ -2,7 +2,7 @@ display: flex; align-items: center; padding: var(--asc-spacing-s1) var(--asc-spacing-s2); - background: var(--asc-color-base-background, #fff); + background: var(--asc-color-background-default, #fff); border: 1px solid var(--asc-color-base-shade4); border-radius: 1.5rem; cursor: pointer; diff --git a/src/v4/social/internal-components/TabsBar/TabsBar.module.css b/src/v4/social/internal-components/TabsBar/TabsBar.module.css index a377b2e4d..495e7c761 100644 --- a/src/v4/social/internal-components/TabsBar/TabsBar.module.css +++ b/src/v4/social/internal-components/TabsBar/TabsBar.module.css @@ -7,7 +7,7 @@ input { .tabsRoot { display: flex; flex-direction: column; - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); } .tabsList { diff --git a/src/v4/social/internal-components/VideoViewer/VideoViewer.module.css b/src/v4/social/internal-components/VideoViewer/VideoViewer.module.css index 44fb5d7d3..df29fdf01 100644 --- a/src/v4/social/internal-components/VideoViewer/VideoViewer.module.css +++ b/src/v4/social/internal-components/VideoViewer/VideoViewer.module.css @@ -9,7 +9,7 @@ } .modalContent { - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); position: relative; display: flex; justify-content: center; diff --git a/src/v4/social/pages/MyCommunitiesSearchPage/MyCommunitiesSearchPage.module.css b/src/v4/social/pages/MyCommunitiesSearchPage/MyCommunitiesSearchPage.module.css index 42ddf1e79..75dd77818 100644 --- a/src/v4/social/pages/MyCommunitiesSearchPage/MyCommunitiesSearchPage.module.css +++ b/src/v4/social/pages/MyCommunitiesSearchPage/MyCommunitiesSearchPage.module.css @@ -4,5 +4,5 @@ gap: 0.5rem; width: 100%; padding: 1rem; - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); } diff --git a/src/v4/social/pages/PostComposerPage/PostComposerPage.module.css b/src/v4/social/pages/PostComposerPage/PostComposerPage.module.css index bcde8bfe5..cabae3895 100644 --- a/src/v4/social/pages/PostComposerPage/PostComposerPage.module.css +++ b/src/v4/social/pages/PostComposerPage/PostComposerPage.module.css @@ -1,7 +1,7 @@ .postComposerPage { position: relative; display: block; - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); height: 100%; } @@ -27,13 +27,13 @@ border-radius: 0.5rem; box-shadow: var(--asc-box-shadow-03); background-color: var(--asc-color-secondary-default); - color: var(--asc-color-base-background); + color: var(--asc-color-background-default); } .selectPostTargetPag_infoIcon { width: 1.5rem; height: 1.5rem; - fill: var(--asc-color-base-background); + fill: var(--asc-color-background-default); } .postComposerPage__notiWrap { diff --git a/src/v4/social/pages/PostDetailPage/PostDetailPage.module.css b/src/v4/social/pages/PostDetailPage/PostDetailPage.module.css index 638bfdc2a..881e97d33 100644 --- a/src/v4/social/pages/PostDetailPage/PostDetailPage.module.css +++ b/src/v4/social/pages/PostDetailPage/PostDetailPage.module.css @@ -3,7 +3,7 @@ flex-direction: column; position: relative; height: 100%; - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); } .postDetailPage__container { @@ -29,7 +29,7 @@ left: 0; right: 0; top: 0; - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); } .postDetailPage__topBar__title { diff --git a/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.module.css b/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.module.css index 47addeb0b..301662eff 100644 --- a/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.module.css +++ b/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.module.css @@ -2,7 +2,7 @@ max-width: 100vw; height: 100vh; padding: var(--asc-spacing-none) var(--asc-spacing-m1); - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); } .selectPostTargetPage__topBar { @@ -10,7 +10,7 @@ justify-content: space-between; align-items: center; padding: var(--asc-spacing-m1) var(--asc-spacing-none); - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); } .selectPostTargetPage__timeline { diff --git a/src/v4/social/pages/SocialGlobalSearchPage/SocialGlobalSearchPage.module.css b/src/v4/social/pages/SocialGlobalSearchPage/SocialGlobalSearchPage.module.css index 5a811d338..fde2fce7f 100644 --- a/src/v4/social/pages/SocialGlobalSearchPage/SocialGlobalSearchPage.module.css +++ b/src/v4/social/pages/SocialGlobalSearchPage/SocialGlobalSearchPage.module.css @@ -4,7 +4,7 @@ gap: 0.5rem; width: 100%; padding: 1rem; - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); } .socialGlobalSearchPage__tabs { diff --git a/src/v4/social/pages/SocialHomePage/SocialHomePage.module.css b/src/v4/social/pages/SocialHomePage/SocialHomePage.module.css index 164175798..660b8f371 100644 --- a/src/v4/social/pages/SocialHomePage/SocialHomePage.module.css +++ b/src/v4/social/pages/SocialHomePage/SocialHomePage.module.css @@ -2,7 +2,7 @@ position: relative; width: 100%; height: 100%; - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); } .socialHomePage__topBar { @@ -10,7 +10,7 @@ position: absolute; width: 100%; height: 7.5rem; - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); } .socialHomePage__topNavigation { diff --git a/src/v4/styles/global.css b/src/v4/styles/global.css index 214774cc5..96c3840ad 100644 --- a/src/v4/styles/global.css +++ b/src/v4/styles/global.css @@ -27,7 +27,6 @@ font-family: Inter, sans-serif; /* color palette */ - --asc-color-alert: #fa4d30; --asc-color-black: #000; --asc-color-white: #fff; --asc-color-primary-default: #1054de; @@ -41,13 +40,20 @@ --asc-color-base-shade2: #898e9e; --asc-color-base-shade3: #a5a9b5; --asc-color-base-shade4: #ebecef; - --asc-color-base-shade5: #f9f9fa; --asc-color-secondary-default: #292b32; --asc-color-secondary-shade1: #636878; --asc-color-secondary-shade2: #898e9e; --asc-color-secondary-shade3: #a5a9b5; --asc-color-secondary-shade4: #ebecef; --asc-color-secondary-shade5: #f9f9fa; + --asc-color-alert-default: #fa4d30; + --asc-color-highlight-default: #1054de; + --asc-color-message-bubble-primary: #1054de; + --asc-color-message-bubble-secondary: #ebecef; + --asc-color-background-default: #fff; + --asc-color-background-shade1: #f6f7f8; + --asc-color-background-transparent-black: #00000080; + --asc-color-background-transparent-white: #fffc; /* Font */ --asc-text-global-font-family: inter, -apple-system, blinkmacsystemfont, arial, sans-serif; @@ -118,7 +124,6 @@ @media (prefers-color-scheme: light) { :root { - --asc-color-alert: #fa4d30; --asc-color-black: #000; --asc-color-white: #fff; --asc-color-primary-default: #1054de; @@ -132,20 +137,25 @@ --asc-color-base-shade2: #898e9e; --asc-color-base-shade3: #a5a9b5; --asc-color-base-shade4: #ebecef; - --asc-color-base-shade5: #f9f9fa; --asc-color-secondary-default: #292b32; --asc-color-secondary-shade1: #636878; --asc-color-secondary-shade2: #898e9e; --asc-color-secondary-shade3: #a5a9b5; --asc-color-secondary-shade4: #ebecef; --asc-color-secondary-shade5: #f9f9fa; - --asc-color-base-background: #fff; + --asc-color-alert-default: #fa4d30; + --asc-color-highlight-default: #1054de; + --asc-color-message-bubble-primary: #1054de; + --asc-color-message-bubble-secondary: #ebecef; + --asc-color-background-default: #fff; + --asc-color-background-shade1: #f6f7f8; + --asc-color-background-transparent-black: #00000080; + --asc-color-background-transparent-white: #fffc; } } @media (prefers-color-scheme: dark) { :root { - --asc-color-alert: #fa4d30; --asc-color-black: #000; --asc-color-white: #fff; --asc-color-primary-default: #1054de; @@ -159,14 +169,20 @@ --asc-color-base-shade2: #6e7487; --asc-color-base-shade3: #40434e; --asc-color-base-shade4: #292b32; - --asc-color-base-shade5: #f9f9fa; --asc-color-secondary-default: #292b32; - --asc-color-secondary-shade1: #636878; + --asc-color-secondary-shade1: #a5a9b5; --asc-color-secondary-shade2: #898e9e; --asc-color-secondary-shade3: #a5a9b5; - --asc-color-secondary-shade4: #ebecef; - --asc-color-secondary-shade5: #f9f9fa; - --asc-color-base-background: #191919; + --asc-color-secondary-shade4: #40434e; + --asc-color-secondary-shade5: #292b32; + --asc-color-alert-default: #fa4d30; + --asc-color-highlight-default: #1054de; + --asc-color-message-bubble-primary: #1054de; + --asc-color-message-bubble-secondary: #292b32; + --asc-color-background-default: #191919; + --asc-color-background-shade1: #40434e; + --asc-color-background-transparent-black: #00000080; + --asc-color-background-transparent-white: #fffc; } } From 5fa2bfe889ac28458d3509dcb5e8820a73b9dc98 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Tue, 9 Jul 2024 17:43:43 +0700 Subject: [PATCH 218/300] fix: ASC-24074 - fix an incorrect text position (#499) * fix: fix an incorrect text position * fix: update classname --- .../internal-components/CommentAd/CommentAd.module.css | 4 ++-- .../social/internal-components/CommentAd/CommentAd.tsx | 10 +++++----- .../internal-components/PostAd/PostAd.module.css | 4 ++-- src/v4/social/internal-components/PostAd/PostAd.tsx | 8 +++++--- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/v4/social/internal-components/CommentAd/CommentAd.module.css b/src/v4/social/internal-components/CommentAd/CommentAd.module.css index 6ece0abc3..92ce5e546 100644 --- a/src/v4/social/internal-components/CommentAd/CommentAd.module.css +++ b/src/v4/social/internal-components/CommentAd/CommentAd.module.css @@ -83,7 +83,7 @@ width: 100%; } -.commentAd__adCard__headline { +.commentAd__adCard__description { color: var(--asc-color-base-shade1); width: 100%; word-wrap: break-word; @@ -91,7 +91,7 @@ text-overflow: ellipsis; } -.commentAd__adCard__description { +.commentAd__adCard__headline { color: var(--asc-color-base-default); width: 100%; word-wrap: break-word; diff --git a/src/v4/social/internal-components/CommentAd/CommentAd.tsx b/src/v4/social/internal-components/CommentAd/CommentAd.tsx index 10e09b608..37f1776df 100644 --- a/src/v4/social/internal-components/CommentAd/CommentAd.tsx +++ b/src/v4/social/internal-components/CommentAd/CommentAd.tsx @@ -45,7 +45,7 @@ export const CommentAd = ({ pageId = '*', ad }: CommentAdProps) => { <div className={styles.commentAd__details}> <div className={styles.commentAd__content}> <Typography.BodyBold className={styles.commentAd__content__username}> - {ad.advertiser?.companyName} + {ad.advertiser?.name} </Typography.BodyBold> <AdsBadge /> @@ -62,11 +62,11 @@ export const CommentAd = ({ pageId = '*', ad }: CommentAdProps) => { </div> <div className={styles.commentAd__adCard__detail}> <div className={styles.commentAd__adCard__textContainer}> - <Typography.Caption className={styles.commentAd__adCard__headline}> - {ad.headline} - </Typography.Caption> - <Typography.BodyBold className={styles.commentAd__adCard__description}> + <Typography.Caption className={styles.commentAd__adCard__description}> {ad.description} + </Typography.Caption> + <Typography.BodyBold className={styles.commentAd__adCard__headline}> + {ad.headline} </Typography.BodyBold> </div> {ad.callToActionUrl ? ( diff --git a/src/v4/social/internal-components/PostAd/PostAd.module.css b/src/v4/social/internal-components/PostAd/PostAd.module.css index c8276b5f7..9aaa39bba 100644 --- a/src/v4/social/internal-components/PostAd/PostAd.module.css +++ b/src/v4/social/internal-components/PostAd/PostAd.module.css @@ -65,11 +65,11 @@ cursor: pointer; } -.footer__content__title { +.footer__content__description { color: var(--asc-color-base-shade1); } -.footer__content__description { +.footer__content__headline { color: var(--asc-color-base-default); } diff --git a/src/v4/social/internal-components/PostAd/PostAd.tsx b/src/v4/social/internal-components/PostAd/PostAd.tsx index 633586683..153ec55f7 100644 --- a/src/v4/social/internal-components/PostAd/PostAd.tsx +++ b/src/v4/social/internal-components/PostAd/PostAd.tsx @@ -37,7 +37,7 @@ export const PostAd = ({ pageId = '*', ad }: PostAdProps) => { </div> <div className={styles.header__detail}> <Typography.BodyBold className={styles.header__title}> - {ad.advertiser?.companyName} + {ad.advertiser?.name} </Typography.BodyBold> <AdsBadge /> </div> @@ -59,9 +59,11 @@ export const PostAd = ({ pageId = '*', ad }: PostAdProps) => { onClick={() => window?.open(ad?.callToActionUrl, '_blank')} > <div> - <Typography.Body className={styles.footer__content__title}>{ad.headline}</Typography.Body> - <Typography.BodyBold className={styles.footer__content__description}> + <Typography.Body className={styles.footer__content__description}> {ad.description} + </Typography.Body> + <Typography.BodyBold className={styles.footer__content__headline}> + {ad.headline} </Typography.BodyBold> </div> {ad.callToActionUrl ? ( From 20f004244fe0405a53374127e9b8dfe438f1360a Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Wed, 10 Jul 2024 20:10:40 +0700 Subject: [PATCH 219/300] feat: ASC-23131 - story premium ads (#508) * feat: story premium ads * chore: remove commented code --- src/v4/social/hooks/useGetActiveStories.ts | 10 +- .../AdInformation.module.css | 4 +- .../StoryAdInformation.module.css | 85 ++++ .../StoryAdInformation/StoryAdInformation.tsx | 67 ++++ .../StoryViewer/Renderers/Image.tsx | 49 ++- .../StoryViewer/Renderers/Video.tsx | 39 +- .../StoryViewer/Renderers/index.tsx | 2 + .../StoryViewer/Renderers/storyAd.module.css | 378 ++++++++++++++++++ .../StoryViewer/Renderers/storyAd.tsx | 186 +++++++++ .../StoryViewer/Renderers/types.ts | 41 +- .../pages/StoryPage/GlobalFeedStory.tsx | 89 +++-- 11 files changed, 853 insertions(+), 97 deletions(-) create mode 100644 src/v4/social/internal-components/StoryAdInformation/StoryAdInformation.module.css create mode 100644 src/v4/social/internal-components/StoryAdInformation/StoryAdInformation.tsx create mode 100644 src/v4/social/internal-components/StoryViewer/Renderers/storyAd.module.css create mode 100644 src/v4/social/internal-components/StoryViewer/Renderers/storyAd.tsx diff --git a/src/v4/social/hooks/useGetActiveStories.ts b/src/v4/social/hooks/useGetActiveStories.ts index f75c35804..a471aa94d 100644 --- a/src/v4/social/hooks/useGetActiveStories.ts +++ b/src/v4/social/hooks/useGetActiveStories.ts @@ -1,15 +1,19 @@ import { StoryRepository } from '@amityco/ts-sdk'; -import useLiveCollection from '~/v4/core/hooks/useLiveCollection'; +import { isNonNullable } from '~/v4/helpers/utils'; +import { usePaginator } from '~/v4/core/hooks/usePagination'; export const useGetActiveStoriesByTarget = (params: Amity.GetStoriesByTargetParam) => { - const { items, ...rest } = useLiveCollection({ + const { items, ...rest } = usePaginator({ fetcher: StoryRepository.getActiveStoriesByTarget, params, shouldCall: true, + pageSize: 10, + placement: 'story' as Amity.AdPlacement, + getItemId: (item) => item?.storyId || '', }); return { - stories: items, + stories: items.filter(isNonNullable), ...rest, }; }; diff --git a/src/v4/social/internal-components/AdInformation.tsx/AdInformation.module.css b/src/v4/social/internal-components/AdInformation.tsx/AdInformation.module.css index cb7ca9ad7..9ffdce2f5 100644 --- a/src/v4/social/internal-components/AdInformation.tsx/AdInformation.module.css +++ b/src/v4/social/internal-components/AdInformation.tsx/AdInformation.module.css @@ -1,6 +1,6 @@ .drawer__content { padding: 1rem; - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); max-height: 50%; position: fixed; bottom: 0; @@ -18,7 +18,7 @@ } .drawer__innerContent { - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); border-top-left-radius: 1.25rem; border-top-right-radius: 1.25rem; flex: 1; diff --git a/src/v4/social/internal-components/StoryAdInformation/StoryAdInformation.module.css b/src/v4/social/internal-components/StoryAdInformation/StoryAdInformation.module.css new file mode 100644 index 000000000..74e1dcc95 --- /dev/null +++ b/src/v4/social/internal-components/StoryAdInformation/StoryAdInformation.module.css @@ -0,0 +1,85 @@ +.drawer__content { + padding-top: 1rem; + background-color: var(--asc-color-background-default); + max-height: 70%; + position: absolute; + bottom: 0; + left: 0; + right: 0; + display: flex; + flex-direction: column; + border-top-left-radius: 1.25rem; + border-top-right-radius: 1.25rem; + z-index: 100000; +} + +.drawer__content:focus { + outline: none; +} + +.drawer__innerContent { + background-color: var(--asc-color-background-default); + border-top-left-radius: 1.25rem; + border-top-right-radius: 1.25rem; + padding-left: 1rem; + padding-right: 1rem; + flex: 1; +} + +.drawer__placeholder { + margin: auto; + width: 3rem; + height: 0.375rem; + flex-shrink: 0; + border-radius: 9999px; + background-color: var(--asc-color-secondary-shade2); + margin-bottom: 2rem; +} + +.drawer__overlay { + background-color: color(from var(--asc-color-black) srgb r g b / 70%); + inset: 0; + position: absolute; + z-index: 100000; +} + +.drawer__title { + padding: 0.75rem 1rem; + border-bottom: 0.0313rem solid var(--asc-color-base-shade3); +} + +.drawer__content__data { + padding: 1.5rem 1rem 0; + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.drawer__content__data__title { + color: var(--asc-color-base-default); +} + +.drawer__content__data__text { + display: flex; + padding: 0.5rem; + align-items: flex-start; + gap: 0.5rem; + background-color: var(--asc-color-base-shade4); + border-radius: 0.25rem; +} + +.drawer__content__data__caption { + color: var(--asc-color-base-shade1); +} + +.drawer__content__data__infoIcon { + height: 1rem; + width: 1rem; + fill: var(--asc-color-secondary-shade2); +} + +.drawer__content__emptySpace { + min-height: 15rem; + width: 100%; + background-color: var(--asc-color-background-default); +} diff --git a/src/v4/social/internal-components/StoryAdInformation/StoryAdInformation.tsx b/src/v4/social/internal-components/StoryAdInformation/StoryAdInformation.tsx new file mode 100644 index 000000000..6cb95d249 --- /dev/null +++ b/src/v4/social/internal-components/StoryAdInformation/StoryAdInformation.tsx @@ -0,0 +1,67 @@ +import React from 'react'; +import { Typography } from '~/v4/core/components'; +import InfoCircle from '~/v4/icons/InfoCircle'; +import { Drawer } from 'vaul'; + +import styles from './StoryAdInformation.module.css'; + +interface StoryAdInformationProps { + isOpen: boolean; + onOpenChange: (open: boolean) => void; + ad: Amity.Ad; + targetRef: React.RefObject<HTMLDivElement>; +} + +export const StoryAdInformation = ({ + isOpen, + onOpenChange, + ad, + targetRef, +}: StoryAdInformationProps) => { + return ( + <Drawer.Root + open={isOpen} + onOpenChange={onOpenChange} + onDrag={(ev) => { + ev.preventDefault(); + ev.stopPropagation(); + }} + > + <Drawer.Portal container={targetRef?.current}> + <Drawer.Overlay className={styles.drawer__overlay} /> + <Drawer.Content className={styles.drawer__content}> + <div className={styles.drawer__innerContent}> + <div className={styles.drawer__placeholder} /> + <Drawer.Title className={styles.drawer__title}> + <Typography.Title>About this advertisement</Typography.Title> + </Drawer.Title> + <div className={styles.drawer__content__data}> + <Typography.BodyBold className={styles.drawer__content__data__title}> + Why this advertisement? + </Typography.BodyBold> + <div className={styles.drawer__content__data__text}> + <InfoCircle className={styles.drawer__content__data__infoIcon} /> + <Typography.Caption className={styles.drawer__content__data__caption}> + You're seeing this advertisement because it was displayed to all users in the + system. + </Typography.Caption> + </div> + </div> + <div className={styles.drawer__content__data}> + <Typography.BodyBold className={styles.drawer__content__data__title}> + About this advertiser + </Typography.BodyBold> + <div className={styles.drawer__content__data__text}> + <InfoCircle className={styles.drawer__content__data__infoIcon} /> + <Typography.Caption className={styles.drawer__content__data__caption}> + Advertiser name: {ad.advertiser?.companyName} + </Typography.Caption> + </div> + </div> + </div> + <div className={styles.drawer__content__emptySpace}></div> + </Drawer.Content> + </Drawer.Portal> + </Drawer.Root> + ); +}; diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index 7ab2a7187..2e23dfa8f 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -24,7 +24,24 @@ import clsx from 'clsx'; import { StoryProgressBar } from '~/v4/social/elements/StoryProgressBar/StoryProgressBar'; -export const renderer: CustomRenderer = ({ story, action, config, onClose, onClickCommunity }) => { +export const renderer: CustomRenderer = ({ + story: { + actions, + fileInputRef, + addStoryButton, + currentIndex, + storiesCount, + increaseIndex, + pageId, + dragEventTarget, + story, + url, + }, + action, + config, + onClose, + onClickCommunity, +}) => { const { formatMessage } = useIntl(); const [loaded, setLoaded] = useState(false); const [isOpenBottomSheet, setIsOpenBottomSheet] = useState(false); @@ -36,7 +53,7 @@ export const renderer: CustomRenderer = ({ story, action, config, onClose, onCli const imageRef = useRef<HTMLImageElement>(null); const isLiked = !!(story && story.myReactions && story.myReactions.includes(LIKE_REACTION_KEY)); - const reactionsCount = story.reactionsCount || 0; + const reactionsCount = story?.reactionsCount || 0; const { storyId, @@ -46,17 +63,10 @@ export const renderer: CustomRenderer = ({ story, action, config, onClose, onCli createdAt, creator, community, - actions, - addStoryButton, - fileInputRef, myReactions, - currentIndex, - storiesCount, - increaseIndex, - pageId, - dragEventTarget, data, - } = story; + items, + } = story as Amity.Story; const { members } = useCommunityMembersCollection({ queryParams: { @@ -244,7 +254,7 @@ export const renderer: CustomRenderer = ({ story, action, config, onClose, onCli [styles.imageFill]: data.imageDisplayMode === 'fill', })} data-qa-anchor="image_view" - src={story.url} + src={url} onLoad={imageLoaded} alt="Story Image" crossOrigin="anonymous" @@ -291,22 +301,21 @@ export const renderer: CustomRenderer = ({ story, action, config, onClose, onCli /> </BottomSheet> - {story.items?.[0]?.data?.url && ( + {items?.[0]?.data?.url && ( <div className={styles.hyperLinkContainer}> <HyperLink href={ - story.items[0].data.url.startsWith('http') - ? story.items[0].data.url - : `https://${story.items[0].data.url}` + items[0].data.url.startsWith('http') + ? items[0].data.url + : `https://${items[0].data.url}` } target="_blank" rel="noopener noreferrer" - onClick={() => story.analytics.markLinkAsClicked()} + onClick={() => story?.analytics.markLinkAsClicked()} > <Truncate lines={1}> <span> - {story.items[0]?.data?.customText || - story.items[0].data.url.replace(/^https?:\/\//, '')} + {items[0]?.data?.customText || items[0].data.url.replace(/^https?:\/\//, '')} </span> </Truncate> </HyperLink> @@ -333,7 +342,7 @@ export const renderer: CustomRenderer = ({ story, action, config, onClose, onCli export const tester: Tester = (story) => { return { - condition: !story.content && (!story.type || story.type === 'image'), + condition: !!story.story?.storyId && (!story.type || story.type === 'image'), priority: 2, }; }; diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx index 9793d7a92..3e173b1b5 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx @@ -24,7 +24,17 @@ import rendererStyles from './Renderers.module.css'; import { StoryProgressBar } from '~/v4/social/elements/StoryProgressBar/StoryProgressBar'; export const renderer: CustomRenderer = ({ - story, + story: { + actions, + addStoryButton, + fileInputRef, + currentIndex, + storiesCount, + increaseIndex, + pageId, + dragEventTarget, + story, + }, action, config, messageHandler, @@ -51,16 +61,10 @@ export const renderer: CustomRenderer = ({ createdAt, creator, community, - actions, - addStoryButton, - fileInputRef, myReactions, - currentIndex, - storiesCount, - increaseIndex, - pageId, - dragEventTarget, - } = story; + data, + items, + } = story as Amity.Story; const { members } = useCommunityMembersCollection({ queryParams: { @@ -284,22 +288,21 @@ export const renderer: CustomRenderer = ({ shouldAllowInteraction={isMember} /> </BottomSheet> - {story.items?.[0]?.data?.url && ( + {items?.[0]?.data?.url && ( <div className={clsx(rendererStyles.hyperLinkContainer)}> <HyperLink href={ - story.items[0].data.url.startsWith('http') - ? story.items[0].data.url - : `https://${story.items[0].data.url}` + items[0].data.url.startsWith('http') + ? items[0].data.url + : `https://${items[0].data.url}` } target="_blank" rel="noopener noreferrer" - onClick={() => story.analytics.markLinkAsClicked()} + onClick={() => story?.analytics.markLinkAsClicked()} > <Truncate lines={1}> <span> - {story.items[0]?.data?.customText || - story.items[0].data.url.replace(/^https?:\/\//, '')} + {items[0]?.data?.customText || items[0].data.url.replace(/^https?:\/\//, '')} </span> </Truncate> </HyperLink> @@ -325,7 +328,7 @@ export const renderer: CustomRenderer = ({ export const tester: Tester = (story) => { return { - condition: story.type === 'video', + condition: !!story.story?.storyId && story.type === 'video', priority: 2, }; }; diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/index.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/index.tsx index 6c09caccd..798376e2b 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/index.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/index.tsx @@ -3,10 +3,12 @@ import video from './Video'; import defaultRenderer from './Default'; import autoplayContent from './AutoPlayContent'; import { CustomRenderer, Tester } from './types'; +import storyAd from './storyAd'; export const renderers: { renderer: CustomRenderer; tester: Tester }[] = [ image, video, + storyAd, autoplayContent, defaultRenderer, ]; diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/storyAd.module.css b/src/v4/social/internal-components/StoryViewer/Renderers/storyAd.module.css new file mode 100644 index 000000000..ecc059b9f --- /dev/null +++ b/src/v4/social/internal-components/StoryViewer/Renderers/storyAd.module.css @@ -0,0 +1,378 @@ +.iconButton { + position: absolute; + width: 2rem; + height: 2rem; + background-color: rgb(0 0 0 / 50%); + border-radius: 50%; + border: none; + top: 6rem; + left: 1.25rem; + z-index: 9999; + cursor: pointer; +} + +.hyperLinkContainer { + position: absolute; + bottom: 1.18rem; + z-index: 99999; + display: flex; + justify-content: center; + align-items: center; + padding: 0.625rem 1rem; + left: 50%; + transform: translateX(-50%); + border: 1px solid var(--asc-color-base-shade4); + border-radius: 1.5rem; + background-color: var(--asc-color-white); + box-shadow: var(--asc-box-shadow-03); +} + +.hyperLink__text { + text-decoration: none; + color: var(--asc-color-secondary-default); + text-align: center; + font-size: 0.9375rem; + font-style: normal; + font-weight: 600; + line-height: 1.25rem; /* 133.333% */ + letter-spacing: -0.015rem; +} + +.rendererContainer { + position: relative; + width: 100%; + height: 100%; + overflow: hidden; +} + +.storyVideo { + width: 100%; + height: 100%; + object-fit: contain; +} + +.muteCircleIcon { + width: 100%; + height: 100%; +} + +.unmuteCircleIcon { + width: 100%; + height: 100%; +} + +.loadingOverlay { + width: 100%; + height: 100%; + position: absolute; + left: 0; + top: 0; + background: rgb(0 0 0 / 90%); + z-index: 9; + display: flex; + justify-content: center; + align-items: center; +} + +.storyImageContainer { + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + background: var(--asc-story-image-background); +} + +.storyImage { + width: 100%; + height: 100%; + object-fit: contain; +} + +.playStoryButton { + color: var(--asc-color-white); + cursor: pointer; +} + +.pauseStoryButton { + color: var(--asc-color-white); + cursor: pointer; +} + +.closeButton { + color: var(--asc-color-white); + width: 1.25rem; + height: 1.25rem; + cursor: pointer; +} + +.verifiedBadge { + color: var(--asc-color-white); +} + +.dotsButton { + width: 1.5rem; + height: 1.5rem; + cursor: pointer; + color: var(--asc-color-white); +} + +.viewStoryInfoContainer { + display: flex; + flex-direction: column; + justify-content: flex-start; + width: 100%; +} + +.viewStoryCompostBarContainer { + width: 100%; + display: flex; + position: absolute; + justify-content: space-between; + align-items: center; + height: 3.5rem; + padding: var(--asc-spacing-s2); + background-color: var(--asc-color-black); + bottom: 0; +} + +.viewStoryCompostBarViewIconContainer { + display: flex; + align-items: center; + justify-content: space-between; + color: var(--asc-color-white); + gap: var(--asc-spacing-xxs2); +} + +.viewStoryCompostBarEngagementContainer { + display: flex; + align-items: center; + justify-content: space-between; + color: var(--asc-color-white); + gap: var(--asc-spacing-s2); +} + +.viewStoryCompostBarEngagementIconContainer { + display: flex; + align-items: center; + justify-content: space-between; + color: var(--asc-color-white); + gap: var(--asc-spacing-xxs2); + border-radius: 50%; + padding: var(--asc-spacing-s1) var(--asc-spacing-s2); + background-color: #292b32; +} + +.header { + height: 5rem; + padding: var(--asc-spacing-s2) var(--asc-spacing-m1) var(--asc-spacing-s2) var(--asc-spacing-m1); +} + +.viewStoryContainer { + position: relative; + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + background-color: var(--asc-color-black); +} + +.viewStoryHeaderContainer { + z-index: 9999; + position: absolute; + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + flex-direction: column; + padding: var(--asc-spacing-m3) var(--asc-spacing-m1) var(--asc-spacing-s2) var(--asc-spacing-m1); + gap: var(--asc-spacing-s1); +} + +.avatarContainer { + position: relative; + width: 2.5rem; + height: 2.5rem; + border-radius: 50%; + flex-shrink: 0; +} + +.addStoryButton { + position: absolute; + bottom: 0; + right: 0; +} + +.addStoryButton:hover { + cursor: pointer; +} + +.viewStoryHeaderListActionsContainer { + display: flex; + gap: var(--asc-spacing-m2); + justify-content: flex-end; + align-items: center; +} + +.viewStoryHeadingInfoContainer { + display: flex; + justify-content: space-between; + width: 100%; + gap: var(--asc-spacing-s2); + align-items: center; +} + +.viewStoryHeading { + cursor: pointer; + display: flex; + gap: var(--asc-spacing-xxs2); + color: var(--asc-color-white); + font-size: var(--asc-text-font-size-sm); + font-style: normal; + font-weight: var(--asc-text-font-weight-bold); + line-height: var(--asc-line-height-md); + letter-spacing: -0.24px; + margin-right: var(--asc-spacing-xxs2); + align-items: center; +} + +.viewStoryHeadingTitle { + width: auto; + max-width: 11.688rem; +} + +.viewStorySubHeading { + display: inline-flex; + gap: var(--asc-spacing-xxs2); + margin-bottom: var(--asc-spacing-xxs2); + color: var(--asc-color-white); + font-size: var(--asc-text-font-size-xs); + font-style: normal; + font-weight: var(--asc-text-font-weight-normal); + line-height: var(--asc-line-height-md); + letter-spacing: -0.1px; +} + +.story { + display: flex; + position: relative; + overflow: hidden; +} + +.storyContent { + width: auto; + max-width: 100%; + max-height: 100%; + margin: auto; + flex: 1; +} + +.actionButton { + display: flex; + justify-content: flex-start; + align-items: center; + text-align: left; + gap: var(--asc-spacing-s1); + border: none; + color: var(--asc-color-base-default); + background-color: var(--asc-color-background-default); + padding: var(--asc-spacing-m1) var(--asc-spacing-m2); + cursor: pointer; + border-radius: var(--asc-border-radius-sm); +} + +.actionButton:hover { + background-color: var(--asc-color-base-shade4); +} + +.navigationOverlay { + position: absolute; + top: 0; + bottom: 0; + width: 50%; + z-index: 10; +} + +.leftOverlay { + left: 0; +} + +.rightOverlay { + right: 0; +} + +.imageFit { + object-fit: contain; +} + +.imageFill { + object-fit: cover; +} + +.infoIcon__button { + z-index: 99999; + position: absolute; + width: 1rem; + height: 1rem; + right: 0.25rem; + bottom: 0.25rem; + cursor: pointer; +} + +.infoIcon { + fill: var(--asc-color-base-shade3); +} + +.storyAd__topBar { + display: grid; + align-items: center; + grid-template-columns: minmax(0, 1fr) min-content; + gap: 0.5rem; + z-index: 99999; + position: absolute; + top: 1.25rem; + width: 100%; + padding-left: 1rem; + padding-right: 1rem; + height: 2.5rem; +} + +.storyAd__topBar__left { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.storyAd__topBar__avatar { + width: 2.5rem; + height: 2.5rem; +} + +.storyAd__topBar__text { + display: flex; + align-items: start; + flex-direction: column; + color: var(--asc-color-base-inverse); + flex-shrink: 1; + overflow: hidden; +} + +.storyAd__topBar__advertiserName { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: var(--asc-color-white); +} + +.storyAd__topBar__right { + display: flex; + align-items: center; + gap: 1.25rem; +} + +.storyAd__closeButton { + fill: var(--asc-color-white); + width: 1.25rem; + height: 1.25rem; + cursor: pointer; +} diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/storyAd.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/storyAd.tsx new file mode 100644 index 000000000..79ee12983 --- /dev/null +++ b/src/v4/social/internal-components/StoryViewer/Renderers/storyAd.tsx @@ -0,0 +1,186 @@ +import React, { useState, useEffect, useCallback, useRef } from 'react'; +import { + CustomRenderer, + Tester, +} from '~/v4/social/internal-components/StoryViewer/Renderers/types'; +import { Button } from '~/v4/core/natives/Button'; + +import styles from './storyAd.module.css'; +import clsx from 'clsx'; + +import { StoryProgressBar } from '~/v4/social/elements/StoryProgressBar/StoryProgressBar'; +import { StoryAdInformation } from '../../StoryAdInformation/StoryAdInformation'; +import InfoCircle from '~/v4/icons/InfoCircle'; +import { AdsBadge } from '../../AdsBadge/AdsBadge'; +import { Avatar, Typography } from '~/v4/core/components/index'; +import Broadcast from '~/v4/icons/Broadcast'; +import useImage from '~/v4/core/hooks/useImage'; +import { PauseIcon, PlayIcon } from '~/icons/index'; +import { CloseButton } from '~/v4/social/elements/index'; + +export const renderer: CustomRenderer = ({ story, action, config, onClose }) => { + const [loaded, setLoaded] = useState(false); + const [isAdvertisementInfoOpen, setIsAdvertisementInfoOpen] = useState(false); + const [isPaused, setIsPaused] = useState(false); + const { loader } = config; + const imageRef = useRef<HTMLImageElement>(null); + const targetRef = useRef<HTMLDivElement>(null); + + const { ad, currentIndex, storiesCount, increaseIndex, pageId, dragEventTarget } = story; + + const play = () => { + action('play', true); + setIsPaused(false); + }; + const pause = () => { + action('pause', true); + setIsPaused(true); + }; + + const avatarFile = useImage({ fileId: ad?.advertiser?.avatar?.fileId }); + const avatarUrl = avatarFile || ad?.advertiser?.avatar?.fileUrl || ''; + + const adImageFile = useImage({ fileId: ad?.image9_16?.fileId }); + const adImageUrl = adImageFile || ad?.image9_16?.fileUrl || ''; + + const imageLoaded = useCallback(() => { + setLoaded(true); + if (isPaused) { + setIsPaused(false); + } + action('play', true); + }, [action, isPaused]); + + const openAdvertisementInfo = () => { + action('pause', true); + setIsAdvertisementInfoOpen(true); + }; + const closeAdvertisementInfo = () => { + action('play', true); + setIsAdvertisementInfoOpen(false); + }; + + const handleProgressComplete = () => { + increaseIndex(); + }; + + useEffect(() => { + if (dragEventTarget) { + const handleDragStart = () => { + action('pause', true); + setIsPaused(true); + }; + const handleDragEnd = () => { + action('play', true); + setIsPaused(false); + }; + + dragEventTarget.current?.addEventListener('dragstart', handleDragStart); + dragEventTarget.current?.addEventListener('dragend', handleDragEnd); + + return () => { + dragEventTarget.current?.removeEventListener('dragstart', handleDragStart); + dragEventTarget.current?.removeEventListener('dragend', handleDragEnd); + }; + } + }, [dragEventTarget]); + + if (!ad) { + return null; + } + + return ( + <div className={styles.rendererContainer} ref={targetRef}> + <StoryProgressBar + pageId={pageId} + duration={5000} + currentIndex={currentIndex} + storiesCount={storiesCount} + isPaused={isPaused || isAdvertisementInfoOpen} + onComplete={handleProgressComplete} + /> + + <div className={styles.storyAd__topBar}> + <div className={styles.storyAd__topBar__left}> + <div className={styles.storyAd__topBar__avatar}> + <Avatar defaultImage={<Broadcast />} avatarUrl={avatarUrl} /> + </div> + <div className={styles.storyAd__topBar__text}> + <Typography.BodyBold className={styles.storyAd__topBar__advertiserName}> + {ad.advertiser?.name} + </Typography.BodyBold> + <AdsBadge /> + </div> + </div> + <div className={styles.storyAd__topBar__right}> + {isPaused ? ( + <PlayIcon + className={styles.playStoryButton} + data-qa-anchor="play_button" + onClick={play} + /> + ) : ( + <PauseIcon + className={styles.pauseStoryButton} + data-qa-anchor="pause_button" + onClick={pause} + /> + )} + <CloseButton + defaultClassName={clsx(styles.storyAd__closeButton)} + pageId={pageId} + onPress={onClose} + /> + </div> + </div> + + <div className={clsx(styles.storyImageContainer)}> + <img + ref={imageRef} + className={clsx(styles.storyImage)} + data-qa-anchor="image_view" + src={adImageUrl} + onLoad={imageLoaded} + alt="Story Image" + crossOrigin="anonymous" + /> + </div> + + {!loaded && <div className={styles.loadingOverlay}>{loader || <div>loading...</div>}</div>} + + {ad.callToActionUrl && ( + <div className={styles.hyperLinkContainer}> + <a + className={styles.hyperLink__text} + href={ad.callToActionUrl} + target="_blank" + rel="noreferrer" + > + {ad.callToAction} + </a> + </div> + )} + <Button className={styles.infoIcon__button} onPress={() => openAdvertisementInfo()}> + <InfoCircle className={styles.infoIcon} /> + </Button> + <StoryAdInformation + ad={ad} + isOpen={isAdvertisementInfoOpen} + targetRef={targetRef} + onOpenChange={(open) => !open && closeAdvertisementInfo()} + /> + </div> + ); +}; + +export const tester: Tester = (story) => { + return { + condition: !!story.ad, + priority: 2, + }; +}; + +export default { + renderer, + tester, +}; diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/types.ts b/src/v4/social/internal-components/StoryViewer/Renderers/types.ts index 8a24f7fcd..440449600 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/types.ts +++ b/src/v4/social/internal-components/StoryViewer/Renderers/types.ts @@ -8,31 +8,32 @@ export type RendererObject = NonNullable<StoriesProps['renderers']>[number]; export type RendererProps = React.ComponentProps<RendererObject['renderer']>; export type Renderer = RendererObject['renderer']; -export type Tester = RendererObject['tester']; +type TesterProp = RendererObject['tester']; + +export type Tester = (story: CustomStory) => ReturnType<TesterProp>; type Action = RendererProps['action']; type Story = RendererProps['story']; -export type CustomStory = Story & - Amity.Story & { - actions: Array<{ - name: string; - action: () => void; - icon: JSX.Element; - }>; - onChange: (file: File) => void; - handleAddIconClick: (e: React.MouseEvent<Element, MouseEvent>) => void; - addStoryButton: JSX.Element; - fileInputRef: React.RefObject<HTMLInputElement>; - storyStyles: { - background: string; - }; - currentIndex: number; - storiesCount: number; - increaseIndex: () => void; - pageId?: string; - dragEventTarget?: React.RefObject<HTMLElement>; +export type CustomStory = Story & { story?: Amity.Story; ad?: Amity.Ad } & { + actions: Array<{ + name: string; + action: () => void; + icon: JSX.Element; + }>; + onChange: (file: File) => void; + handleAddIconClick: (e: React.MouseEvent<Element, MouseEvent>) => void; + addStoryButton: JSX.Element; + fileInputRef: React.RefObject<HTMLInputElement>; + storyStyles: { + background: string; }; + currentIndex: number; + storiesCount: number; + increaseIndex: () => void; + pageId?: string; + dragEventTarget?: React.RefObject<HTMLElement>; +}; export type CustomRendererProps = RendererProps & { story: CustomStory; diff --git a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx index 9cb050167..4fb0def54 100644 --- a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx @@ -27,6 +27,9 @@ import styles from './StoryPage.module.css'; const DURATION = 5000; +const isStory = (story: Amity.Story | Amity.Ad): story is Amity.Story => + !!(story as Amity.Story)?.storyId; + interface GlobalFeedStoryProps { pageId?: string; targetId: string; @@ -95,8 +98,14 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ [renderers, onClose, onSwipeDown, onClickCommunity, targetId], ); - const isStoryCreator = stories[currentIndex]?.creator?.userId === currentUserId; - const isModerator = checkStoryPermission(client, stories[currentIndex]?.targetId); + const currentStory = stories[currentIndex]; + + const isStoryCreator = isStory(currentStory) + ? currentStory?.creator?.userId === currentUserId + : false; + const isModerator = isStory(currentStory) + ? checkStoryPermission(client, currentStory?.targetId) + : false; const previousStory = () => { if (currentIndex === 0) { @@ -218,31 +227,42 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ }; const formattedStories = stories?.map((story) => { - const isImage = story?.dataType === 'image'; - const url = isImage ? story?.imageData?.fileUrl : story?.videoData?.videoUrl?.['720p']; - - return { - ...story, - url, - type: isImage ? 'image' : 'video', - actions: [ - isStoryCreator || isModerator - ? { - name: 'Delete', - action: () => deleteStory(story?.storyId as string), - icon: <TrashIcon className={styles.deleteIcon} />, - } - : null, - ].filter(isNonNullable), - onCreateStory, - discardStory, - addStoryButton, - fileInputRef, - currentIndex, - storiesCount: stories?.length, - increaseIndex, - pageId, - }; + if (isStory(story)) { + const isImage = story?.dataType === 'image'; + const url = isImage ? story?.imageData?.fileUrl : story?.videoData?.videoUrl?.['720p']; + + return { + story, + url, + type: isImage ? 'image' : 'video', + actions: [ + isStoryCreator || isModerator + ? { + name: 'Delete', + action: () => deleteStory(story?.storyId as string), + icon: <TrashIcon className={styles.deleteIcon} />, + } + : null, + ].filter(isNonNullable), + onCreateStory, + discardStory, + addStoryButton, + fileInputRef, + currentIndex, + storiesCount: stories?.length, + increaseIndex, + pageId, + }; + } else { + return { + ad: story, + actions: [], + pageId, + currentIndex, + storiesCount: stories?.length, + increaseIndex, + }; + } }); const nextStory = () => { @@ -265,17 +285,18 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ const targetRootId = 'asc-uikit-stories-viewer'; useEffect(() => { - if (stories[stories.length - 1]?.syncState === 'syncing') { + const lastStory = stories[stories.length - 1]; + if (isStory(lastStory) && lastStory?.syncState === 'syncing') { setCurrentIndex(stories.length - 1); } - if (stories[currentIndex]) { - stories[currentIndex]?.analytics.markAsSeen(); + if (currentStory && isStory(currentStory)) { + currentStory?.analytics.markAsSeen(); } - }, [currentIndex, stories]); + }, [currentStory, stories]); useEffect(() => { - if (stories.every((story) => story?.isSeen)) return; - const firstUnseenStoryIndex = stories.findIndex((story) => !story?.isSeen); + if (stories.filter(isStory).every((story) => story?.isSeen)) return; + const firstUnseenStoryIndex = stories.filter(isStory).findIndex((story) => !story?.isSeen); if (firstUnseenStoryIndex !== -1) { setCurrentIndex(firstUnseenStoryIndex); @@ -347,7 +368,7 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ stories={formattedStories} renderers={globalFeedRenderers as RendererObject[]} defaultInterval={DURATION} - onStoryStart={() => stories[currentIndex]?.analytics.markAsSeen()} + onStoryStart={() => isStory(currentStory) && currentStory?.analytics.markAsSeen()} onStoryEnd={increaseIndex} onNext={nextStory} onPrevious={previousStory} From 1cb6ea65f85bdd7aafb601c972e9059ca33b3fc9 Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Wed, 10 Jul 2024 22:48:37 +0700 Subject: [PATCH 220/300] feat: ASC-23312 - support image video upload (#506) * feat: upload images * feat: handle failed * feat: add upload image * feat: upload video * feat: prevent more than 10 uploads * feat: handle error * feat: apply detail media attachment * fix: resolution thumbnail * feat: camera upload * fix: remove not used file * fix: remove log * style: data-attribute * refactor: isMobile utils * fix: params * refactor: onChange --- .../DetailedMediaAttachment.tsx | 50 +++- .../MediaAttachment.module.css | 5 + .../MediaAttachment/MediaAttachment.tsx | 58 ++++- .../CameraButton/CameraButton.module.css | 5 + .../elements/CameraButton/CameraButton.tsx | 167 +++++++++++- .../ImageButton/ImageButton.module.css | 5 + .../elements/ImageButton/ImageButton.tsx | 58 ++++- .../elements/PostTextField/PostTextField.tsx | 40 +-- .../VideoButton/VideoButton.module.css | 5 + .../elements/VideoButton/VideoButton.tsx | 94 ++++++- src/v4/social/hooks/useFileUpload.ts | 123 +++++++++ .../ImageThumbnail/ImageThumbnail.module.css | 60 +++++ .../ImageThumbnail/ImageThumbnail.tsx | 79 ++++++ .../ImageThumbnail/index.tsx | 1 + .../VideoThumbnail/VideoThumbnail.module.css | 60 +++++ .../VideoThumbnail/VideoThumbnail.tsx | 87 +++++++ .../VideoThumbnail/index.tsx | 1 + .../PostComposerPage/PostComposerPage.tsx | 238 +++++++++++++++--- src/v4/social/utils/isMobile.ts | 4 + 19 files changed, 1056 insertions(+), 84 deletions(-) create mode 100644 src/v4/social/hooks/useFileUpload.ts create mode 100644 src/v4/social/internal-components/ImageThumbnail/ImageThumbnail.module.css create mode 100644 src/v4/social/internal-components/ImageThumbnail/ImageThumbnail.tsx create mode 100644 src/v4/social/internal-components/ImageThumbnail/index.tsx create mode 100644 src/v4/social/internal-components/VideoThumbnail/VideoThumbnail.module.css create mode 100644 src/v4/social/internal-components/VideoThumbnail/VideoThumbnail.tsx create mode 100644 src/v4/social/internal-components/VideoThumbnail/index.tsx create mode 100644 src/v4/social/utils/isMobile.ts diff --git a/src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.tsx b/src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.tsx index 9163482f9..9c0b66042 100644 --- a/src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.tsx +++ b/src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.tsx @@ -8,9 +8,29 @@ import { FileButton } from '~/v4/social/elements/FileButton'; interface DetailedMediaAttachmentProps { pageId: string; + uploadLoading?: boolean; + onChangeImages?: (files: File[]) => void; + onChangeVideos?: (files: File[]) => void; + onChangeThumbnail?: ( + thumbnail: { file: File; videoUrl: string; thumbnail: string | undefined }[], + ) => void; + isVisibleCamera: boolean; + isVisibleImage: boolean; + isVisibleVideo: boolean; + videoThumbnail?: { file: File; videoUrl: string; thumbnail: string | undefined }[]; } -export function DetailedMediaAttachment({ pageId }: DetailedMediaAttachmentProps) { +export function DetailedMediaAttachment({ + pageId, + uploadLoading, + onChangeImages, + onChangeVideos, + onChangeThumbnail, + isVisibleCamera, + isVisibleImage, + isVisibleVideo, + videoThumbnail, +}: DetailedMediaAttachmentProps) { const componentId = 'detailed_media_attachment'; const { themeStyles, accessibilityId, isExcluded } = useAmityComponent({ pageId, componentId }); @@ -23,9 +43,31 @@ export function DetailedMediaAttachment({ pageId }: DetailedMediaAttachmentProps className={styles.detailedMediaAttachment} > <div className={styles.detailedMediaAttachment__swipeDown} /> - <CameraButton pageId={pageId} componentId={componentId} /> - <ImageButton pageId={pageId} componentId={componentId} /> - <VideoButton pageId={pageId} componentId={componentId} /> + {isVisibleCamera && ( + <CameraButton + pageId={pageId} + componentId={componentId} + onChange={onChangeImages} + isVisibleImage={isVisibleImage} + isVisibleVideo={isVisibleVideo} + onChangeVideos={onChangeVideos} + videoThumbnail={videoThumbnail} + onChangeThumbnail={onChangeThumbnail} + /> + )} + {isVisibleImage && ( + <ImageButton pageId={pageId} componentId={componentId} onChange={onChangeImages} /> + )} + + {isVisibleVideo && ( + <VideoButton + pageId={pageId} + componentId={componentId} + onChangeVideos={onChangeVideos} + onChangeThumbnail={onChangeThumbnail} + videoThumbnail={videoThumbnail} + /> + )} </div> ); } diff --git a/src/v4/social/components/MediaAttachment/MediaAttachment.module.css b/src/v4/social/components/MediaAttachment/MediaAttachment.module.css index 96fb48dc7..074bf56b4 100644 --- a/src/v4/social/components/MediaAttachment/MediaAttachment.module.css +++ b/src/v4/social/components/MediaAttachment/MediaAttachment.module.css @@ -25,3 +25,8 @@ display: flex; justify-content: space-between; } + +.mediaAttachment__wrapMedia_2items { + display: flex; + justify-content: space-around; +} diff --git a/src/v4/social/components/MediaAttachment/MediaAttachment.tsx b/src/v4/social/components/MediaAttachment/MediaAttachment.tsx index f249b9a58..402fc47e2 100644 --- a/src/v4/social/components/MediaAttachment/MediaAttachment.tsx +++ b/src/v4/social/components/MediaAttachment/MediaAttachment.tsx @@ -4,13 +4,31 @@ import { useAmityComponent } from '~/v4/core/hooks/uikit'; import { CameraButton } from '~/v4/social/elements/CameraButton'; import { ImageButton } from '~/v4/social/elements/ImageButton'; import { VideoButton } from '~/v4/social/elements/VideoButton'; -import { FileButton } from '~/v4/social/elements/FileButton'; +import clsx from 'clsx'; interface MediaAttachmentProps { pageId: string; + uploadLoading?: boolean; + onChangeImages?: (files: File[]) => void; + onChangeVideos?: (files: File[]) => void; + onChangeThumbnail?: (thumbnail: { file: File; videoUrl: string; thumbnail: string | undefined }[]) => void; + isVisibleCamera: boolean; + isVisibleImage: boolean; + isVisibleVideo: boolean; + videoThumbnail?: { file: File; videoUrl: string; thumbnail: string | undefined }[]; } -export function MediaAttachment({ pageId }: MediaAttachmentProps) { +export function MediaAttachment({ + pageId, + uploadLoading, + onChangeImages, + onChangeVideos, + onChangeThumbnail, + isVisibleCamera, + isVisibleImage, + isVisibleVideo, + videoThumbnail, +}: MediaAttachmentProps) { const componentId = 'media_attachment'; const { themeStyles, accessibilityId, isExcluded } = useAmityComponent({ pageId, componentId }); @@ -19,10 +37,38 @@ export function MediaAttachment({ pageId }: MediaAttachmentProps) { return ( <div style={themeStyles} data-qa-anchor={accessibilityId} className={styles.mediaAttachment}> <div className={styles.mediaAttachment__swipeDown} /> - <div className={styles.mediaAttachment__wrapMedia}> - <CameraButton pageId={pageId} componentId={componentId} /> - <ImageButton pageId={pageId} componentId={componentId} /> - <VideoButton pageId={pageId} componentId={componentId} /> + <div + className={clsx( + !isVisibleImage || !isVisibleVideo || !isVisibleCamera + ? styles.mediaAttachment__wrapMedia_2items + : styles.mediaAttachment__wrapMedia, + )} + > + {isVisibleCamera && ( + <CameraButton + pageId={pageId} + componentId={componentId} + onChange={onChangeImages} + isVisibleImage={isVisibleImage} + isVisibleVideo={isVisibleVideo} + onChangeVideos={onChangeVideos} + videoThumbnail={videoThumbnail} + onChangeThumbnail={onChangeThumbnail} + /> + )} + {isVisibleImage && ( + <ImageButton pageId={pageId} componentId={componentId} onChange={onChangeImages} /> + )} + + {isVisibleVideo && ( + <VideoButton + pageId={pageId} + componentId={componentId} + onChangeVideos={onChangeVideos} + onChangeThumbnail={onChangeThumbnail} + videoThumbnail={videoThumbnail} + /> + )} </div> </div> ); diff --git a/src/v4/social/elements/CameraButton/CameraButton.module.css b/src/v4/social/elements/CameraButton/CameraButton.module.css index e70fb016c..207f4ba28 100644 --- a/src/v4/social/elements/CameraButton/CameraButton.module.css +++ b/src/v4/social/elements/CameraButton/CameraButton.module.css @@ -3,6 +3,7 @@ align-items: center; padding: 0.75rem 1rem; gap: 0.75rem; + cursor: pointer; } .cameraButton__icon { @@ -13,3 +14,7 @@ border-radius: 50%; padding: 0.25rem; } + +.cameraButton_input { + display: none; +} diff --git a/src/v4/social/elements/CameraButton/CameraButton.tsx b/src/v4/social/elements/CameraButton/CameraButton.tsx index 40021cc6b..9023cddfe 100644 --- a/src/v4/social/elements/CameraButton/CameraButton.tsx +++ b/src/v4/social/elements/CameraButton/CameraButton.tsx @@ -1,16 +1,25 @@ -import React from 'react'; +import React, { useCallback, useState } from 'react'; import { IconComponent } from '~/v4/core/IconComponent'; -import { Typography } from '~/v4/core/components'; +import { Typography } from '~/v4/core/components'; import { useAmityElement } from '~/v4/core/hooks/uikit'; import styles from './CameraButton.module.css'; import clsx from 'clsx'; +import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; +import { Button } from '~/v4/core/natives/Button'; interface CameraButtonProps { pageId: string; componentId?: string; imgIconClassName?: string; defaultIconClassName?: string; - onClick?: (e: React.MouseEvent) => void; + onChange?: (files: File[]) => void; + isVisibleImage?: boolean; + isVisibleVideo?: boolean; + onChangeVideos?: (files: File[]) => void; + onChangeThumbnail?: ( + thumbnail: { file: File; videoUrl: string; thumbnail: string | undefined }[], + ) => void; + videoThumbnail?: { file: File; videoUrl: string; thumbnail: string | undefined }[]; } const CameraSvg = (props: React.SVGProps<SVGSVGElement>) => { @@ -46,20 +55,134 @@ export function CameraButton({ componentId = '*', imgIconClassName, defaultIconClassName, - onClick, + onChange, + isVisibleImage, + isVisibleVideo, + onChangeVideos, + videoThumbnail, + onChangeThumbnail, }: CameraButtonProps) { const elementId = 'camera_button'; const { themeStyles, isExcluded, config, accessibilityId, uiReference, defaultConfig } = useAmityElement({ pageId, componentId, elementId }); if (isExcluded) return null; + const [uploadedImages, setUploadedImages] = useState<File[]>([]); + const { confirm } = useConfirmContext(); + + const triggerFileInput = () => { + const fileInput = document.getElementById('upload') as HTMLInputElement; + fileInput.click(); + }; + + const onLoadVideo: React.ChangeEventHandler<HTMLInputElement> = useCallback( + (e) => { + e.preventDefault(); + e.stopPropagation(); + const targetFiles = e.target.files ? [...e.target.files] : []; + + const existingVideosCount = videoThumbnail ? videoThumbnail.length : 0; + + if (targetFiles.length + existingVideosCount > 10) { + confirm({ + pageId: pageId, + type: 'info', + title: 'Maximum upload limit reached', + content: + 'You’ve reached the upload limit of 10 videos. Any additional videos will not be saved. ', + okText: 'Close', + }); + return; + } + + if (targetFiles.length) { + onChangeVideos?.(targetFiles); + const updatedVideos = targetFiles.map((file) => ({ + file, + videoUrl: URL.createObjectURL(file), + thumbnail: null, + })); + onChangeThumbnail?.((prevVideos) => [...prevVideos, ...updatedVideos]); + videoThumbnail && + updatedVideos.forEach((video, index) => + generateThumbnail(video.file, index + videoThumbnail.length), + ); + } + }, + [onChangeVideos, videoThumbnail?.length, onChangeThumbnail], + ); + + const onLoadImage: React.ChangeEventHandler<HTMLInputElement> = useCallback( + (e) => { + e.preventDefault(); + e.stopPropagation(); + const targetFiles = e.target.files ? [...e.target.files] : []; + + if (targetFiles.length + uploadedImages.length > 10) { + confirm({ + pageId: pageId, + type: 'info', + title: 'Maximum upload limit reached', + content: + 'You’ve reached the upload limit of 10 images. Any additional images will not be saved. ', + okText: 'Close', + }); + return; + } + + if (targetFiles.length) { + setUploadedImages((prevImages) => [...prevImages, ...targetFiles]); + onChange?.(targetFiles); + } + }, + [onChange], + ); + + const onLoadMedia: React.ChangeEventHandler<HTMLInputElement> = useCallback( + (e) => { + e.preventDefault(); + e.stopPropagation(); + const targetFiles = e.target.files ? [...e.target.files] : []; + const isImage = targetFiles.some((file) => file.type.startsWith('image/')); + const isVideo = targetFiles.some((file) => file.type.startsWith('video/')); + + if (isImage) { + onLoadImage(e); + } else if (isVideo) { + onLoadVideo(e); + } + }, + [onLoadImage, onLoadVideo], + ); + + const generateThumbnail = (file: File, index: number) => { + const videoElement = document.createElement('video'); + const canvasElement = document.createElement('canvas'); + const context = canvasElement.getContext('2d'); + + videoElement.src = URL.createObjectURL(file); + videoElement.currentTime = 10; // Seek to 10 seconds (you can adjust this) + + videoElement.addEventListener('loadeddata', () => { + videoElement.pause(); + canvasElement.width = videoElement.videoWidth; + canvasElement.height = videoElement.videoHeight; + context?.drawImage(videoElement, 0, 0, canvasElement.width, canvasElement.height); + const thumbnail = canvasElement.toDataURL('image/png'); + onChangeThumbnail?.((prevVideos) => { + const newVideos = [...prevVideos]; + newVideos[index].thumbnail = thumbnail; + return newVideos; + }); + }); + }; return ( - <div + <Button style={themeStyles} data-qa-anchor={accessibilityId} className={styles.cameraButton} - onClick={() => {}} + onPress={triggerFileInput} > <IconComponent defaultIcon={() => ( @@ -70,6 +193,36 @@ export function CameraButton({ configIconName={config.icon} /> {config.text && <Typography.BodyBold>{config.text}</Typography.BodyBold>} - </div> + + {isVisibleImage && !isVisibleVideo && ( + <input + type="file" + onChange={onLoadImage} + id="upload" + accept="image/*" + className={styles.cameraButton_input} + /> + )} + + {!isVisibleImage && isVisibleVideo && ( + <input + type="file" + onChange={onLoadVideo} + id="upload" + accept="video/*" + className={styles.cameraButton_input} + /> + )} + + {isVisibleVideo && isVisibleVideo && ( + <input + type="file" + onChange={onLoadMedia} + id="upload" + accept="image/*,video/*" + className={styles.cameraButton_input} + /> + )} + </Button> ); } diff --git a/src/v4/social/elements/ImageButton/ImageButton.module.css b/src/v4/social/elements/ImageButton/ImageButton.module.css index d37774386..73f1e5b98 100644 --- a/src/v4/social/elements/ImageButton/ImageButton.module.css +++ b/src/v4/social/elements/ImageButton/ImageButton.module.css @@ -3,6 +3,7 @@ align-items: center; padding: 0.75rem 1rem; gap: 0.75rem; + cursor: pointer; } .imageButton__icon { @@ -13,3 +14,7 @@ border-radius: 50%; padding: 0.25rem; } + +.imageButton_input { + display: none; +} diff --git a/src/v4/social/elements/ImageButton/ImageButton.tsx b/src/v4/social/elements/ImageButton/ImageButton.tsx index c26c04d3f..7079ec33e 100644 --- a/src/v4/social/elements/ImageButton/ImageButton.tsx +++ b/src/v4/social/elements/ImageButton/ImageButton.tsx @@ -1,16 +1,18 @@ -import React from 'react'; +import React, { useCallback, useState } from 'react'; import { IconComponent } from '~/v4/core/IconComponent'; import { Typography } from '~/v4/core/components'; import { useAmityElement } from '~/v4/core/hooks/uikit'; import styles from './ImageButton.module.css'; import clsx from 'clsx'; +import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; +import { Button } from '~/v4/core/natives/Button'; interface ImageButtonProps { pageId: string; componentId?: string; imgIconClassName?: string; defaultIconClassName?: string; - onClick?: (e: React.MouseEvent) => void; + onChange?: (files: File[]) => void; } const ImageButtonSvg = (props: React.SVGProps<SVGSVGElement>) => { @@ -47,20 +49,56 @@ export function ImageButton({ componentId = '*', imgIconClassName, defaultIconClassName, - onClick, + onChange, }: ImageButtonProps) { const elementId = 'image_button'; const { themeStyles, isExcluded, config, accessibilityId, uiReference, defaultConfig } = useAmityElement({ pageId, componentId, elementId }); + const [uploadedImages, setUploadedImages] = useState<File[]>([]); + + const { confirm } = useConfirmContext(); + if (isExcluded) return null; + const triggerFileInput = () => { + const fileInput = document.getElementById('image-upload') as HTMLInputElement; + fileInput.click(); + }; + + const onLoad: React.ChangeEventHandler<HTMLInputElement> = useCallback( + (e) => { + e.preventDefault(); + e.stopPropagation(); + const targetFiles = e.target.files ? [...e.target.files] : []; + + if (targetFiles.length + uploadedImages.length > 10) { + confirm({ + pageId: pageId, + type: 'info', + title: 'Maximum upload limit reached', + content: + 'You’ve reached the upload limit of 10 images. Any additional images will not be saved. ', + okText: 'Close', + }); + return; + } + + if (targetFiles.length) { + setUploadedImages((prevImages) => [...prevImages, ...targetFiles]); + onChange?.(targetFiles); + } + }, + [onChange], + ); + return ( - <div + <Button + type="button" style={themeStyles} data-qa-anchor={accessibilityId} className={styles.imageButton} - onClick={() => {}} + onPress={triggerFileInput} > <IconComponent defaultIcon={() => ( @@ -71,6 +109,14 @@ export function ImageButton({ configIconName={config.icon} /> {config.text && <Typography.BodyBold>{config.text}</Typography.BodyBold>} - </div> + <input + type="file" + onChange={onLoad} + multiple + id="image-upload" + accept="image/*" + className={styles.imageButton_input} + /> + </Button> ); } diff --git a/src/v4/social/elements/PostTextField/PostTextField.tsx b/src/v4/social/elements/PostTextField/PostTextField.tsx index af7e3f47d..026905b93 100644 --- a/src/v4/social/elements/PostTextField/PostTextField.tsx +++ b/src/v4/social/elements/PostTextField/PostTextField.tsx @@ -3,13 +3,13 @@ import { LexicalComposer } from '@lexical/react/LexicalComposer'; import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin'; import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'; import { ContentEditable } from '@lexical/react/LexicalContentEditable'; -import { $getRoot, EditorState, LexicalEditor, SerializedLexicalNode } from 'lexical'; +import { LexicalEditor, SerializedLexicalNode } from 'lexical'; import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary'; import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'; import { AutoFocusPlugin } from '@lexical/react/LexicalAutoFocusPlugin'; import { MentionNode } from '~/v4/social/internal-components/MentionTextInput/MentionNodes'; import { MentionTextInputPlugin } from '~/v4/social/internal-components/MentionTextInput/MentionTextInput'; -import { MetaData, CreatePostParams } from '~/v4/social/pages/PostComposerPage/PostComposerPage'; +import { CreatePostParams, MetaData } from '~/v4/social/pages/PostComposerPage/PostComposerPage'; import styles from './PostTextField.module.css'; const theme = { @@ -77,23 +77,25 @@ function editorStateToText(editor: LexicalEditor) { export const PostTextField = forwardRef<LexicalEditor, PostTextFieldProps>( ({ onChange, communityId }) => { return ( - <LexicalComposer initialConfig={editorConfig}> - <div className={styles.editorContainer}> - <RichTextPlugin - contentEditable={<ContentEditable />} - placeholder={<div className={styles.editorPlaceholder}>What’s going on...</div>} - ErrorBoundary={LexicalErrorBoundary} - /> - <OnChangePlugin - onChange={(editorState, editor) => { - onChange(editorStateToText(editor)); - }} - /> - <HistoryPlugin /> - <AutoFocusPlugin /> - <MentionTextInputPlugin communityId={communityId} /> - </div> - </LexicalComposer> + <> + <LexicalComposer initialConfig={editorConfig}> + <div className={styles.editorContainer}> + <RichTextPlugin + contentEditable={<ContentEditable />} + placeholder={<div className={styles.editorPlaceholder}>What’s going on...</div>} + ErrorBoundary={LexicalErrorBoundary} + /> + <OnChangePlugin + onChange={(editorState, editor) => { + onChange(editorStateToText(editor)); + }} + /> + <HistoryPlugin /> + <AutoFocusPlugin /> + <MentionTextInputPlugin communityId={communityId} /> + </div> + </LexicalComposer> + </> ); }, ); diff --git a/src/v4/social/elements/VideoButton/VideoButton.module.css b/src/v4/social/elements/VideoButton/VideoButton.module.css index d33d1a1f3..69be521a5 100644 --- a/src/v4/social/elements/VideoButton/VideoButton.module.css +++ b/src/v4/social/elements/VideoButton/VideoButton.module.css @@ -3,6 +3,7 @@ align-items: center; padding: 0.75rem 1rem; gap: 0.75rem; + cursor: pointer; } .videoButton__icon { @@ -13,3 +14,7 @@ border-radius: 50%; padding: 0.25rem; } + +.videoButton__input { + display: none; +} diff --git a/src/v4/social/elements/VideoButton/VideoButton.tsx b/src/v4/social/elements/VideoButton/VideoButton.tsx index b126e1227..85c7ef6e6 100644 --- a/src/v4/social/elements/VideoButton/VideoButton.tsx +++ b/src/v4/social/elements/VideoButton/VideoButton.tsx @@ -1,16 +1,22 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import { IconComponent } from '~/v4/core/IconComponent'; import { Typography } from '~/v4/core/components'; import { useAmityElement } from '~/v4/core/hooks/uikit'; import styles from './VideoButton.module.css'; import clsx from 'clsx'; +import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; +import { Button } from '~/v4/core/natives/Button'; interface VideoButtonProps { pageId: string; componentId?: string; imgIconClassName?: string; defaultIconClassName?: string; - onClick?: (e: React.MouseEvent) => void; + onChangeVideos?: (files: File[]) => void; + onChangeThumbnail?: ( + thumbnail: { file: File; videoUrl: string; thumbnail: string | undefined }[], + ) => void; + videoThumbnail?: { file: File; videoUrl: string; thumbnail: string | undefined }[]; } const VideoButtonSvg = (props: React.SVGProps<SVGSVGElement>) => { @@ -45,7 +51,9 @@ export function VideoButton({ componentId = '*', imgIconClassName, defaultIconClassName, - onClick, + onChangeVideos, + onChangeThumbnail, + videoThumbnail, }: VideoButtonProps) { const elementId = 'video_button'; const { themeStyles, isExcluded, config, accessibilityId, uiReference, defaultConfig } = @@ -53,12 +61,77 @@ export function VideoButton({ if (isExcluded) return null; + const { confirm } = useConfirmContext(); + + const triggerFileInput = () => { + const fileInput = document.getElementById('video-upload') as HTMLInputElement; + fileInput.click(); + }; + + const onLoad: React.ChangeEventHandler<HTMLInputElement> = useCallback( + (e) => { + e.preventDefault(); + e.stopPropagation(); + const targetFiles = e.target.files ? [...e.target.files] : []; + const existingVideosCount = videoThumbnail ? videoThumbnail.length : 0; + + if (targetFiles.length + existingVideosCount > 10) { + confirm({ + pageId: pageId, + type: 'info', + title: 'Maximum upload limit reached', + content: + 'You’ve reached the upload limit of 10 videos. Any additional videos will not be saved. ', + okText: 'Close', + }); + return; + } + + if (targetFiles.length) { + onChangeVideos?.(targetFiles); + const updatedVideos = targetFiles.map((file) => ({ + file, + videoUrl: URL.createObjectURL(file), + thumbnail: null, + })); + onChangeThumbnail?.((prevVideo) => [...prevVideo, ...updatedVideos]); + videoThumbnail && + updatedVideos.forEach((video, index) => + generateThumbnail(video.file, index + videoThumbnail.length), + ); + } + }, + [onChangeVideos, videoThumbnail?.length, onChangeThumbnail], + ); + + const generateThumbnail = (file: File, index: number) => { + const videoElement = document.createElement('video'); + const canvasElement = document.createElement('canvas'); + const context = canvasElement.getContext('2d'); + + videoElement.src = URL.createObjectURL(file); + videoElement.currentTime = 10; // Seek to 10 seconds (you can adjust this) + + videoElement.addEventListener('loadeddata', () => { + videoElement.pause(); + canvasElement.width = videoElement.videoWidth; + canvasElement.height = videoElement.videoHeight; + context?.drawImage(videoElement, 0, 0, canvasElement.width, canvasElement.height); + const thumbnail = canvasElement.toDataURL('image/png'); + onChangeThumbnail?.((prevVideos) => { + const newVideos = [...prevVideos]; + newVideos[index].thumbnail = thumbnail; + return newVideos; + }); + }); + }; + return ( - <div + <Button style={themeStyles} data-qa-anchor={accessibilityId} className={styles.videoButton} - onClick={() => {}} + onPress={triggerFileInput} > <IconComponent defaultIcon={() => ( @@ -69,6 +142,15 @@ export function VideoButton({ configIconName={config.icon} /> {config.text && <Typography.BodyBold>{config.text}</Typography.BodyBold>} - </div> + + <input + type="file" + accept="video/*" + onChange={onLoad} + multiple + id="video-upload" + className={styles.videoButton__input} + /> + </Button> ); } diff --git a/src/v4/social/hooks/useFileUpload.ts b/src/v4/social/hooks/useFileUpload.ts new file mode 100644 index 000000000..7adee79de --- /dev/null +++ b/src/v4/social/hooks/useFileUpload.ts @@ -0,0 +1,123 @@ +import { useState, useCallback, useEffect, useMemo } from 'react'; +import { FileRepository, FileType } from '@amityco/ts-sdk'; +import { isNonNullable } from '~/helpers/utils'; + +export function isAmityFile(file: Amity.File | File): file is Amity.File { + return (file as Amity.File).fileId !== undefined; +} + +export const getUpdatedTime = (file: File | Amity.File) => { + if (!isAmityFile(file)) return file.lastModified; + return file.updatedAt ? new Date(file.updatedAt).getTime() : Date.now(); +}; + +export default function useFileUpload({ + files = [], + uploadedFiles = [], + onChange = (data: { uploaded: Array<Amity.File>; uploading: Array<File> }) => {}, + onLoadingChange = (loading: boolean) => {}, + onError = (isError: boolean) => {}, +}: { + files?: Iterable<File> | Array<File>; + uploadedFiles: Amity.File[]; + onChange?: (data: { uploaded: Array<Amity.File>; uploading: Array<File> }) => void; + onLoadingChange?: (loading: boolean) => void; + onError?: (isError: boolean) => void; +}) { + const [rejected, setRejected] = useState<string[]>([]); // filenames that has loading error + + const fileList = useMemo(() => (Array.isArray(files) ? files : Array.from(files)), [files]); + + const reset = useCallback(() => { + onChange({ + uploaded: [], + uploading: [], + }); + }, [onChange]); + + const retry = useCallback(() => { + setRejected([]); + }, []); + + const removeFile = useCallback( + (file: Amity.File | File, index?: number) => { + if (isAmityFile(file)) { + const remaining = uploadedFiles.filter((item) => item.fileId !== file.fileId); + onChange({ + uploaded: remaining, + uploading: fileList, + }); + } else if (index) { + const remaining = uploadedFiles.filter((_, i) => i !== index); + + onChange({ + uploaded: remaining, + uploading: fileList, + }); + } else { + const remaining = fileList.filter((item) => item.name !== file.name); + onChange({ + uploaded: uploadedFiles, + uploading: remaining, + }); + } + }, + [onChange], + ); + + // file upload function + useEffect(() => { + if (!fileList.length) return; + + async function run() { + onLoadingChange(true); + try { + const updatedFiles = await Promise.all( + fileList.map(async (file: File) => { + const formData: FormData = new FormData(); + formData.append('files', file); + const uploadedFile = await (async () => { + if (file.type.includes(FileType.IMAGE)) { + return FileRepository.createImage(formData); + } else if (file.type.includes(FileType.VIDEO)) { + return FileRepository.createVideo(formData); + } + })(); + + if (uploadedFile && uploadedFile.data.length > 0) { + return uploadedFile.data[0]; + } + + return null; + }), + ); + + const updated = [...uploadedFiles, ...updatedFiles].filter(isNonNullable); + onChange({ + uploaded: updated, + uploading: [], + }); + setRejected([]); + } catch (e) { + setRejected(fileList.map((file) => file.name)); + onError(true); + } finally { + onLoadingChange(false); + } + } + + run(); + }, [fileList]); + + const allFiles = [...uploadedFiles, ...fileList]; + + return { + allFiles, + uploading: fileList, + uploaded: uploadedFiles, + removeFile, + reset, + rejected, + retry, + }; +} diff --git a/src/v4/social/internal-components/ImageThumbnail/ImageThumbnail.module.css b/src/v4/social/internal-components/ImageThumbnail/ImageThumbnail.module.css new file mode 100644 index 000000000..23f2ee62f --- /dev/null +++ b/src/v4/social/internal-components/ImageThumbnail/ImageThumbnail.module.css @@ -0,0 +1,60 @@ +.thumbnail__container { + max-width: 100%; + display: grid; + gap: 0.5rem; + padding: 0 1rem; + width: 100%; +} + +.thumbnail__container[data-images-amount='1'] { + grid-template-columns: repeat(1, 1fr); + height: 21rem; +} + +.thumbnail__container[data-images-amount='2'] { + grid-template-columns: repeat(2, 1fr); + height: 21rem; +} + +.thumbnail__container[data-images-amount='3'] { + grid-template-columns: repeat(3, 1fr); +} + +.thumbnail__wrapper { + position: relative; + text-align: center; + width: 100%; +} + +.thumbnail__wrapper[data-images-height='true'] { + height: 6.8rem; +} + +.thumbnail { + border-radius: 0.25rem; + width: 100%; + height: 100%; + object-fit: cover; +} + +.closeIcon { + position: absolute; + right: 0.5rem; + top: 0.5rem; + width: 1.5rem; + height: 1.5rem; + padding: 0.19rem; + background-color: rgb(0 0 0 / 50%); + fill: var(--asc-color-primary-shade4); + border-radius: 50%; +} + +.icon__status { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 1.75rem; + height: 1.75rem; + fill: var(--asc-color-primary-shade4); +} diff --git a/src/v4/social/internal-components/ImageThumbnail/ImageThumbnail.tsx b/src/v4/social/internal-components/ImageThumbnail/ImageThumbnail.tsx new file mode 100644 index 000000000..773a60f71 --- /dev/null +++ b/src/v4/social/internal-components/ImageThumbnail/ImageThumbnail.tsx @@ -0,0 +1,79 @@ +import React from 'react'; +import styles from './ImageThumbnail.module.css'; +import { CloseIcon, ExclamationCircle } from '~/icons'; +import { Spinner } from '~/v4/social/internal-components/Spinner'; +import clsx from 'clsx'; +import useFileUpload from '~/v4/social/hooks/useFileUpload'; +import { FileRepository } from '@amityco/ts-sdk'; + +interface ImageThumbnailProps { + files: File[]; + uploadedFiles: Amity.File[]; + onChange: (data: { uploaded: Array<Amity.File>; uploading: Array<File> }) => void; + onLoadingChange: (loading: boolean) => void; + uploadLoading: boolean; + onError: (isError: boolean) => void; + isErrorUpload?: boolean; +} + +export function ImageThumbnail({ + files, + uploadedFiles, + onChange, + onLoadingChange, + uploadLoading, + onError, + isErrorUpload, +}: ImageThumbnailProps) { + // Images/files incoming from uploads. + + const useFileUploadProps = useFileUpload({ + files, + uploadedFiles, + onChange, + onLoadingChange, + onError, + }); + + const { allFiles, removeFile } = useFileUploadProps; + + if (allFiles.length === 0) return null; + + return ( + allFiles && ( + <div + data-images-amount={Math.min(allFiles.length, 3)} + className={styles.thumbnail__container} + > + {allFiles?.map((file, index: number) => ( + <div + key={index} + data-images-height={allFiles.length > 2} + className={styles.thumbnail__wrapper} + > + {uploadLoading ? ( + <div className={styles.icon__status}> + <Spinner /> + </div> + ) : isErrorUpload ? ( + <div className={styles.icon__status}> + <ExclamationCircle /> + </div> + ) : ( + <> + <img + className={styles.thumbnail} + src={FileRepository.fileUrlWithSize((file as Amity.File)?.fileUrl, 'medium')} + alt={(file as Amity.File).attributes?.name} + /> + <button type="reset" onClick={() => removeFile(file)}> + <CloseIcon className={styles.closeIcon} /> + </button> + </> + )} + </div> + ))} + </div> + ) + ); +} diff --git a/src/v4/social/internal-components/ImageThumbnail/index.tsx b/src/v4/social/internal-components/ImageThumbnail/index.tsx new file mode 100644 index 000000000..39522bd22 --- /dev/null +++ b/src/v4/social/internal-components/ImageThumbnail/index.tsx @@ -0,0 +1 @@ +export { ImageThumbnail } from './ImageThumbnail'; diff --git a/src/v4/social/internal-components/VideoThumbnail/VideoThumbnail.module.css b/src/v4/social/internal-components/VideoThumbnail/VideoThumbnail.module.css new file mode 100644 index 000000000..0f704bd2e --- /dev/null +++ b/src/v4/social/internal-components/VideoThumbnail/VideoThumbnail.module.css @@ -0,0 +1,60 @@ +.thumbnail__container { + max-width: 100%; + display: grid; + gap: 0.5rem; + padding: 0 1rem; + width: 100%rem; +} + +.thumbnail__container[data-images-amount='1'] { + grid-template-columns: repeat(1, 1fr); + height: 21rem; +} + +.thumbnail__container[data-images-amount='2'] { + grid-template-columns: repeat(2, 1fr); + height: 21rem; +} + +.thumbnail__container[data-images-amount='3'] { + grid-template-columns: repeat(3, 1fr); +} + +.thumbnail__wrapper { + position: relative; + text-align: center; + width: 100%; +} + +.thumbnail__wrapper_item_3 { + height: 6.8rem; +} + +.thumbnail { + border-radius: 0.25rem; + width: 100%; + height: 100%; + object-fit: cover; +} + +.closeIcon { + position: absolute; + right: 0.5rem; + top: 0.5rem; + width: 1.5rem; + height: 1.5rem; + padding: 0.19rem; + background-color: rgb(0 0 0 / 50%); + fill: var(--asc-color-primary-shade4); + border-radius: 50%; +} + +.icon__status { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 1.75rem; + height: 1.75rem; + fill: var(--asc-color-primary-shade4); +} diff --git a/src/v4/social/internal-components/VideoThumbnail/VideoThumbnail.tsx b/src/v4/social/internal-components/VideoThumbnail/VideoThumbnail.tsx new file mode 100644 index 000000000..70606d9fd --- /dev/null +++ b/src/v4/social/internal-components/VideoThumbnail/VideoThumbnail.tsx @@ -0,0 +1,87 @@ +import React from 'react'; +import styles from './VideoThumbnail.module.css'; +import useFileUpload from '~/v4/social/hooks/useFileUpload'; +import clsx from 'clsx'; +import { CloseIcon, ExclamationCircle } from '~/icons'; +import { Spinner } from '~/v4/social/internal-components/Spinner'; + +interface VideoThumbnailProps { + files: File[]; + uploadedFiles: Amity.File[]; + onLoadingChange: (loading: boolean) => void; + onChange: (data: { uploaded: Array<Amity.File>; uploading: Array<File> }) => void; + uploadLoading: boolean; + onError: (isError: boolean) => void; + videoThumbnail?: { file: File; videoUrl: string; thumbnail: string | undefined }[]; + removeThumbnail: (index: number) => void; + isErrorUpload?: boolean; +} + +export const VideoThumbnail = ({ + files, + uploadedFiles, + onLoadingChange, + onChange, + uploadLoading, + onError, + videoThumbnail, + removeThumbnail, + isErrorUpload, +}: VideoThumbnailProps) => { + const useFileUploadProps = useFileUpload({ + files, + uploadedFiles, + onChange, + onLoadingChange, + onError, + }); + + const { allFiles, removeFile } = useFileUploadProps; + + if (allFiles.length === 0) return null; + + const handleRemoveThumbnail = (file: File, index: number) => { + removeFile(file, index); + removeThumbnail(index); + }; + + if (!videoThumbnail) return null; + return ( + <div + data-images-amount={Math.min(videoThumbnail.length, 3)} + className={styles.thumbnail__container} + > + {videoThumbnail?.map((file, index) => ( + <div + key={file.videoUrl} + className={clsx( + styles.thumbnail__wrapper, + videoThumbnail.length > 2 && styles.thumbnail__wrapper_item_3, + )} + > + {uploadLoading ? ( + <div className={styles.icon__status}> + <Spinner /> + </div> + ) : isErrorUpload ? ( + <div className={styles.icon__status}> + <ExclamationCircle /> + </div> + ) : ( + <> + <img className={styles.thumbnail} src={file.thumbnail} /> + <button + type="reset" + onClick={() => { + handleRemoveThumbnail(file.file, index); + }} + > + <CloseIcon className={styles.closeIcon} /> + </button> + </> + )} + </div> + ))} + </div> + ); +}; diff --git a/src/v4/social/internal-components/VideoThumbnail/index.tsx b/src/v4/social/internal-components/VideoThumbnail/index.tsx new file mode 100644 index 000000000..4d54e05a1 --- /dev/null +++ b/src/v4/social/internal-components/VideoThumbnail/index.tsx @@ -0,0 +1 @@ +export { VideoThumbnail } from './VideoThumbnail'; \ No newline at end of file diff --git a/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx b/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx index d2d8c93bf..5852ebb94 100644 --- a/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx +++ b/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx @@ -1,8 +1,7 @@ -import React, { useRef, useState } from 'react'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; import styles from './PostComposerPage.module.css'; import { useAmityPage } from '~/v4/core/hooks/uikit'; import { CloseButton } from '~/v4/social/elements/CloseButton/CloseButton'; -import { useNavigation } from '~/v4/core/providers/NavigationProvider'; import { CommunityDisplayName } from '~/v4/social/elements/CommunityDisplayName'; import { CreateNewPostButton } from '~/v4/social/elements/CreateNewPostButton'; import { PostTextField } from '~/v4/social/elements/PostTextField'; @@ -11,13 +10,19 @@ import { LexicalEditor } from 'lexical'; import { useMutation } from '@tanstack/react-query'; import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; import { Notification } from '~/v4/core/components/Notification'; -import { Spinner } from '../../internal-components/Spinner'; +import { Spinner } from '~/v4/social/internal-components/Spinner'; import ExclamationCircle from '~/v4/icons/ExclamationCircle'; import { useForm } from 'react-hook-form'; import { Mentioned } from '~/v4/helpers/utils'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; import useConnectionState from '~/v4/social/hooks/useConnectionState'; -import { usePostContext } from '~/v4/social/providers/PostProvider'; +import { Drawer } from 'vaul'; +import ReactDOM from 'react-dom'; +import { DetailedMediaAttachment } from '~/v4/social/components/DetailedMediaAttachment'; +import { ImageThumbnail } from '~/v4/social/internal-components/ImageThumbnail'; +import { MediaAttachment } from '~/v4/social/components/MediaAttachment'; +import { VideoThumbnail } from '~/v4/social/internal-components/VideoThumbnail'; +import { isMobile } from '~/v4/social/utils/isMobile'; export enum Mode { CREATE = 'create', @@ -25,12 +30,12 @@ export enum Mode { } interface AmityPostComposerEditOptions { mode: Mode.EDIT; - post: Amity.Post; + post?: Amity.Post; } interface AmityPostComposerCreateOptions { mode: Mode.CREATE; - targetId?: string | null; + targetId: string | null; targetType: 'community' | 'user'; community?: Amity.Community; } @@ -71,6 +76,10 @@ export type CreatePostParams = { type: string; userIds: string[]; }[]; + attachments: { + fileId: string; + type: string; + }[]; }; const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCreateOptions) => { @@ -79,11 +88,18 @@ const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCr pageId, }); + // const HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU = '290px'; //Including file button + const HEIGHT_MEDIA_ATTACHMENT_MENU = '92px'; + const HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU = '236px'; //Not including file button + const HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_2 = '176px'; //Show 2 menus + const editorRef = useRef<LexicalEditor | null>(null); const { AmityPostComposerPageBehavior } = usePageBehavior(); const { confirm } = useConfirmContext(); const isOnline = useConnectionState(); - const { setPost } = usePostContext(); + const [snap, setSnap] = useState<number | string | null>(HEIGHT_MEDIA_ATTACHMENT_MENU); + const [isShowBottomMenu] = useState<boolean>(true); + const drawerRef = useRef<HTMLDivElement>(null); const [textValue, setTextValue] = useState<CreatePostParams>({ text: '', @@ -94,19 +110,37 @@ const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCr userIds: [''], }, ], + attachments: [ + { + fileId: '', + type: 'image', + }, + ], }); + //Upload media + const [postImages, setPostImages] = useState<Amity.File[]>([]); + const [postVideos, setPostVideos] = useState<Amity.File[]>([]); + + // Images/files incoming from uploads. + const [incomingImages, setIncomingImages] = useState<File[]>([]); + const [incomingVideos, setIncomingVideos] = useState<File[]>([]); + const [uploadLoading, setUploadLoading] = useState(false); + + // Visible menu attachment + const [isVisibleCamera, setIsVisibleCamera] = useState(false); + const [isVisibleImage, setIsVisibleImage] = useState(true); + const [isVisibleVideo, setIsVisibleVideo] = useState(true); + + const [isErrorUpload, setIsErrorUpload] = useState(false); + const [videoThumbnail, setVideoThumbnail] = useState< + { file: File; videoUrl: string; thumbnail: string | undefined }[] + >([]); + const useMutateCreatePost = () => useMutation({ - mutationFn: async () => { - return PostRepository.createPost({ - targetId: targetId, - targetType: targetType, - data: { text: textValue.text }, - dataType: 'text', - metadata: { mentioned: textValue.mentioned }, - mentionees: textValue.mentionees, - }); + mutationFn: async (params: Parameters<typeof PostRepository.createPost>[0]) => { + return PostRepository.createPost(params); }, onSuccess: () => { AmityPostComposerPageBehavior.goToSocialHomePage(); @@ -116,20 +150,44 @@ const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCr }, }); - const { mutateAsync: mutateCreatePostAsync, isPending, isError, isSuccess } = useMutateCreatePost(); + const { mutateAsync: mutateCreatePostAsync, isPending, isError } = useMutateCreatePost(); const { handleSubmit } = useForm(); const onSubmit = () => { - mutateCreatePostAsync().then((response) => { - setPost(response.data); - }) + const attachmentsImage = postImages.map((file) => ({ + type: 'image', + fileId: file.fileId, + })); + const attachmentsVideo = postVideos.map((file) => ({ + type: 'video', + fileId: file.fileId, + })); + + const attachments = [...attachmentsImage, ...attachmentsVideo]; + + mutateCreatePostAsync({ + targetId: targetId, + targetType: targetType, + data: { text: textValue.text }, + dataType: 'text', + metadata: { mentioned: textValue.mentioned }, + mentionees: textValue.mentionees, + attachments: attachments, + }); }; const onChange = (val: CreatePostParams) => { setTextValue(val); }; + const handleSnapChange = (newSnap: string | number | null) => { + if (snap === HEIGHT_MEDIA_ATTACHMENT_MENU && newSnap === '0px') { + return; + } + setSnap(newSnap); + }; + const onClickClose = () => { confirm({ pageId: pageId, @@ -144,6 +202,38 @@ const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCr }); }; + useEffect(() => { + if (postImages.length > 0) { + setIsVisibleImage(true); + setIsVisibleVideo(false); + } else if (postVideos.length > 0 && videoThumbnail.length > 0) { + setIsVisibleImage(false); + setIsVisibleVideo(true); + } else if (postImages.length == 0 || videoThumbnail.length == 0) { + setIsVisibleImage(true); + setIsVisibleVideo(true); + } + }, [postImages, postVideos, isVisibleImage, isVisibleVideo, videoThumbnail]); + + //check mobile device + useEffect(() => { + if (isMobile()) { + setIsVisibleCamera(true); + } else { + setIsVisibleCamera(false); + } + }, []); + + const handleThumbnailChange = ( + updatedThumbnails: { file: File; videoUrl: string; thumbnail: string | undefined }[], + ) => { + setVideoThumbnail(updatedThumbnails); + }; + + const handleRemoveThumbnail = (index: number) => { + setVideoThumbnail((prev) => prev.filter((_, i) => i !== index)); + }; + return ( <div className={styles.postComposerPage} style={themeStyles}> <form onSubmit={handleSubmit(onSubmit)}> @@ -153,26 +243,102 @@ const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCr <CreateNewPostButton pageId={pageId} onSubmit={handleSubmit(onSubmit)} - isValid={textValue.text.length > 0 && !isPending} + isValid={textValue.text.length > 0 || postImages.length > 0 || postVideos.length > 0} /> </div> - <PostTextField ref={editorRef} onChange={onChange} communityId={targetId} /> - </form> - {isPending && isOnline && ( - <Notification - content="Posting..." - icon={<Spinner />} - className={styles.postComposerPage__status} + <PostTextField ref={editorRef} onChange={onChange} /> + <ImageThumbnail + files={incomingImages} + uploadedFiles={postImages} + uploadLoading={uploadLoading} + onLoadingChange={setUploadLoading} + onChange={({ uploaded, uploading }) => { + setPostImages(uploaded); + setIncomingImages(uploading); + }} + onError={setIsErrorUpload} + isErrorUpload={isErrorUpload} /> - )} - {(isError || (!isOnline && isPending)) && ( - <Notification - content="Failed to create post" - icon={<ExclamationCircle className={styles.selectPostTargetPag_infoIcon} />} - className={styles.postComposerPage__status} - duration={3000} + <VideoThumbnail + files={incomingVideos} + uploadedFiles={postVideos} + uploadLoading={uploadLoading} + onLoadingChange={setUploadLoading} + onChange={({ uploaded, uploading }) => { + setPostVideos(uploaded); + setIncomingVideos(uploading); + }} + onError={setIsErrorUpload} + isErrorUpload={isErrorUpload} + videoThumbnail={videoThumbnail} + removeThumbnail={handleRemoveThumbnail} /> - )} + </form> + <div ref={drawerRef}></div> + {drawerRef.current + ? ReactDOM.createPortal( + <Drawer.Root + snapPoints={[ + HEIGHT_MEDIA_ATTACHMENT_MENU, + HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_2, + HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU, + ]} + activeSnapPoint={snap} + setActiveSnapPoint={handleSnapChange} + open={isShowBottomMenu} + modal={false} + > + <Drawer.Portal container={drawerRef.current}> + <Drawer.Content className={styles.drawer__content}> + <div className={styles.postComposerPage__notiWrap}> + {isPending && ( + <Notification + content="Posting..." + icon={<Spinner />} + className={styles.postComposerPage__status} + /> + )} + {isError && ( + <Notification + content="Failed to create post" + icon={<ExclamationCircle className={styles.selectPostTargetPag_infoIcon} />} + className={styles.postComposerPage__status} + duration={3000} + /> + )} + </div> + {snap == HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_2 || + snap == HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU ? ( + <DetailedMediaAttachment + pageId={pageId} + uploadLoading={uploadLoading} + onChangeImages={(newImageFiles) => setIncomingImages(newImageFiles)} + onChangeThumbnail={handleThumbnailChange} + videoThumbnail={videoThumbnail} + onChangeVideos={setIncomingVideos} + isVisibleCamera={isVisibleCamera} + isVisibleImage={isVisibleImage} + isVisibleVideo={isVisibleVideo} + /> + ) : ( + <MediaAttachment + pageId={pageId} + uploadLoading={uploadLoading} + onChangeImages={(newImageFiles) => setIncomingImages(newImageFiles)} + onChangeThumbnail={(updatedVideo) => handleThumbnailChange(updatedVideo)} + videoThumbnail={videoThumbnail} + onChangeVideos={setIncomingVideos} + isVisibleCamera={isVisibleCamera} + isVisibleImage={isVisibleImage} + isVisibleVideo={isVisibleVideo} + /> + )} + </Drawer.Content> + </Drawer.Portal> + </Drawer.Root>, + drawerRef.current, + ) + : null} </div> ); }; diff --git a/src/v4/social/utils/isMobile.ts b/src/v4/social/utils/isMobile.ts new file mode 100644 index 000000000..e1b11b064 --- /dev/null +++ b/src/v4/social/utils/isMobile.ts @@ -0,0 +1,4 @@ +export const isMobile = () => { + const regex = /Mobi|Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i; + return regex.test(navigator.userAgent); +}; From 8a50f992c424d5057b7dae5095d84c692a4ffaf2 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Thu, 11 Jul 2024 13:17:41 +0700 Subject: [PATCH 221/300] fix: ASC-23693 - fix premium ads global feed (#512) * fix: fix usePaginatorCore logic * fix: remove poll liveStream file posts from a global feed * fix: reduce a request amount * fix: time-window ad and type error * fix: child post * fix: filter liveStream poll file post * fix: react rerender issue * fix: observer display --- .../core/hooks/collections/useGlobalFeed.ts | 79 ---------- src/v4/core/hooks/usePagination.ts | 146 ++++++++++-------- src/v4/core/providers/AmityUIKitProvider.tsx | 5 +- .../components/GlobalFeed/GlobalFeed.tsx | 21 +-- .../social/components/Newsfeed/Newsfeed.tsx | 11 +- .../PostContent/ImageContent/ImageContent.tsx | 71 ++++++--- .../PostContent/VideoContent/VideoContent.tsx | 100 +++++++----- .../StoryTab/StoryTabGlobalFeed.tsx | 97 +++++------- .../collections/useGlobalStoryTargets.ts | 5 +- .../PostComposerPage/PostComposerPage.tsx | 6 +- .../social/providers/GlobalFeedProvider.tsx | 138 +++++++++++++++++ src/v4/social/providers/PostProvider.tsx | 28 ---- 12 files changed, 395 insertions(+), 312 deletions(-) delete mode 100644 src/v4/core/hooks/collections/useGlobalFeed.ts create mode 100644 src/v4/social/providers/GlobalFeedProvider.tsx delete mode 100644 src/v4/social/providers/PostProvider.tsx diff --git a/src/v4/core/hooks/collections/useGlobalFeed.ts b/src/v4/core/hooks/collections/useGlobalFeed.ts deleted file mode 100644 index 6d3bea6d9..000000000 --- a/src/v4/core/hooks/collections/useGlobalFeed.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { FeedRepository } from '@amityco/ts-sdk'; -import { useCallback, useEffect, useMemo, useState } from 'react'; -import { usePaginatorApi } from '../usePagination'; - -export const useGlobalFeed = () => { - const [items, setItems] = useState<Array<Amity.Post | Amity.Ad>>([]); - const [isLoading, setIsLoading] = useState(false); - const [queryToken, setQueryToken] = useState<string | null>(null); - const [loadMoreHasBeenCalled, setLoadMoreHasBeenCalled] = useState(false); - const limit = 10; - - const { itemWithAds } = usePaginatorApi({ - items: items, - pageSize: limit, - placement: 'feed' as Amity.AdPlacement, - getItemId: (item) => item.postId, - }); - - async function fetchMore() { - try { - setIsLoading(true); - const newPosts = await FeedRepository.getCustomRankingGlobalFeed({ - limit: 10, - queryToken: queryToken || undefined, - }); - setQueryToken(newPosts.paging.next || null); - setItems((prev) => [...prev, ...newPosts.data]); - } finally { - setTimeout(() => { - setIsLoading(false); - }, 300); - } - } - - useEffect(() => { - fetchMore(); - - return () => { - setItems([]); - setQueryToken(null); - }; - }, []); - - const prependItem = (post: Amity.Post) => { - setItems((prevItems) => [post, ...prevItems]); - }; - - const removeItem = (postId: string) => { - const newItems = items.filter((item) => item.postId !== postId); - setItems(newItems); - }; - - const hasMore = useMemo(() => queryToken !== null, [queryToken]); - - const loadMore = useCallback(() => { - setLoadMoreHasBeenCalled(true); - if (isLoading) return; - if (hasMore) { - fetchMore(); - } - }, [isLoading, hasMore, fetchMore]); - - const refetch = () => { - setItems([]); - setQueryToken(null); - fetchMore(); - }; - - return { - itemWithAds, - isLoading, - prependItem, - removeItem, - loadMore, - hasMore, - loadMoreHasBeenCalled, - refetch, - }; -}; diff --git a/src/v4/core/hooks/usePagination.ts b/src/v4/core/hooks/usePagination.ts index 916b366a5..352bc3823 100644 --- a/src/v4/core/hooks/usePagination.ts +++ b/src/v4/core/hooks/usePagination.ts @@ -1,4 +1,4 @@ -import { useCallback, useMemo, useState } from 'react'; +import { useMemo, useState } from 'react'; import useLiveCollection from '~/v4/core/hooks/useLiveCollection'; import { AdEngine } from '../AdEngine'; import { useAdSettings, useRecommendAds } from '../providers/AdEngineProvider'; @@ -32,69 +32,85 @@ const usePaginatorCore = <T>({ const recommendedAds = useRecommendAds({ count, placement, communityId }); - const combineItemsWithAds = useCallback( - (newItems: T[]) => { - if (!adSettings?.enabled) { - return newItems; - } - if (frequency?.type === 'fixed') { - const newItemIds = new Set(newItems.map((item) => getItemId(item))); - - const prevItemWithAds = itemWithAds - .map((itemWithAd) => { - const itemId = getItemId(itemWithAd[0]); - - if (!newItemIds.has(itemId)) { - return null; - } - return itemWithAd; - }) - .filter(isNonNullable); - - const startItem = prevItemWithAds[0]; - const latestItem = prevItemWithAds[prevItemWithAds.length - 1]; - - const startIndex = latestItem - ? newItems.findIndex((newItem) => getItemId(newItem) === getItemId(latestItem[0])) - : 0; - - const topIndex = startItem - ? newItems.findIndex((newItem) => getItemId(newItem) === getItemId(startItem[0])) - : 0; - - const newestItems: Array<[T]> = (newItems || []).slice(0, topIndex).map((item) => [item]); - - const prevItems = [...newestItems, ...prevItemWithAds]; - - let runningAdIndex = currentAdIndex; - let runningIndex = currentIndex; - const suffixItems: Array<[T] | [T, Amity.Ad]> = newItems - .slice(startIndex) - .map((newItem) => { - runningIndex = runningIndex + 1; - const shouldPlaceAd = runningIndex % frequency.value === 0; - - if (!shouldPlaceAd) return [newItem]; - - const ad = recommendedAds[runningAdIndex]; - runningAdIndex = - runningAdIndex + 1 > recommendedAds.length - 1 ? 0 : runningAdIndex + 1; - return [newItem, ad]; - }); - - setCurrentAdIndex(runningAdIndex); - setCurrentIndex(runningIndex); - setItemWithAds([...prevItems, ...suffixItems]); - return [...prevItems, ...suffixItems].flatMap((item) => item); - } else if (frequency?.type === 'time-window') { - return [...newItems.slice(0, 1), recommendedAds[0], ...newItems.slice(1)]; - } + const reset = () => { + setCurrentAdIndex(0); + setItemWithAds([]); + setCurrentIndex(0); + }; + + const combineItemsWithAds = (newItems: T[]) => { + if (!adSettings?.enabled) { return newItems; - }, - [frequency, recommendedAds, adSettings?.enabled, getItemId], - ); + } + if (frequency?.type === 'fixed') { + const newItemIds = new Set(newItems.map((item) => getItemId(item))); + + const prevItemWithAds = itemWithAds + .map((itemWithAd) => { + const itemId = getItemId(itemWithAd[0]); + + if (!newItemIds.has(itemId)) { + return null; + } + return itemWithAd; + }) + .filter(isNonNullable); + + const startItem = prevItemWithAds[0]; + + const topIndex = (() => { + if (startItem) { + const foundedIndex = newItems.findIndex( + (newItem) => getItemId(newItem) === getItemId(startItem[0]), + ); + if (foundedIndex === -1) { + return 0; + } + return foundedIndex; + } + return 0; + })(); + + const newestItems: Array<[T]> = (newItems || []).slice(0, topIndex).map((item) => [item]); + + const prevItems = [...newestItems, ...prevItemWithAds]; + + const filteredNewItems = newItems.slice(topIndex).filter((newItem) => { + const itemId = getItemId(newItem); + return !prevItems.some((prevItem) => getItemId(prevItem[0]) === itemId); + }); + + let runningAdIndex = currentAdIndex; + let runningIndex = currentIndex; + const suffixItems: Array<[T] | [T, Amity.Ad]> = filteredNewItems.map((newItem) => { + runningIndex = runningIndex + 1; + const shouldPlaceAd = runningIndex % frequency.value === 0; + + if (!shouldPlaceAd) return [newItem]; + + const ad = recommendedAds[runningAdIndex]; + runningAdIndex = runningAdIndex + 1 > recommendedAds.length - 1 ? 0 : runningAdIndex + 1; + return [newItem, ad]; + }); + + setCurrentAdIndex(runningAdIndex); + setCurrentIndex(runningIndex); + const newItemsWithAds = [...prevItems, ...suffixItems]; + setItemWithAds([...prevItems, ...suffixItems]); + if (newItemsWithAds.length === 0) { + setCurrentAdIndex(0); + setCurrentIndex(0); + } + return [...prevItems, ...suffixItems].flatMap((item) => item); + } else if (frequency?.type === 'time-window') { + return [...newItems.slice(0, 1), recommendedAds[0], ...newItems.slice(1)].filter( + isNonNullable, + ); + } + return newItems; + }; - return { combineItemsWithAds }; + return { combineItemsWithAds, reset }; }; export const usePaginator = <TCallback, TParams>({ @@ -158,10 +174,10 @@ export const usePaginatorApi = <T>(params: { communityId?: string; getItemId: (item: T) => string; }) => { - const { items } = params; - const { combineItemsWithAds } = usePaginatorCore(params); + const { items, ...rest } = params; + const { combineItemsWithAds, reset } = usePaginatorCore(rest); const itemWithAds = useMemo(() => combineItemsWithAds(items), [items]); - return { itemWithAds }; + return { itemWithAds, reset }; }; diff --git a/src/v4/core/providers/AmityUIKitProvider.tsx b/src/v4/core/providers/AmityUIKitProvider.tsx index f6601018b..7241dfb53 100644 --- a/src/v4/core/providers/AmityUIKitProvider.tsx +++ b/src/v4/core/providers/AmityUIKitProvider.tsx @@ -27,7 +27,6 @@ import { defaultConfig, Config, CustomizationProvider } from './CustomizationPro import { ThemeProvider } from './ThemeProvider'; import { PageBehavior, PageBehaviorProvider } from './PageBehaviorProvider'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { UIStyles } from '~/core/providers/UiKitProvider/styles'; import AmityUIKitManager from '../AmityUIKitManager'; import { ConfirmProvider } from '~/v4/core/providers/ConfirmProvider'; import { ConfirmProvider as LegacyConfirmProvider } from '~/core/providers/ConfirmProvider'; @@ -35,9 +34,9 @@ import { NotificationProvider } from '~/v4/core/providers/NotificationProvider'; import { DrawerProvider } from '~/v4/core/providers/DrawerProvider'; import { NotificationProvider as LegacyNotificationProvider } from '~/core/providers/NotificationProvider'; import { CustomReactionProvider } from './CustomReactionProvider'; -import { PostProvider } from '~/v4/social/providers/PostProvider'; import { AdEngineProvider } from './AdEngineProvider'; import { AdEngine } from '../AdEngine'; +import { GlobalFeedProvider } from '~/v4/social/providers/GlobalFeedProvider'; export type AmityUIKitConfig = Config; @@ -166,7 +165,7 @@ const AmityUIKitProvider: React.FC<AmityUIKitProviderProps> = ({ <PostRendererProvider config={postRendererConfig}> <NavigationProvider> <PageBehaviorProvider pageBehavior={pageBehavior}> - <PostProvider>{children}</PostProvider> + <GlobalFeedProvider>{children}</GlobalFeedProvider> </PageBehaviorProvider> </NavigationProvider> </PostRendererProvider> diff --git a/src/v4/social/components/GlobalFeed/GlobalFeed.tsx b/src/v4/social/components/GlobalFeed/GlobalFeed.tsx index 6170b1bef..8fc7c30a3 100644 --- a/src/v4/social/components/GlobalFeed/GlobalFeed.tsx +++ b/src/v4/social/components/GlobalFeed/GlobalFeed.tsx @@ -3,7 +3,6 @@ import { PostContent, PostContentSkeleton } from '../PostContent'; import { EmptyNewsfeed } from '~/v4/social/components/EmptyNewsFeed/EmptyNewsFeed'; import useIntersectionObserver from '~/v4/core/hooks/useIntersectionObserver'; import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; -import { usePostContext } from '~/v4/social/providers/PostProvider'; import styles from './GlobalFeed.module.css'; import { useAmityComponent } from '~/v4/core/hooks/uikit'; @@ -19,7 +18,7 @@ interface GlobalFeedProps { } const isAmityAd = (item: Amity.Post | Amity.Ad): item is Amity.Ad => { - return 'adId' in item; + return (item as Amity.Ad)?.adId !== undefined; }; export const GlobalFeed = ({ @@ -38,7 +37,6 @@ export const GlobalFeed = ({ const intersectionRef = useRef<HTMLDivElement>(null); const { AmityGlobalFeedComponentBehavior } = usePageBehavior(); - const { post: newPost } = usePostContext(); useIntersectionObserver({ ref: intersectionRef, @@ -53,23 +51,8 @@ export const GlobalFeed = ({ return ( <div className={styles.global_feed} style={themeStyles} data-qa-anchor={accessibilityId}> - {newPost && ( - <> - <div className={styles.global_feed__postContainer}> - <PostContent - pageId={pageId} - post={newPost} - type="feed" - onClick={() => { - AmityGlobalFeedComponentBehavior?.goToPostDetailPage?.({ postId: newPost.postId }); - }} - /> - </div> - <div className={styles.global_feed__divider} /> - </> - )} {items.map((item, index) => ( - <div key={item.postId}> + <div key={item.postId || item.adId}> {index !== 0 ? <div className={styles.global_feed__divider} /> : null} {isAmityAd(item) ? ( <PostAd key={item.adId} ad={item} /> diff --git a/src/v4/social/components/Newsfeed/Newsfeed.tsx b/src/v4/social/components/Newsfeed/Newsfeed.tsx index dde75c597..6af5f7a47 100644 --- a/src/v4/social/components/Newsfeed/Newsfeed.tsx +++ b/src/v4/social/components/Newsfeed/Newsfeed.tsx @@ -1,11 +1,11 @@ -import React, { useRef, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { StoryTab } from '~/v4/social/components/StoryTab'; -import { useGlobalFeed } from '~/v4/core/hooks/collections/useGlobalFeed'; import { EmptyNewsfeed } from '~/v4/social/components/EmptyNewsFeed'; import { GlobalFeed } from '~/v4/social/components/GlobalFeed'; import styles from './Newsfeed.module.css'; import { useAmityComponent } from '~/v4/core/hooks/uikit'; +import { useGlobalFeedContext } from '../../providers/GlobalFeedProvider'; const Spinner = (props: React.SVGProps<SVGSVGElement>) => { return ( @@ -55,7 +55,12 @@ export const Newsfeed = ({ pageId = '*' }: NewsfeedProps) => { const touchStartY = useRef(0); const [touchDiff, setTouchDiff] = useState(0); - const { itemWithAds, hasMore, isLoading, loadMore, refetch, removeItem } = useGlobalFeed(); + const { itemWithAds, hasMore, isLoading, loadMore, fetch, refetch, removeItem } = + useGlobalFeedContext(); + + useEffect(() => { + fetch(); + }, []); const onFeedReachBottom = () => { if (hasMore && !isLoading) loadMore(); diff --git a/src/v4/social/components/PostContent/ImageContent/ImageContent.tsx b/src/v4/social/components/PostContent/ImageContent/ImageContent.tsx index 708ad50ad..fa9e52774 100644 --- a/src/v4/social/components/PostContent/ImageContent/ImageContent.tsx +++ b/src/v4/social/components/PostContent/ImageContent/ImageContent.tsx @@ -1,10 +1,10 @@ import React, { useMemo } from 'react'; import useImage from '~/v4/core/hooks/useImage'; -import usePostByIds from '~/v4/core/hooks/usePostByIds'; import { Typography } from '~/v4/core/components'; import { useAmityElement } from '~/v4/core/hooks/uikit/index'; import styles from './ImageContent.module.css'; +import usePost from '~/v4/core/hooks/objects/usePost'; interface ImageContentProps { pageId?: string; @@ -14,7 +14,7 @@ interface ImageContentProps { onImageClick: (imageIndex: number) => void; } -const Image = ({ fileId }: { fileId: string }) => { +const ImageThumbnail = ({ fileId }: { fileId: string }) => { const imageUrl = useImage({ fileId, }); @@ -22,6 +22,41 @@ const Image = ({ fileId }: { fileId: string }) => { return <img loading="lazy" className={styles.imageContent__img} src={imageUrl} alt={fileId} />; }; +const Image = ({ + postId, + index, + imageLeftCount, + postAmount, + onImageClick, +}: { + postId: string; + index: number; + imageLeftCount: number; + postAmount: number; + onImageClick: () => void; +}) => { + const { post: imagePost, isLoading } = usePost(postId); + + if (isLoading) { + return null; + } + + return ( + <div + key={imagePost.postId} + className={styles.imageContent__imgContainer} + onClick={() => onImageClick()} + > + <ImageThumbnail fileId={imagePost.data.fileId} /> + {imageLeftCount > 0 && index === postAmount - 1 && ( + <Typography.Heading className={styles.imageContent__imgCover}> + + {imageLeftCount} + </Typography.Heading> + )} + </div> + ); +}; + export const ImageContent = ({ pageId = '*', componentId = '*', @@ -35,15 +70,17 @@ export const ImageContent = ({ elementId, }); - const first4Images = useMemo(() => post.children.slice(0, 4), [post.children]); + const first4Images = post.children.slice(0, 4); const imageLeftCount = Math.max(0, post.children.length - 4); - const posts = usePostByIds(first4Images); + const { post: childPost, isLoading } = usePost(post.children?.[0]); - const imagePosts = posts.filter((post) => post.dataType === 'image'); + if (isLoading) { + return null; + } - if (imagePosts.length === 0) { + if (childPost?.dataType !== 'image') { return null; } @@ -53,19 +90,15 @@ export const ImageContent = ({ style={themeStyles} data-images-amount={Math.min(post.children.length, 4)} > - {imagePosts.map((post, index) => ( - <div - key={post.postId} - className={styles.imageContent__imgContainer} - onClick={() => onImageClick(index)} - > - <Image fileId={post.data.fileId} /> - {imageLeftCount > 0 && index === posts.length - 1 && ( - <Typography.Heading className={styles.imageContent__imgCover}> - + {imageLeftCount} - </Typography.Heading> - )} - </div> + {first4Images.map((postId: string, index: number) => ( + <Image + key={postId} + postId={postId} + index={index} + imageLeftCount={imageLeftCount} + postAmount={post.children.length} + onImageClick={() => onImageClick(index)} + /> ))} </div> ); diff --git a/src/v4/social/components/PostContent/VideoContent/VideoContent.tsx b/src/v4/social/components/PostContent/VideoContent/VideoContent.tsx index ec18a759d..be93f1618 100644 --- a/src/v4/social/components/PostContent/VideoContent/VideoContent.tsx +++ b/src/v4/social/components/PostContent/VideoContent/VideoContent.tsx @@ -1,9 +1,9 @@ import React, { useMemo } from 'react'; import useImage from '~/v4/core/hooks/useImage'; -import usePostByIds from '~/v4/core/hooks/usePostByIds'; import { Typography } from '~/v4/core/components'; import { useAmityElement } from '~/v4/core/hooks/uikit'; import styles from './VideoContent.module.css'; +import usePost from '~/v4/core/hooks/objects/usePost'; const PlayButtonSvg = (props: React.SVGProps<SVGSVGElement>) => ( <svg @@ -25,6 +25,56 @@ const PlayButtonSvg = (props: React.SVGProps<SVGSVGElement>) => ( </svg> ); +const VideoThumbnail = ({ fileId }: { fileId: string }) => { + const videoThumbnailUrl = useImage({ + fileId, + }); + + return <img className={styles.videoContent__video} src={videoThumbnailUrl} alt={fileId} />; +}; + +const Video = ({ + postId, + postAmount, + videoLeftCount, + index, + onVideoClick, +}: { + postId: string; + postAmount: number; + videoLeftCount: number; + index: number; + onVideoClick: () => void; +}) => { + const { post: videoPost, isLoading } = usePost(postId); + + if (isLoading) { + return null; + } + + return ( + <div + className={styles.videoContent__videoContainer} + data-videos-amount={Math.min(postAmount, 4)} + onClick={() => onVideoClick()} + > + <VideoThumbnail fileId={videoPost.data.thumbnailFileId} /> + {videoLeftCount > 0 && index === postAmount - 1 && ( + <Typography.Heading className={styles.videoContent__videoCover}> + + {videoLeftCount} + </Typography.Heading> + )} + {videoLeftCount === 0 || index < postAmount - 1 ? ( + <div className={styles.videoContent__playButtonCover}> + <div className={styles.videoContent__playButton} onClick={() => onVideoClick()}> + <PlayButtonSvg className={styles.videoContent__playButton__svg} /> + </div> + </div> + ) : null} + </div> + ); +}; + interface VideoContentProps { pageId?: string; componentId?: string; @@ -33,14 +83,6 @@ interface VideoContentProps { onVideoClick: (index: number) => void; } -const Video = ({ fileId }: { fileId: string }) => { - const videoThumbnailUrl = useImage({ - fileId, - }); - - return <img className={styles.videoContent__video} src={videoThumbnailUrl} alt={fileId} />; -}; - export const VideoContent = ({ pageId = '*', componentId = '*', @@ -54,15 +96,13 @@ export const VideoContent = ({ elementId, }); - const first4Videos = useMemo(() => post.children.slice(0, 4), [post.children]); - - const posts = usePostByIds(first4Videos); + const first4Videos = post.children.slice(0, 4); - const videoPosts = posts.filter((post) => post.dataType === 'video'); + const { post: childPost } = usePost(post.children[0]); const videoLeftCount = Math.max(0, post.children.length - 4); - if (videoPosts.length === 0) { + if (childPost?.dataType !== 'video') { return null; } @@ -73,29 +113,15 @@ export const VideoContent = ({ style={themeStyles} data-videos-amount={Math.min(post.children.length, 4)} > - {videoPosts.map((post, index) => ( - <div - className={styles.videoContent__videoContainer} - data-videos-amount={Math.min(post.children.length, 4)} - onClick={() => onVideoClick(index)} - > - <Video fileId={post.data.thumbnailFileId} /> - {videoLeftCount > 0 && index === posts.length - 1 && ( - <Typography.Heading className={styles.videoContent__videoCover}> - + {videoLeftCount} - </Typography.Heading> - )} - {videoLeftCount === 0 || index < posts.length - 1 ? ( - <div className={styles.videoContent__playButtonCover}> - <div - className={styles.videoContent__playButton} - onClick={() => onVideoClick(index)} - > - <PlayButtonSvg className={styles.videoContent__playButton__svg} /> - </div> - </div> - ) : null} - </div> + {first4Videos.map((postId: string, index: number) => ( + <Video + key={postId} + index={index} + postId={postId} + videoLeftCount={videoLeftCount} + postAmount={post.children.length} + onVideoClick={() => onVideoClick(index)} + /> ))} </div> </div> diff --git a/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx b/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx index 638049f4e..6e4d6be36 100644 --- a/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx +++ b/src/v4/social/components/StoryTab/StoryTabGlobalFeed.tsx @@ -30,50 +30,29 @@ export const StoryTabGlobalFeed = ({ }); const containerRef = useRef<HTMLDivElement>(null); - const observerRef = useRef<IntersectionObserver | null>(null); + const observerRef = useRef<HTMLDivElement>(null); useEffect(() => { - if (containerRef.current) { - observerRef.current = new IntersectionObserver( - (entries) => { - const lastStory = entries[0]; - - if (lastStory.isIntersecting && hasMore) { - loadMoreStories(); - } - }, - { root: containerRef.current, rootMargin: '0px', threshold: 0.9 }, - ); + if (!containerRef.current) { + return; + } + const intersectionObserver = new IntersectionObserver( + (entries) => { + const lastStory = entries[0]; - if (stories.length > 0) { - const lastStoryElement = containerRef.current.children[stories.length - 1]; - if (lastStoryElement) { - observerRef.current.observe(lastStoryElement); + if (lastStory.isIntersecting && hasMore) { + loadMoreStories(); } - } - } + }, + { root: containerRef.current, rootMargin: '0px', threshold: 0.9 }, + ); return () => { - if (observerRef.current) { - observerRef.current.disconnect(); + if (intersectionObserver) { + intersectionObserver.disconnect(); } }; - }, [stories, hasMore, loadMoreStories]); - - if (isExcluded) return null; - - if (isLoading) { - return ( - <div style={themeStyles} className={styles.storyTabContainer}> - {Array.from({ length: 10 }).map((_, index) => ( - <div key={index} className={styles.storyTabSkeleton}> - <div className={styles.storyTabSkeletonAvatar} /> - <div className={styles.storyTabSkeletonUsername} /> - </div> - ))} - </div> - ); - } + }, [containerRef?.current]); if (isExcluded) return null; @@ -86,25 +65,33 @@ export const StoryTabGlobalFeed = ({ className={styles.storyTabContainer} ref={containerRef} > - {stories.map((story) => { - return ( - <StoryTabItem - pageId={pageId} - componentId={componentId} - key={story.targetId} - targetId={story.targetId} - hasUnseen={story.hasUnseen} - isErrored={story.failedStoriesCount > 0} - onClick={() => - goToViewStoryPage({ - storyTargets: stories, - storyTarget: story, - }) - } - size={64} - /> - ); - })} + {isLoading + ? Array.from({ length: 10 }).map((_, index) => ( + <div key={index} className={styles.storyTabSkeleton}> + <div className={styles.storyTabSkeletonAvatar} /> + <div className={styles.storyTabSkeletonUsername} /> + </div> + )) + : stories.map((story) => { + return ( + <StoryTabItem + pageId={pageId} + componentId={componentId} + key={story.targetId} + targetId={story.targetId} + hasUnseen={story.hasUnseen} + isErrored={story.failedStoriesCount > 0} + onClick={() => + goToViewStoryPage({ + storyTargets: stories, + storyTarget: story, + }) + } + size={64} + /> + ); + })} + <div ref={observerRef} style={{ height: '1px', width: '1px' }} /> </div> ); }; diff --git a/src/v4/social/hooks/collections/useGlobalStoryTargets.ts b/src/v4/social/hooks/collections/useGlobalStoryTargets.ts index 5c3fdf584..cb3a4f1e5 100644 --- a/src/v4/social/hooks/collections/useGlobalStoryTargets.ts +++ b/src/v4/social/hooks/collections/useGlobalStoryTargets.ts @@ -4,14 +4,14 @@ import useLiveCollection from '~/v4/core/hooks/useLiveCollection'; export const useGlobalStoryTargets = ( params: Amity.LiveCollectionParams<Amity.StoryGlobalQuery>, ) => { - const { items, hasMore, loadMore, ...rest } = useLiveCollection({ + const { items, hasMore, loadMore, isLoading, ...rest } = useLiveCollection({ fetcher: StoryRepository.getGlobalStoryTargets, params, shouldCall: true, }); const loadMoreStories = () => { - if (hasMore) { + if (hasMore && !isLoading) { loadMore(); } }; @@ -19,6 +19,7 @@ export const useGlobalStoryTargets = ( return { stories: items, hasMore, + isLoading, loadMoreStories, ...rest, }; diff --git a/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx b/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx index 5852ebb94..0df503dc3 100644 --- a/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx +++ b/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx @@ -23,6 +23,7 @@ import { ImageThumbnail } from '~/v4/social/internal-components/ImageThumbnail'; import { MediaAttachment } from '~/v4/social/components/MediaAttachment'; import { VideoThumbnail } from '~/v4/social/internal-components/VideoThumbnail'; import { isMobile } from '~/v4/social/utils/isMobile'; +import { useGlobalFeedContext } from '../../providers/GlobalFeedProvider'; export enum Mode { CREATE = 'create', @@ -96,10 +97,10 @@ const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCr const editorRef = useRef<LexicalEditor | null>(null); const { AmityPostComposerPageBehavior } = usePageBehavior(); const { confirm } = useConfirmContext(); - const isOnline = useConnectionState(); const [snap, setSnap] = useState<number | string | null>(HEIGHT_MEDIA_ATTACHMENT_MENU); const [isShowBottomMenu] = useState<boolean>(true); const drawerRef = useRef<HTMLDivElement>(null); + const { prependItem } = useGlobalFeedContext(); const [textValue, setTextValue] = useState<CreatePostParams>({ text: '', @@ -142,8 +143,9 @@ const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCr mutationFn: async (params: Parameters<typeof PostRepository.createPost>[0]) => { return PostRepository.createPost(params); }, - onSuccess: () => { + onSuccess: (response) => { AmityPostComposerPageBehavior.goToSocialHomePage(); + prependItem(response.data); }, onError: (error) => { console.error('Failed to create post', error); diff --git a/src/v4/social/providers/GlobalFeedProvider.tsx b/src/v4/social/providers/GlobalFeedProvider.tsx new file mode 100644 index 000000000..d11a8ffb9 --- /dev/null +++ b/src/v4/social/providers/GlobalFeedProvider.tsx @@ -0,0 +1,138 @@ +import React, { createContext, useContext, useState, useCallback, useMemo } from 'react'; +import { FeedRepository, PostRepository } from '@amityco/ts-sdk'; + +import { usePaginatorApi } from '~/v4/core/hooks/usePagination'; +import { isNonNullable } from '~/v4/helpers/utils'; + +const useGlobalFeed = () => { + const [items, setItems] = useState<Array<Amity.Post>>([]); + const [isLoading, setIsLoading] = useState(false); + const [queryToken, setQueryToken] = useState<string | null>(null); + const [loadMoreHasBeenCalled, setLoadMoreHasBeenCalled] = useState(false); + const [hasBeenFetched, setHasBeenFetched] = useState(false); + const limit = 10; + + const { itemWithAds, reset } = usePaginatorApi({ + items: items, + pageSize: limit, + placement: 'feed' as Amity.AdPlacement, + getItemId: (item) => item.postId, + }); + + async function fetchMore() { + try { + setIsLoading(true); + const newPosts = await FeedRepository.getCustomRankingGlobalFeed({ + limit: 10, + queryToken: queryToken || undefined, + }); + + const filteredPosts = ( + await Promise.all( + newPosts.data.map(async (post: Amity.Post) => { + if (post?.children?.length > 0) { + let unsub = () => {}; + const childPost = await new Promise<Amity.Post>((resolve) => { + unsub = PostRepository.getPost(post.children[0], (response) => { + return resolve(response.data); + }); + }); + unsub(); + if (!childPost) { + return post; + } + if (['liveStream', 'poll', 'file'].includes(childPost.dataType)) { + return null; + } + return post; + } + return post; + }), + ) + ).filter(isNonNullable); + + setQueryToken(newPosts.paging.next || null); + setItems((prev) => [...prev, ...filteredPosts]); + } finally { + setTimeout(() => { + setIsLoading(false); + }, 300); + } + } + + const prependItem = (post: Amity.Post) => { + setItems((prevItems) => [post, ...prevItems]); + }; + + const removeItem = (postId: string) => { + const newItems = items.filter((item) => item.postId !== postId); + setItems(newItems); + }; + + const hasMore = useMemo(() => queryToken !== null, [queryToken]); + + const loadMore = () => { + setLoadMoreHasBeenCalled(true); + if (isLoading) return; + if (hasMore) { + fetchMore(); + } + }; + + const fetch = () => { + if (hasBeenFetched) return; + refetch(); + setHasBeenFetched(true); + }; + + const refetch = () => { + setItems([]); + setQueryToken(null); + fetchMore(); + reset(); + }; + + return { + itemWithAds, + isLoading, + prependItem, + removeItem, + loadMore, + hasMore, + loadMoreHasBeenCalled, + fetch, + refetch, + }; +}; + +type GlobalFeedContextType = ReturnType<typeof useGlobalFeed>; + +const GlobalFeedContext = createContext<GlobalFeedContextType>({ + itemWithAds: [], + isLoading: false, + prependItem: () => {}, + removeItem: () => {}, + loadMore: () => {}, + hasMore: false, + loadMoreHasBeenCalled: false, + fetch: () => {}, + refetch: () => {}, +}); + +export const useGlobalFeedContext = () => { + const context = useContext(GlobalFeedContext); + if (!context) { + throw new Error('useGlobalFeedContext must be used within a GlobalFeedProvider'); + } + return context; +}; + +type GlobalFeedProviderProps = { + children: React.ReactNode; +}; + +export const GlobalFeedProvider: React.FC<GlobalFeedProviderProps> = ({ children }) => { + const value = useGlobalFeed(); + + return <GlobalFeedContext.Provider value={value}>{children}</GlobalFeedContext.Provider>; +}; diff --git a/src/v4/social/providers/PostProvider.tsx b/src/v4/social/providers/PostProvider.tsx deleted file mode 100644 index 5829de918..000000000 --- a/src/v4/social/providers/PostProvider.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React, { createContext, useContext, useState } from 'react'; - -type PostContextType = { - post: Amity.Post | null; - setPost: (post: Amity.Post) => void; -}; - -const PostContext = createContext<PostContextType>({ - post: null, - setPost: () => {}, -}); - -export const usePostContext = () => useContext(PostContext); - -type PostProviderProps = { - children: React.ReactNode; -}; - -export const PostProvider: React.FC<PostProviderProps> = ({ children }) => { - const [post, setPost] = useState<Amity.Post | null>(null); - - const value: PostContextType = { - post, - setPost, - }; - - return <PostContext.Provider value={value}>{children}</PostContext.Provider>; -}; From 3e1fa8ee6ded21978b5c933fcf8a5e34eaefe0da Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Thu, 11 Jul 2024 13:42:55 +0700 Subject: [PATCH 222/300] fix: retain scroll position (#513) --- .../pages/SocialHomePage/SocialHomePage.tsx | 20 ++++++++++++++++++- .../social/providers/GlobalFeedProvider.tsx | 20 ++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx b/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx index 925c35e2f..f0770e2a3 100644 --- a/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx +++ b/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx @@ -11,6 +11,7 @@ import { useAmityPage } from '~/v4/core/hooks/uikit'; import { CreatePostMenu } from '~/v4/social/components/CreatePostMenu'; import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; import { useNavigation } from '~/v4/core/providers/NavigationProvider'; +import { useGlobalFeedContext } from '../../providers/GlobalFeedProvider'; enum EnumTabNames { Newsfeed = 'Newsfeed', @@ -25,17 +26,34 @@ export function SocialHomePage() { }); const { goToSocialGlobalSearchPage, goToMyCommunitiesSearchPage } = useNavigation(); + const { scrollPosition, onScroll } = useGlobalFeedContext(); + const [activeTab, setActiveTab] = useState(EnumTabNames.Newsfeed); const [isShowCreatePostMenu, setIsShowCreatePostMenu] = useState(false); const createPostMenuRef = useRef<HTMLDivElement | null>(null); const createPostButtonRef = useRef<HTMLDivElement>(null); + const containerRef = useRef<HTMLDivElement>(null); + const initialLoad = useRef(true); + + useEffect(() => { + if (!containerRef.current) return; + containerRef.current.scrollTop = scrollPosition; + setTimeout(() => { + initialLoad.current = false; + }, 100); + }, [containerRef.current]); const handleClickButton = (event: React.MouseEvent) => { event.stopPropagation(); setIsShowCreatePostMenu((prev) => !prev); }; + const handleScroll = (event: React.UIEvent<HTMLDivElement, UIEvent>) => { + if (initialLoad.current) return; + onScroll(event); + }; + const handleGlobalSearchClick = () => { switch (activeTab) { case EnumTabNames.Newsfeed: @@ -97,7 +115,7 @@ export function SocialHomePage() { /> </div> </div> - <div className={styles.socialHomePage__contents}> + <div className={styles.socialHomePage__contents} ref={containerRef} onScroll={handleScroll}> {activeTab === EnumTabNames.Newsfeed && <Newsfeed pageId={pageId} />} {activeTab === EnumTabNames.Explore && <div>Explore</div>} {activeTab === EnumTabNames.MyCommunities && <MyCommunities pageId={pageId} />} diff --git a/src/v4/social/providers/GlobalFeedProvider.tsx b/src/v4/social/providers/GlobalFeedProvider.tsx index d11a8ffb9..904eedfce 100644 --- a/src/v4/social/providers/GlobalFeedProvider.tsx +++ b/src/v4/social/providers/GlobalFeedProvider.tsx @@ -1,4 +1,12 @@ -import React, { createContext, useContext, useState, useCallback, useMemo } from 'react'; +import React, { + createContext, + useContext, + useState, + useCallback, + useMemo, + useEffect, + useRef, +} from 'react'; import { FeedRepository, PostRepository } from '@amityco/ts-sdk'; import { usePaginatorApi } from '~/v4/core/hooks/usePagination'; @@ -10,6 +18,7 @@ const useGlobalFeed = () => { const [queryToken, setQueryToken] = useState<string | null>(null); const [loadMoreHasBeenCalled, setLoadMoreHasBeenCalled] = useState(false); const [hasBeenFetched, setHasBeenFetched] = useState(false); + const [scrollPosition, setScrollPosition] = useState(0); const limit = 10; const { itemWithAds, reset } = usePaginatorApi({ @@ -19,6 +28,11 @@ const useGlobalFeed = () => { getItemId: (item) => item.postId, }); + const onScroll = (event: React.UIEvent<HTMLDivElement>) => { + const target = event.target as HTMLDivElement; + setScrollPosition(target.scrollTop); + }; + async function fetchMore() { try { setIsLoading(true); @@ -102,6 +116,8 @@ const useGlobalFeed = () => { loadMoreHasBeenCalled, fetch, refetch, + scrollPosition, + onScroll, }; }; @@ -117,6 +133,8 @@ const GlobalFeedContext = createContext<GlobalFeedContextType>({ loadMoreHasBeenCalled: false, fetch: () => {}, refetch: () => {}, + scrollPosition: 0, + onScroll: () => {}, }); export const useGlobalFeedContext = () => { From a43aedd6afa713d68be7bbda78c21ec22b4f9105 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Thu, 11 Jul 2024 15:37:21 +0700 Subject: [PATCH 223/300] fix: remove button hover color (#517) --- src/v4/social/internal-components/PostAd/PostAd.module.css | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/v4/social/internal-components/PostAd/PostAd.module.css b/src/v4/social/internal-components/PostAd/PostAd.module.css index 9aaa39bba..87e80f158 100644 --- a/src/v4/social/internal-components/PostAd/PostAd.module.css +++ b/src/v4/social/internal-components/PostAd/PostAd.module.css @@ -80,10 +80,6 @@ border-radius: var(--asc-border-radius-md); } -.footer__content__button:hover:not(.disabled) { - background-color: var(--asc-color-primary-50); -} - .infoIcon__button { position: absolute; width: 1rem; From 8a29b4e3d4beacc0697a80b80f6413eba14c89b5 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Thu, 11 Jul 2024 15:37:40 +0700 Subject: [PATCH 224/300] fix: reaction count (#509) --- src/v4/social/elements/ReactionButton/ReactionButton.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/v4/social/elements/ReactionButton/ReactionButton.tsx b/src/v4/social/elements/ReactionButton/ReactionButton.tsx index 7332a88ad..3d3d3356e 100644 --- a/src/v4/social/elements/ReactionButton/ReactionButton.tsx +++ b/src/v4/social/elements/ReactionButton/ReactionButton.tsx @@ -10,6 +10,7 @@ import Love from '~/v4/social/elements/ReactionButton/Love'; import styles from './ReactionButton.module.css'; import { useAmityElement } from '~/v4/core/hooks/uikit'; import { Button } from '~/v4/core/natives/Button'; +import millify from 'millify'; const LikeSvg = (props: React.SVGProps<SVGSVGElement>) => ( <svg @@ -102,7 +103,7 @@ export function ReactionButton({ className={styles.reactButton__reactionsText} data-has-my-reaction={hasMyReaction} > - {typeof reactionsCount === 'number' ? reactionsCount : myReaction || config.text} + {typeof reactionsCount === 'number' ? millify(reactionsCount) : myReaction || config.text} </Typography.BodyBold> </Button> ); From 48bd769c7aa9d9aa0f84a328038007e1e2cd6ab6 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Thu, 11 Jul 2024 15:38:40 +0700 Subject: [PATCH 225/300] fix: comment ad styles (#518) --- .../CommentAd/CommentAd.module.css | 19 +++++++++++++++++-- .../CommentAd/CommentAd.tsx | 4 ++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/v4/social/internal-components/CommentAd/CommentAd.module.css b/src/v4/social/internal-components/CommentAd/CommentAd.module.css index 92ce5e546..ea11871f6 100644 --- a/src/v4/social/internal-components/CommentAd/CommentAd.module.css +++ b/src/v4/social/internal-components/CommentAd/CommentAd.module.css @@ -2,6 +2,7 @@ width: 100%; position: relative; height: 100%; + max-width: 24rem; } .commentAd__container { @@ -34,8 +35,16 @@ max-width: max-content; } -.commentAd__content__username { +.commentAd__content__advertiserName__container { + width: 100%; +} + +.commentAd__content__advertiserName { color: var(--asc-color-base-default); + width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .commentAd__content__text { @@ -86,7 +95,7 @@ .commentAd__adCard__description { color: var(--asc-color-base-shade1); width: 100%; - word-wrap: break-word; + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } @@ -97,6 +106,9 @@ word-wrap: break-word; overflow: hidden; text-overflow: ellipsis; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + display: -webkit-box; } .commentAd__adCard__button { @@ -113,6 +125,9 @@ word-wrap: break-word; overflow: hidden; text-overflow: ellipsis; + -webkit-line-clamp: 1; + -webkit-box-orient: vertical; + display: -webkit-box; } .infoIcon__button { diff --git a/src/v4/social/internal-components/CommentAd/CommentAd.tsx b/src/v4/social/internal-components/CommentAd/CommentAd.tsx index 37f1776df..118b5a070 100644 --- a/src/v4/social/internal-components/CommentAd/CommentAd.tsx +++ b/src/v4/social/internal-components/CommentAd/CommentAd.tsx @@ -44,8 +44,8 @@ export const CommentAd = ({ pageId = '*', ad }: CommentAdProps) => { </div> <div className={styles.commentAd__details}> <div className={styles.commentAd__content}> - <Typography.BodyBold className={styles.commentAd__content__username}> - {ad.advertiser?.name} + <Typography.BodyBold className={styles.commentAd__content__advertiserName__container}> + <div className={styles.commentAd__content__advertiserName}>{ad.advertiser?.name}</div> </Typography.BodyBold> <AdsBadge /> From de2d038ba9b17cda63a18907beb5616863759fda Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Thu, 11 Jul 2024 15:40:11 +0700 Subject: [PATCH 226/300] fix: ASC-00000 - fix paginator (#516) * fix: return an empty array when its length is 0 * chore: rename usePagination to usePaginator --- src/v4/core/hooks/{usePagination.ts => usePaginator.ts} | 3 +++ src/v4/social/components/PostCommentList/PostCommentList.tsx | 2 +- src/v4/social/hooks/collections/useCommentsCollection.ts | 2 +- src/v4/social/hooks/useGetActiveStories.ts | 2 +- src/v4/social/providers/GlobalFeedProvider.tsx | 2 +- 5 files changed, 7 insertions(+), 4 deletions(-) rename src/v4/core/hooks/{usePagination.ts => usePaginator.ts} (98%) diff --git a/src/v4/core/hooks/usePagination.ts b/src/v4/core/hooks/usePaginator.ts similarity index 98% rename from src/v4/core/hooks/usePagination.ts rename to src/v4/core/hooks/usePaginator.ts index 352bc3823..56765ed18 100644 --- a/src/v4/core/hooks/usePagination.ts +++ b/src/v4/core/hooks/usePaginator.ts @@ -103,6 +103,9 @@ const usePaginatorCore = <T>({ } return [...prevItems, ...suffixItems].flatMap((item) => item); } else if (frequency?.type === 'time-window') { + if (newItems.length === 0) { + return newItems; + } return [...newItems.slice(0, 1), recommendedAds[0], ...newItems.slice(1)].filter( isNonNullable, ); diff --git a/src/v4/social/components/PostCommentList/PostCommentList.tsx b/src/v4/social/components/PostCommentList/PostCommentList.tsx index 4b486025a..2cf0ef9fb 100644 --- a/src/v4/social/components/PostCommentList/PostCommentList.tsx +++ b/src/v4/social/components/PostCommentList/PostCommentList.tsx @@ -7,7 +7,7 @@ import { useAmityComponent } from '~/v4/core/hooks/uikit/index'; import useUserSubscription from '~/v4/core/hooks/subscriptions/useUserSubscription'; import { CommentRepository, SubscriptionLevels } from '@amityco/ts-sdk'; import useCommunitySubscription from '~/v4/core/hooks/subscriptions/useCommunitySubscription'; -import { usePaginator } from '~/v4/core/hooks/usePagination'; +import { usePaginator } from '~/v4/core/hooks/usePaginator'; import { CommentAd } from '../../internal-components/CommentAd/CommentAd'; type PostCommentListProps = { diff --git a/src/v4/social/hooks/collections/useCommentsCollection.ts b/src/v4/social/hooks/collections/useCommentsCollection.ts index 65cd6e32e..b8a63f09a 100644 --- a/src/v4/social/hooks/collections/useCommentsCollection.ts +++ b/src/v4/social/hooks/collections/useCommentsCollection.ts @@ -1,6 +1,6 @@ import { CommentRepository } from '@amityco/ts-sdk'; import useLiveCollection from '~/v4/core/hooks/useLiveCollection'; -import { usePaginator } from '~/v4/core/hooks/usePagination'; +import { usePaginator } from '~/v4/core/hooks/usePaginator'; type useCommentsParams = { parentId?: string | null; diff --git a/src/v4/social/hooks/useGetActiveStories.ts b/src/v4/social/hooks/useGetActiveStories.ts index a471aa94d..3b2aabc39 100644 --- a/src/v4/social/hooks/useGetActiveStories.ts +++ b/src/v4/social/hooks/useGetActiveStories.ts @@ -1,6 +1,6 @@ import { StoryRepository } from '@amityco/ts-sdk'; import { isNonNullable } from '~/v4/helpers/utils'; -import { usePaginator } from '~/v4/core/hooks/usePagination'; +import { usePaginator } from '~/v4/core/hooks/usePaginator'; export const useGetActiveStoriesByTarget = (params: Amity.GetStoriesByTargetParam) => { const { items, ...rest } = usePaginator({ diff --git a/src/v4/social/providers/GlobalFeedProvider.tsx b/src/v4/social/providers/GlobalFeedProvider.tsx index 904eedfce..313d2a374 100644 --- a/src/v4/social/providers/GlobalFeedProvider.tsx +++ b/src/v4/social/providers/GlobalFeedProvider.tsx @@ -9,7 +9,7 @@ import React, { } from 'react'; import { FeedRepository, PostRepository } from '@amityco/ts-sdk'; -import { usePaginatorApi } from '~/v4/core/hooks/usePagination'; +import { usePaginatorApi } from '~/v4/core/hooks/usePaginator'; import { isNonNullable } from '~/v4/helpers/utils'; const useGlobalFeed = () => { From 399b381606f03b57040a342f06d63cd31fd14686 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Thu, 11 Jul 2024 15:40:26 +0700 Subject: [PATCH 227/300] fix: ASC-23591 - image ratio (#515) * chore: remove unused code * fix: fix image ratio --- .../PostContent/ImageContent/ImageContent.module.css | 3 +-- src/v4/social/providers/GlobalFeedProvider.tsx | 10 +--------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/v4/social/components/PostContent/ImageContent/ImageContent.module.css b/src/v4/social/components/PostContent/ImageContent/ImageContent.module.css index 384bff5ae..107d4bb9a 100644 --- a/src/v4/social/components/PostContent/ImageContent/ImageContent.module.css +++ b/src/v4/social/components/PostContent/ImageContent/ImageContent.module.css @@ -7,12 +7,11 @@ } .imageContent[data-images-amount='1'] { - aspect-ratio: 16/ 9; grid-template: 'image1' / minmax(0, 1fr); } .imageContent[data-images-amount='2'] { - aspect-ratio: 16/ 9; + aspect-ratio: 1; grid-template: 'image1 image2' 50% 'image1 image2' 50% diff --git a/src/v4/social/providers/GlobalFeedProvider.tsx b/src/v4/social/providers/GlobalFeedProvider.tsx index 313d2a374..0fc3674ac 100644 --- a/src/v4/social/providers/GlobalFeedProvider.tsx +++ b/src/v4/social/providers/GlobalFeedProvider.tsx @@ -1,12 +1,4 @@ -import React, { - createContext, - useContext, - useState, - useCallback, - useMemo, - useEffect, - useRef, -} from 'react'; +import React, { createContext, useContext, useState, useMemo } from 'react'; import { FeedRepository, PostRepository } from '@amityco/ts-sdk'; import { usePaginatorApi } from '~/v4/core/hooks/usePaginator'; From 812c3e4a525937c0b794248dc12d762de6a587bd Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Thu, 11 Jul 2024 17:19:25 +0700 Subject: [PATCH 228/300] fix: ASC-24193 - fix a background color css variable (#510) * fix: asc-color-base-background to asc-color-background-default * fix: update PostAd footer background color --- src/v4/social/components/PostComment/PostComment.module.css | 4 ++-- .../PostCommentComposer/PostCommentComposer.module.css | 2 +- .../PostCommentMentionInput/PostMentionUser.module.css | 2 +- .../components/PostReplyComment/PostReplyComment.module.css | 4 ++-- .../PostReplyCommentList/PostReplyCommentList.module.css | 2 +- .../social/elements/PostTextField/PostTextField.module.css | 4 ++-- .../internal-components/CommentAd/CommentAd.module.css | 2 +- .../internal-components/CommentList/CommentList.module.css | 2 +- .../CommunityMember/CommunityMember.module.css | 2 +- src/v4/social/internal-components/PostAd/PostAd.module.css | 5 ++--- 10 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/v4/social/components/PostComment/PostComment.module.css b/src/v4/social/components/PostComment/PostComment.module.css index d30a25e8e..17d6b6287 100644 --- a/src/v4/social/components/PostComment/PostComment.module.css +++ b/src/v4/social/components/PostComment/PostComment.module.css @@ -111,7 +111,7 @@ cursor: pointer; align-items: flex-start; gap: 0.25rem; - background: var(--asc-color-base-background); + background: var(--asc-color-background-default); border: 1px solid var(--asc-color-base-shade4); border-radius: 0.25rem; width: max-content; @@ -165,7 +165,7 @@ color: var(--asc-color-secondary-shade1); border-radius: var(--asc-border-radius-sm); border: 1px solid var(--asc-color-secondary-shade1); - background: var(--asc-color-base-background); + background: var(--asc-color-background-default); } .postComment__edit__saveButton { diff --git a/src/v4/social/components/PostCommentComposer/PostCommentComposer.module.css b/src/v4/social/components/PostCommentComposer/PostCommentComposer.module.css index 6596919df..554f64b3e 100644 --- a/src/v4/social/components/PostCommentComposer/PostCommentComposer.module.css +++ b/src/v4/social/components/PostCommentComposer/PostCommentComposer.module.css @@ -2,7 +2,7 @@ position: relative; display: flex; flex-direction: row; - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); align-items: flex-end; padding: var(--asc-spacing-s1) 0 var(--asc-spacing-s1) var(--asc-spacing-m1); border-top: 1px solid var(--asc-color-base-shade4); diff --git a/src/v4/social/components/PostCommentMentionInput/PostMentionUser.module.css b/src/v4/social/components/PostCommentMentionInput/PostMentionUser.module.css index b4b048958..2167f73ff 100644 --- a/src/v4/social/components/PostCommentMentionInput/PostMentionUser.module.css +++ b/src/v4/social/components/PostCommentMentionInput/PostMentionUser.module.css @@ -3,7 +3,7 @@ display: flex; align-items: center; padding: 0.5rem 1rem; - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); color: var(--asc-color-base-default); } diff --git a/src/v4/social/components/PostReplyComment/PostReplyComment.module.css b/src/v4/social/components/PostReplyComment/PostReplyComment.module.css index c8e13327b..1b4c2f03b 100644 --- a/src/v4/social/components/PostReplyComment/PostReplyComment.module.css +++ b/src/v4/social/components/PostReplyComment/PostReplyComment.module.css @@ -86,7 +86,7 @@ cursor: pointer; align-items: flex-start; gap: 0.25rem; - background: var(--asc-color-base-background); + background: var(--asc-color-background-default); border: 1px solid var(--asc-color-base-shade4); border-radius: 0.25rem; width: max-content; @@ -181,7 +181,7 @@ color: var(--asc-color-secondary-shade1); border-radius: var(--asc-border-radius-sm); border: 1px solid var(--asc-color-secondary-shade1); - background: var(--asc-color-base-background); + background: var(--asc-color-background-default); } .postReplyComment__edit__saveButton { diff --git a/src/v4/social/components/PostReplyCommentList/PostReplyCommentList.module.css b/src/v4/social/components/PostReplyCommentList/PostReplyCommentList.module.css index fd5a7905d..207117e63 100644 --- a/src/v4/social/components/PostReplyCommentList/PostReplyCommentList.module.css +++ b/src/v4/social/components/PostReplyCommentList/PostReplyCommentList.module.css @@ -4,7 +4,7 @@ cursor: pointer; align-items: flex-start; gap: 0.25rem; - background: var(--asc-color-base-background); + background: var(--asc-color-background-default); border: 1px solid var(--asc-color-base-shade4); border-radius: 0.25rem; width: max-content; diff --git a/src/v4/social/elements/PostTextField/PostTextField.module.css b/src/v4/social/elements/PostTextField/PostTextField.module.css index f292e725d..5554cf35f 100644 --- a/src/v4/social/elements/PostTextField/PostTextField.module.css +++ b/src/v4/social/elements/PostTextField/PostTextField.module.css @@ -12,12 +12,12 @@ height: 100%; width: 100%; color: var(--asc-color-base-default); - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); } .editorParagraph span { color: var(--asc-color-base-default); - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); } .editorContainer { diff --git a/src/v4/social/internal-components/CommentAd/CommentAd.module.css b/src/v4/social/internal-components/CommentAd/CommentAd.module.css index ea11871f6..7376c0f37 100644 --- a/src/v4/social/internal-components/CommentAd/CommentAd.module.css +++ b/src/v4/social/internal-components/CommentAd/CommentAd.module.css @@ -56,7 +56,7 @@ display: flex; border-radius: 0.5rem; border: 1px solid var(--asc-color-base-shade4); - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); height: 100%; width: 100%; cursor: pointer; diff --git a/src/v4/social/internal-components/CommentList/CommentList.module.css b/src/v4/social/internal-components/CommentList/CommentList.module.css index 60de970b5..7fe757517 100644 --- a/src/v4/social/internal-components/CommentList/CommentList.module.css +++ b/src/v4/social/internal-components/CommentList/CommentList.module.css @@ -37,5 +37,5 @@ border-radius: var(--asc-border-radius-sm); text-align: center; color: var(--asc-color-base-shade2); - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); } diff --git a/src/v4/social/internal-components/CommunityMember/CommunityMember.module.css b/src/v4/social/internal-components/CommunityMember/CommunityMember.module.css index b4b048958..2167f73ff 100644 --- a/src/v4/social/internal-components/CommunityMember/CommunityMember.module.css +++ b/src/v4/social/internal-components/CommunityMember/CommunityMember.module.css @@ -3,7 +3,7 @@ display: flex; align-items: center; padding: 0.5rem 1rem; - background-color: var(--asc-color-base-background); + background-color: var(--asc-color-background-default); color: var(--asc-color-base-default); } diff --git a/src/v4/social/internal-components/PostAd/PostAd.module.css b/src/v4/social/internal-components/PostAd/PostAd.module.css index 87e80f158..3d643ae11 100644 --- a/src/v4/social/internal-components/PostAd/PostAd.module.css +++ b/src/v4/social/internal-components/PostAd/PostAd.module.css @@ -1,6 +1,6 @@ .container { position: relative; - background-color: var(--asc-color-base-background-sh); + background-color: var(--asc-color-background-default-sh); } .innerContainer { @@ -51,8 +51,7 @@ } .footer { - /* TODO: change it to css variable */ - background-color: #f6f7f8; + background-color: var(--asc-color-background-shade1); padding: var(--asc-spacing-m1) var(--asc-spacing-s2); display: flex; flex-direction: row; From 6bde5233a286baa3dd74a97581b958e6d6c140f9 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Thu, 11 Jul 2024 17:34:20 +0700 Subject: [PATCH 229/300] chore: ASC-00000 - premium ads pure components (#522) * feat: export CommentAd and PostAd as a pure component # Conflicts: # src/index.ts # src/v4/social/internal-components/CommentAd/CommentAd.tsx # src/v4/social/internal-components/PostAd/PostAd.tsx * chore: rename AdInformation dir * fix: componentId --- src/index.ts | 18 ---- .../AdInformation.module.css | 0 .../AdInformation.tsx | 0 .../CommentAd/CommentAd.stories.tsx | 14 --- .../CommentAd/CommentAd.tsx | 79 +++------------- ...ntAd.module.css => UICommentAd.module.css} | 0 .../CommentAd/UICommentAd.stories.tsx | 14 +++ .../CommentAd/UICommentAd.tsx | 89 +++++++++++++++++++ .../internal-components/CommentAd/index.tsx | 0 .../internal-components/PostAd/PostAd.tsx | 76 +++------------- ...{PostAd.module.css => UIPostAd.module.css} | 0 ...ostAd.stories.tsx => UIPostAd.stories.tsx} | 4 +- .../internal-components/PostAd/UIPostAd.tsx | 83 +++++++++++++++++ .../internal-components/PostAd/index.tsx | 3 + 14 files changed, 216 insertions(+), 164 deletions(-) rename src/v4/social/internal-components/{AdInformation.tsx => AdInformation}/AdInformation.module.css (100%) rename src/v4/social/internal-components/{AdInformation.tsx => AdInformation}/AdInformation.tsx (100%) delete mode 100644 src/v4/social/internal-components/CommentAd/CommentAd.stories.tsx rename src/v4/social/internal-components/CommentAd/{CommentAd.module.css => UICommentAd.module.css} (100%) create mode 100644 src/v4/social/internal-components/CommentAd/UICommentAd.stories.tsx create mode 100644 src/v4/social/internal-components/CommentAd/UICommentAd.tsx create mode 100644 src/v4/social/internal-components/CommentAd/index.tsx rename src/v4/social/internal-components/PostAd/{PostAd.module.css => UIPostAd.module.css} (100%) rename src/v4/social/internal-components/PostAd/{PostAd.stories.tsx => UIPostAd.stories.tsx} (78%) create mode 100644 src/v4/social/internal-components/PostAd/UIPostAd.tsx create mode 100644 src/v4/social/internal-components/PostAd/index.tsx diff --git a/src/index.ts b/src/index.ts index 860a99bf6..c87480642 100644 --- a/src/index.ts +++ b/src/index.ts @@ -53,21 +53,3 @@ export { StoryPreview as AmityStoryPreview, StoryPreviewThumbnail as AmityStoryPreviewThumbnail, } from './v4/social/internal-components/StoryPreview'; - -// import AmityComment from './components/Comment'; -// import AmityCommentComposeBar from './components/CommentComposeBar'; -// import AmityCommentLikeButton from './components/CommentLikeButton'; -// import AmityCommunity from './components/Community'; -// import AmityCommunityItem from './components/CommunityItem'; -// import AmityEmptyFeed from './components/EmptyFeed'; -// import AmityFiles from './components/Files'; -// import AmityImageGallery from './components/ImageGallery'; -// import AmityImages from './components/Images'; -// import AmityMessage from './components/Message'; -// import AmityMessageComposeBar from './components/MessageComposeBar'; -// import AmityMessageList from './components/MessageList'; -// import AmityPost from './components/Post'; -// import AmityPostCreator from './components/PostCreator'; -// import AmityPostLikeButton from './components/PostLikeButton'; -// import AmityRecentChat from './components/RecentChat'; -// import AmitySideMenu from './components/SideMenu'; diff --git a/src/v4/social/internal-components/AdInformation.tsx/AdInformation.module.css b/src/v4/social/internal-components/AdInformation/AdInformation.module.css similarity index 100% rename from src/v4/social/internal-components/AdInformation.tsx/AdInformation.module.css rename to src/v4/social/internal-components/AdInformation/AdInformation.module.css diff --git a/src/v4/social/internal-components/AdInformation.tsx/AdInformation.tsx b/src/v4/social/internal-components/AdInformation/AdInformation.tsx similarity index 100% rename from src/v4/social/internal-components/AdInformation.tsx/AdInformation.tsx rename to src/v4/social/internal-components/AdInformation/AdInformation.tsx diff --git a/src/v4/social/internal-components/CommentAd/CommentAd.stories.tsx b/src/v4/social/internal-components/CommentAd/CommentAd.stories.tsx deleted file mode 100644 index 792215d86..000000000 --- a/src/v4/social/internal-components/CommentAd/CommentAd.stories.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; -import { CommentAd } from './CommentAd'; - -export default { - title: 'v4-social/internal-components/CommentAd', -}; - -export const CommentAdStory = { - render: () => ( - <div style={{ width: '80%', margin: 'auto' }}> - <CommentAd /> - </div> - ), -}; diff --git a/src/v4/social/internal-components/CommentAd/CommentAd.tsx b/src/v4/social/internal-components/CommentAd/CommentAd.tsx index 118b5a070..92f9bfbe6 100644 --- a/src/v4/social/internal-components/CommentAd/CommentAd.tsx +++ b/src/v4/social/internal-components/CommentAd/CommentAd.tsx @@ -1,13 +1,7 @@ -import React, { useState } from 'react'; -import styles from './CommentAd.module.css'; -import { Avatar, Typography } from '~/v4/core/components'; -import { AdsBadge } from '../AdsBadge/AdsBadge'; -import Broadcast from '~/v4/icons/Broadcast'; -import InfoCircle from '~/v4/icons/InfoCircle'; +import React from 'react'; import { useAmityComponent } from '~/v4/core/hooks/uikit/index'; -import { Button } from '~/v4/core/natives/Button'; import useImage from '~/v4/core/hooks/useImage'; -import { AdInformation } from '../AdInformation.tsx/AdInformation'; +import { UICommentAd } from './UICommentAd'; interface CommentAdProps { pageId?: string; @@ -21,74 +15,23 @@ export const CommentAd = ({ pageId = '*', ad }: CommentAdProps) => { componentId, }); - const [isAdvertisementInfoOpen, setIsAdvertisementInfoOpen] = useState(false); - const avatarFile = useImage({ fileId: ad.advertiser?.avatar?.fileId }); const avatarUrl = avatarFile || ad.advertiser?.avatar?.fileUrl || ''; const adImageFile = useImage({ fileId: ad.image1_1?.fileId }); const adImageUrl = adImageFile || ad.image1_1?.fileUrl || ''; - const handleCallToActionClick = () => { - window?.open(ad?.callToActionUrl, '_blank'); + const handleCallToActionClick = (link: string) => { + window?.open(link, '_blank'); }; return ( - <div className={styles.commentAd} style={themeStyles}> - <Button className={styles.infoIcon__button} onPress={() => setIsAdvertisementInfoOpen(true)}> - <InfoCircle className={styles.infoIcon} /> - </Button> - <div className={styles.commentAd__container}> - <div className={styles.commentAd__avatar}> - <Avatar avatarUrl={avatarUrl} defaultImage={<Broadcast />} /> - </div> - <div className={styles.commentAd__details}> - <div className={styles.commentAd__content}> - <Typography.BodyBold className={styles.commentAd__content__advertiserName__container}> - <div className={styles.commentAd__content__advertiserName}>{ad.advertiser?.name}</div> - </Typography.BodyBold> - - <AdsBadge /> - - <div> - <Typography.Body className={styles.commentAd__content__text}> - {ad.body} - </Typography.Body> - </div> - - <div className={styles.commentAd__adCard} onClick={handleCallToActionClick}> - <div className={styles.commentAd__adCard__imageContainer}> - <img className={styles.commentAd__adCard__image} src={adImageUrl} /> - </div> - <div className={styles.commentAd__adCard__detail}> - <div className={styles.commentAd__adCard__textContainer}> - <Typography.Caption className={styles.commentAd__adCard__description}> - {ad.description} - </Typography.Caption> - <Typography.BodyBold className={styles.commentAd__adCard__headline}> - {ad.headline} - </Typography.BodyBold> - </div> - {ad.callToActionUrl ? ( - <Button - className={styles.commentAd__adCard__button} - onPress={handleCallToActionClick} - > - <Typography.CaptionBold className={styles.commentAd__adCard__button__text}> - {ad.callToAction} - </Typography.CaptionBold> - </Button> - ) : null} - </div> - </div> - </div> - </div> - </div> - <AdInformation - ad={ad} - isOpen={isAdvertisementInfoOpen} - onOpenChange={(open) => setIsAdvertisementInfoOpen(open)} - /> - </div> + <UICommentAd + themeStyles={themeStyles} + avatarUrl={avatarUrl} + adImageUrl={adImageUrl} + ad={ad} + onCallToActionClick={handleCallToActionClick} + /> ); }; diff --git a/src/v4/social/internal-components/CommentAd/CommentAd.module.css b/src/v4/social/internal-components/CommentAd/UICommentAd.module.css similarity index 100% rename from src/v4/social/internal-components/CommentAd/CommentAd.module.css rename to src/v4/social/internal-components/CommentAd/UICommentAd.module.css diff --git a/src/v4/social/internal-components/CommentAd/UICommentAd.stories.tsx b/src/v4/social/internal-components/CommentAd/UICommentAd.stories.tsx new file mode 100644 index 000000000..da014ab97 --- /dev/null +++ b/src/v4/social/internal-components/CommentAd/UICommentAd.stories.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { UICommentAd } from './UICommentAd'; + +export default { + title: 'v4-social/internal-components/UICommentAd', +}; + +export const UICommentAdStory = { + render: () => ( + <div style={{ width: '80%', margin: 'auto' }}> + <UICommentAd /> + </div> + ), +}; diff --git a/src/v4/social/internal-components/CommentAd/UICommentAd.tsx b/src/v4/social/internal-components/CommentAd/UICommentAd.tsx new file mode 100644 index 000000000..4d68f2315 --- /dev/null +++ b/src/v4/social/internal-components/CommentAd/UICommentAd.tsx @@ -0,0 +1,89 @@ +import React, { useState } from 'react'; +import styles from './UICommentAd.module.css'; +import { Avatar, Typography } from '~/v4/core/components'; +import { AdsBadge } from '../AdsBadge/AdsBadge'; +import Broadcast from '~/v4/icons/Broadcast'; +import InfoCircle from '~/v4/icons/InfoCircle'; +import { Button } from '~/v4/core/natives/Button'; +import { AdInformation } from '../AdInformation/AdInformation'; + +interface UICommentAdProps { + ad: Amity.Ad; + avatarUrl?: string; + adImageUrl?: string; + themeStyles?: React.CSSProperties; + onCallToActionClick?: (link: string) => void; +} + +export const UICommentAd = ({ + ad, + themeStyles, + avatarUrl, + adImageUrl, + onCallToActionClick, +}: UICommentAdProps) => { + const [isAdvertisementInfoOpen, setIsAdvertisementInfoOpen] = useState(false); + + const handleCallToActionClick = () => { + onCallToActionClick?.(ad.callToActionUrl); + }; + + return ( + <div className={styles.commentAd} style={themeStyles}> + <Button className={styles.infoIcon__button} onPress={() => setIsAdvertisementInfoOpen(true)}> + <InfoCircle className={styles.infoIcon} /> + </Button> + <div className={styles.commentAd__container}> + <div className={styles.commentAd__avatar}> + <Avatar avatarUrl={avatarUrl} defaultImage={<Broadcast />} /> + </div> + <div className={styles.commentAd__details}> + <div className={styles.commentAd__content}> + <Typography.BodyBold className={styles.commentAd__content__advertiserName__container}> + <div className={styles.commentAd__content__advertiserName}>{ad.advertiser?.name}</div> + </Typography.BodyBold> + + <AdsBadge /> + + <div> + <Typography.Body className={styles.commentAd__content__text}> + {ad.body} + </Typography.Body> + </div> + + <div className={styles.commentAd__adCard} onClick={handleCallToActionClick}> + <div className={styles.commentAd__adCard__imageContainer}> + <img className={styles.commentAd__adCard__image} src={adImageUrl} /> + </div> + <div className={styles.commentAd__adCard__detail}> + <div className={styles.commentAd__adCard__textContainer}> + <Typography.Caption className={styles.commentAd__adCard__description}> + {ad.description} + </Typography.Caption> + <Typography.BodyBold className={styles.commentAd__adCard__headline}> + {ad.headline} + </Typography.BodyBold> + </div> + {ad.callToActionUrl ? ( + <Button + className={styles.commentAd__adCard__button} + onPress={handleCallToActionClick} + > + <Typography.CaptionBold className={styles.commentAd__adCard__button__text}> + {ad.callToAction} + </Typography.CaptionBold> + </Button> + ) : null} + </div> + </div> + </div> + </div> + </div> + <AdInformation + ad={ad} + isOpen={isAdvertisementInfoOpen} + onOpenChange={(open) => setIsAdvertisementInfoOpen(open)} + /> + </div> + ); +}; diff --git a/src/v4/social/internal-components/CommentAd/index.tsx b/src/v4/social/internal-components/CommentAd/index.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/src/v4/social/internal-components/PostAd/PostAd.tsx b/src/v4/social/internal-components/PostAd/PostAd.tsx index 153ec55f7..49289befa 100644 --- a/src/v4/social/internal-components/PostAd/PostAd.tsx +++ b/src/v4/social/internal-components/PostAd/PostAd.tsx @@ -1,13 +1,7 @@ -import React, { useState } from 'react'; -import styles from './PostAd.module.css'; -import { Avatar, Typography } from '~/v4/core/components'; -import { AdsBadge } from '../AdsBadge/AdsBadge'; -import Broadcast from '~/v4/icons/Broadcast'; -import InfoCircle from '~/v4/icons/InfoCircle'; +import React from 'react'; import { useAmityComponent } from '~/v4/core/hooks/uikit/index'; -import { Button } from '~/v4/core/natives/Button'; import useImage from '~/v4/core/hooks/useImage'; -import { AdInformation } from '../AdInformation.tsx/AdInformation'; +import { UIPostAd } from './UIPostAd'; interface PostAdProps { pageId?: string; @@ -21,65 +15,23 @@ export const PostAd = ({ pageId = '*', ad }: PostAdProps) => { componentId, }); - const [isAdvertisementInfoOpen, setIsAdvertisementInfoOpen] = useState(false); - const avatarFile = useImage({ fileId: ad.advertiser?.avatar?.fileId }); const avatarUrl = avatarFile || ad.advertiser?.avatar?.fileUrl || ''; const adImageFile = useImage({ fileId: ad.image1_1?.fileId }); const adImageUrl = adImageFile || ad.image1_1?.fileUrl || ''; - return ( - <div className={styles.container} style={themeStyles}> - <div className={styles.innerContainer}> - <div className={styles.header}> - <div className={styles.header__avatar}> - <Avatar avatarUrl={avatarUrl} defaultImage={<Broadcast />} /> - </div> - <div className={styles.header__detail}> - <Typography.BodyBold className={styles.header__title}> - {ad.advertiser?.name} - </Typography.BodyBold> - <AdsBadge /> - </div> - </div> - <div> - <Typography.Body className={styles.content__text}>{ad.body}</Typography.Body> - <div className={styles.content__imageContainer}> - <img className={styles.content__image} src={adImageUrl} /> - </div> - </div> - </div> - <Button className={styles.infoIcon__button} onPress={() => setIsAdvertisementInfoOpen(true)}> - <InfoCircle className={styles.infoIcon} /> - </Button> - <div - className={styles.footer} - data-has-url={!!ad.callToActionUrl} - onClick={() => window?.open(ad?.callToActionUrl, '_blank')} - > - <div> - <Typography.Body className={styles.footer__content__description}> - {ad.description} - </Typography.Body> - <Typography.BodyBold className={styles.footer__content__headline}> - {ad.headline} - </Typography.BodyBold> - </div> - {ad.callToActionUrl ? ( - <Button - className={styles.footer__content__button} - onPress={() => window?.open(ad?.callToActionUrl, '_blank')} - > - <Typography.CaptionBold>{ad.callToAction}</Typography.CaptionBold> - </Button> - ) : null} - </div> - <AdInformation - ad={ad} - isOpen={isAdvertisementInfoOpen} - onOpenChange={(open) => setIsAdvertisementInfoOpen(open)} - /> - </div> + const handleCallToActionClick = (link: string) => { + window?.open(link, '_blank'); + }; + + return ( + <UIPostAd + themeStyles={themeStyles} + avatarUrl={avatarUrl} + adImageUrl={adImageUrl} + ad={ad} + onCallToActionClick={handleCallToActionClick} + /> ); }; diff --git a/src/v4/social/internal-components/PostAd/PostAd.module.css b/src/v4/social/internal-components/PostAd/UIPostAd.module.css similarity index 100% rename from src/v4/social/internal-components/PostAd/PostAd.module.css rename to src/v4/social/internal-components/PostAd/UIPostAd.module.css diff --git a/src/v4/social/internal-components/PostAd/PostAd.stories.tsx b/src/v4/social/internal-components/PostAd/UIPostAd.stories.tsx similarity index 78% rename from src/v4/social/internal-components/PostAd/PostAd.stories.tsx rename to src/v4/social/internal-components/PostAd/UIPostAd.stories.tsx index 65904d417..e745ee859 100644 --- a/src/v4/social/internal-components/PostAd/PostAd.stories.tsx +++ b/src/v4/social/internal-components/PostAd/UIPostAd.stories.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { PostAd } from './PostAd'; +import { UIPostAd } from './UIPostAd'; export default { title: 'v4-social/internal-components/PostAd', @@ -8,7 +8,7 @@ export default { export const PostAdStory = { render: () => ( <div style={{ width: '80%', margin: 'auto' }}> - <PostAd /> + <UIPostAd /> </div> ), }; diff --git a/src/v4/social/internal-components/PostAd/UIPostAd.tsx b/src/v4/social/internal-components/PostAd/UIPostAd.tsx new file mode 100644 index 000000000..14ad06142 --- /dev/null +++ b/src/v4/social/internal-components/PostAd/UIPostAd.tsx @@ -0,0 +1,83 @@ +import React, { useState } from 'react'; +import { Avatar, Typography } from '~/v4/core/components'; +import { AdsBadge } from '../AdsBadge/AdsBadge'; +import Broadcast from '~/v4/icons/Broadcast'; +import InfoCircle from '~/v4/icons/InfoCircle'; +import { Button } from '~/v4/core/natives/Button'; +import { AdInformation } from '../AdInformation/AdInformation'; + +import styles from './UIPostAd.module.css'; + +interface UIPostAdProps { + ad: Amity.Ad; + avatarUrl: string; + adImageUrl: string; + themeStyles?: React.CSSProperties; + onCallToActionClick?: (link: string) => void; +} + +export const UIPostAd = ({ + ad, + avatarUrl, + adImageUrl, + themeStyles, + onCallToActionClick, +}: UIPostAdProps) => { + const [isAdvertisementInfoOpen, setIsAdvertisementInfoOpen] = useState(false); + + const handleCallToActionClick = () => { + onCallToActionClick?.(ad.callToActionUrl); + }; + + return ( + <div className={styles.container} style={themeStyles}> + <div className={styles.innerContainer}> + <div className={styles.header}> + <div className={styles.header__avatar}> + <Avatar avatarUrl={avatarUrl} defaultImage={<Broadcast />} /> + </div> + <div className={styles.header__detail}> + <Typography.BodyBold className={styles.header__title}> + {ad.advertiser?.name} + </Typography.BodyBold> + <AdsBadge /> + </div> + </div> + <div> + <Typography.Body className={styles.content__text}>{ad.body}</Typography.Body> + <div className={styles.content__imageContainer}> + <img className={styles.content__image} src={adImageUrl} /> + </div> + </div> + </div> + <Button className={styles.infoIcon__button} onPress={() => setIsAdvertisementInfoOpen(true)}> + <InfoCircle className={styles.infoIcon} /> + </Button> + + <div + className={styles.footer} + data-has-url={!!ad.callToActionUrl} + onClick={handleCallToActionClick} + > + <div> + <Typography.Body className={styles.footer__content__description}> + {ad.description} + </Typography.Body> + <Typography.BodyBold className={styles.footer__content__headline}> + {ad.headline} + </Typography.BodyBold> + </div> + {ad.callToActionUrl ? ( + <Button className={styles.footer__content__button} onPress={handleCallToActionClick}> + <Typography.CaptionBold>{ad.callToAction}</Typography.CaptionBold> + </Button> + ) : null} + </div> + <AdInformation + ad={ad} + isOpen={isAdvertisementInfoOpen} + onOpenChange={(open) => setIsAdvertisementInfoOpen(open)} + /> + </div> + ); +}; diff --git a/src/v4/social/internal-components/PostAd/index.tsx b/src/v4/social/internal-components/PostAd/index.tsx new file mode 100644 index 000000000..a7c26f488 --- /dev/null +++ b/src/v4/social/internal-components/PostAd/index.tsx @@ -0,0 +1,3 @@ +import { PostAd } from './PostAd'; + +export { PostAd }; From cf5bb43cb72e365fade6bfac73bc7794e30319db Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Thu, 11 Jul 2024 17:35:42 +0700 Subject: [PATCH 230/300] chore: ASC-00000 - console story ad ui (#521) * chore: console story ad ui * chore: add TODO --- .../StoryAd/UIStoryAd.module.css | 382 ++++++++++++++++++ .../internal-components/StoryAd/UIStoryAd.tsx | 153 +++++++ .../internal-components/StoryAd/index.ts | 1 + .../StoryViewer/Renderers/storyAd.tsx | 135 ++----- 4 files changed, 562 insertions(+), 109 deletions(-) create mode 100644 src/v4/social/internal-components/StoryAd/UIStoryAd.module.css create mode 100644 src/v4/social/internal-components/StoryAd/UIStoryAd.tsx create mode 100644 src/v4/social/internal-components/StoryAd/index.ts diff --git a/src/v4/social/internal-components/StoryAd/UIStoryAd.module.css b/src/v4/social/internal-components/StoryAd/UIStoryAd.module.css new file mode 100644 index 000000000..dee56ad71 --- /dev/null +++ b/src/v4/social/internal-components/StoryAd/UIStoryAd.module.css @@ -0,0 +1,382 @@ +.iconButton { + position: absolute; + width: 2rem; + height: 2rem; + + /* TODO: change to css variable */ + background-color: rgb(0 0 0 / 50%); + border-radius: 50%; + border: none; + top: 6rem; + left: 1.25rem; + z-index: 9999; + cursor: pointer; +} + +.hyperLinkContainer { + position: absolute; + bottom: 1.18rem; + z-index: 99999; + display: flex; + justify-content: center; + align-items: center; + padding: 0.625rem 1rem; + left: 50%; + transform: translateX(-50%); + border: 1px solid var(--asc-color-base-shade4); + border-radius: 1.5rem; + background-color: var(--asc-color-white); + box-shadow: var(--asc-box-shadow-03); +} + +.hyperLink__text { + text-decoration: none; + color: var(--asc-color-secondary-default); + text-align: center; + font-size: 0.9375rem; + font-style: normal; + font-weight: 600; + line-height: 1.25rem; /* 133.333% */ + letter-spacing: -0.015rem; +} + +.rendererContainer { + position: relative; + width: 100%; + height: 100%; + overflow: hidden; +} + +.storyVideo { + width: 100%; + height: 100%; + object-fit: contain; +} + +.muteCircleIcon { + width: 100%; + height: 100%; +} + +.unmuteCircleIcon { + width: 100%; + height: 100%; +} + +.loadingOverlay { + width: 100%; + height: 100%; + position: absolute; + left: 0; + top: 0; + background: rgb(0 0 0 / 90%); + z-index: 9; + display: flex; + justify-content: center; + align-items: center; +} + +.storyImageContainer { + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + background: var(--asc-story-image-background); +} + +.storyImage { + width: 100%; + height: 100%; + object-fit: contain; +} + +.playStoryButton { + color: var(--asc-color-white); + cursor: pointer; +} + +.pauseStoryButton { + color: var(--asc-color-white); + cursor: pointer; +} + +.closeButton { + color: var(--asc-color-white); + width: 1.25rem; + height: 1.25rem; + cursor: pointer; +} + +.verifiedBadge { + color: var(--asc-color-white); +} + +.dotsButton { + width: 1.5rem; + height: 1.5rem; + cursor: pointer; + color: var(--asc-color-white); +} + +.viewStoryInfoContainer { + display: flex; + flex-direction: column; + justify-content: flex-start; + width: 100%; +} + +.viewStoryCompostBarContainer { + width: 100%; + display: flex; + position: absolute; + justify-content: space-between; + align-items: center; + height: 3.5rem; + padding: var(--asc-spacing-s2); + background-color: var(--asc-color-black); + bottom: 0; +} + +.viewStoryCompostBarViewIconContainer { + display: flex; + align-items: center; + justify-content: space-between; + color: var(--asc-color-white); + gap: var(--asc-spacing-xxs2); +} + +.viewStoryCompostBarEngagementContainer { + display: flex; + align-items: center; + justify-content: space-between; + color: var(--asc-color-white); + gap: var(--asc-spacing-s2); +} + +.viewStoryCompostBarEngagementIconContainer { + display: flex; + align-items: center; + justify-content: space-between; + color: var(--asc-color-white); + gap: var(--asc-spacing-xxs2); + border-radius: 50%; + padding: var(--asc-spacing-s1) var(--asc-spacing-s2); + + /* TODO: change to css variable */ + background-color: #292b32; +} + +.header { + height: 5rem; + padding: var(--asc-spacing-s2) var(--asc-spacing-m1) var(--asc-spacing-s2) var(--asc-spacing-m1); +} + +.viewStoryContainer { + position: relative; + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + background-color: var(--asc-color-black); +} + +.viewStoryHeaderContainer { + z-index: 9999; + position: absolute; + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + flex-direction: column; + padding: var(--asc-spacing-m3) var(--asc-spacing-m1) var(--asc-spacing-s2) var(--asc-spacing-m1); + gap: var(--asc-spacing-s1); +} + +.avatarContainer { + position: relative; + width: 2.5rem; + height: 2.5rem; + border-radius: 50%; + flex-shrink: 0; +} + +.addStoryButton { + position: absolute; + bottom: 0; + right: 0; +} + +.addStoryButton:hover { + cursor: pointer; +} + +.viewStoryHeaderListActionsContainer { + display: flex; + gap: var(--asc-spacing-m2); + justify-content: flex-end; + align-items: center; +} + +.viewStoryHeadingInfoContainer { + display: flex; + justify-content: space-between; + width: 100%; + gap: var(--asc-spacing-s2); + align-items: center; +} + +.viewStoryHeading { + cursor: pointer; + display: flex; + gap: var(--asc-spacing-xxs2); + color: var(--asc-color-white); + font-size: var(--asc-text-font-size-sm); + font-style: normal; + font-weight: var(--asc-text-font-weight-bold); + line-height: var(--asc-line-height-md); + letter-spacing: -0.24px; + margin-right: var(--asc-spacing-xxs2); + align-items: center; +} + +.viewStoryHeadingTitle { + width: auto; + max-width: 11.688rem; +} + +.viewStorySubHeading { + display: inline-flex; + gap: var(--asc-spacing-xxs2); + margin-bottom: var(--asc-spacing-xxs2); + color: var(--asc-color-white); + font-size: var(--asc-text-font-size-xs); + font-style: normal; + font-weight: var(--asc-text-font-weight-normal); + line-height: var(--asc-line-height-md); + letter-spacing: -0.1px; +} + +.story { + display: flex; + position: relative; + overflow: hidden; +} + +.storyContent { + width: auto; + max-width: 100%; + max-height: 100%; + margin: auto; + flex: 1; +} + +.actionButton { + display: flex; + justify-content: flex-start; + align-items: center; + text-align: left; + gap: var(--asc-spacing-s1); + border: none; + color: var(--asc-color-base-default); + background-color: var(--asc-color-background-default); + padding: var(--asc-spacing-m1) var(--asc-spacing-m2); + cursor: pointer; + border-radius: var(--asc-border-radius-sm); +} + +.actionButton:hover { + background-color: var(--asc-color-base-shade4); +} + +.navigationOverlay { + position: absolute; + top: 0; + bottom: 0; + width: 50%; + z-index: 10; +} + +.leftOverlay { + left: 0; +} + +.rightOverlay { + right: 0; +} + +.imageFit { + object-fit: contain; +} + +.imageFill { + object-fit: cover; +} + +.infoIcon__button { + z-index: 99999; + position: absolute; + width: 1rem; + height: 1rem; + right: 0.25rem; + bottom: 0.25rem; + cursor: pointer; +} + +.infoIcon { + fill: var(--asc-color-base-shade3); +} + +.storyAd__topBar { + display: grid; + align-items: center; + grid-template-columns: minmax(0, 1fr) min-content; + gap: 0.5rem; + z-index: 99999; + position: absolute; + top: 1.25rem; + width: 100%; + padding-left: 1rem; + padding-right: 1rem; + height: 2.5rem; +} + +.storyAd__topBar__left { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.storyAd__topBar__avatar { + width: 2.5rem; + height: 2.5rem; +} + +.storyAd__topBar__text { + display: flex; + align-items: start; + flex-direction: column; + color: var(--asc-color-base-inverse); + flex-shrink: 1; + overflow: hidden; +} + +.storyAd__topBar__advertiserName { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: var(--asc-color-white); +} + +.storyAd__topBar__right { + display: flex; + align-items: center; + gap: 1.25rem; +} + +.storyAd__closeButton { + fill: var(--asc-color-white); + width: 1.25rem; + height: 1.25rem; + cursor: pointer; +} diff --git a/src/v4/social/internal-components/StoryAd/UIStoryAd.tsx b/src/v4/social/internal-components/StoryAd/UIStoryAd.tsx new file mode 100644 index 000000000..6e74a7a18 --- /dev/null +++ b/src/v4/social/internal-components/StoryAd/UIStoryAd.tsx @@ -0,0 +1,153 @@ +import React, { useState, useRef, ReactNode } from 'react'; +import clsx from 'clsx'; + +import { Button } from '~/v4/core/natives/Button'; +import { StoryProgressBar } from '~/v4/social/elements/StoryProgressBar/StoryProgressBar'; +import InfoCircle from '~/v4/icons/InfoCircle'; +import { Avatar, Typography } from '~/v4/core/components/index'; +import Broadcast from '~/v4/icons/Broadcast'; +import { PauseIcon, PlayIcon } from '~/icons/index'; +import { CloseButton } from '~/v4/social/elements/index'; +import { StoryAdInformation } from '~/v4/social/internal-components/StoryAdInformation/StoryAdInformation'; +import { AdsBadge } from '~/v4/social/internal-components/AdsBadge/AdsBadge'; + +import styles from './UIStoryAd.module.css'; + +interface UIStoryAdProps { + pageId?: string; + ad: Amity.Ad; + avatarUrl: string; + adImageUrl: string; + isPaused: boolean; + currentIndex: number; + storiesCount: number; + renderLoader?: () => ReactNode; + onComplete?: () => void; + onPlayClick?: () => void; + onPauseClick?: () => void; + onClose?: () => void; + onImageLoaded?: () => void; + onAdvertisementInfoOpen?: () => void; + onAdvertisementInfoClose?: () => void; +} + +export const UIStoryAd = ({ + pageId = '*', + ad, + avatarUrl, + adImageUrl, + currentIndex, + isPaused, + storiesCount, + renderLoader = () => <div>loading...</div>, + onComplete = () => {}, + onPauseClick, + onPlayClick, + onClose, + onImageLoaded, + onAdvertisementInfoOpen, + onAdvertisementInfoClose, +}: UIStoryAdProps) => { + const [isImageLoaded, setIsImageLoaded] = useState(false); + const [isAdvertisementInfoOpen, setIsAdvertisementInfoOpen] = useState(false); + + const openAdvertisementInfo = () => { + setIsAdvertisementInfoOpen(true); + onAdvertisementInfoOpen?.(); + }; + const closeAdvertisementInfo = () => { + setIsAdvertisementInfoOpen(false); + onAdvertisementInfoClose?.(); + }; + + const imageRef = useRef<HTMLImageElement>(null); + const targetRef = useRef<HTMLDivElement>(null); + + const handleImageLoaded = () => { + setIsImageLoaded(true); + onImageLoaded?.(); + }; + + return ( + <div className={styles.rendererContainer}> + <StoryProgressBar + pageId={pageId} + duration={5000} + currentIndex={currentIndex} + storiesCount={storiesCount} + isPaused={isPaused} + onComplete={onComplete} + /> + + <div className={styles.storyAd__topBar}> + <div className={styles.storyAd__topBar__left}> + <div className={styles.storyAd__topBar__avatar}> + <Avatar defaultImage={<Broadcast />} avatarUrl={avatarUrl} /> + </div> + <div className={styles.storyAd__topBar__text}> + <Typography.BodyBold className={styles.storyAd__topBar__advertiserName}> + {ad.advertiser?.name} + </Typography.BodyBold> + <AdsBadge /> + </div> + </div> + <div className={styles.storyAd__topBar__right}> + {isPaused ? ( + <PlayIcon + className={styles.playStoryButton} + data-qa-anchor="play_button" + onClick={() => onPlayClick?.()} + /> + ) : ( + <PauseIcon + className={styles.pauseStoryButton} + data-qa-anchor="pause_button" + onClick={() => onPauseClick?.()} + /> + )} + <CloseButton + defaultClassName={clsx(styles.storyAd__closeButton)} + pageId={pageId} + onPress={onClose} + /> + </div> + </div> + + <div className={clsx(styles.storyImageContainer)}> + <img + ref={imageRef} + className={clsx(styles.storyImage)} + data-qa-anchor="image_view" + src={adImageUrl} + onLoad={handleImageLoaded} + alt="Story Image" + crossOrigin="anonymous" + /> + </div> + + {!isImageLoaded && <div className={styles.loadingOverlay}>{renderLoader()}</div>} + + {ad.callToActionUrl && ( + <div className={styles.hyperLinkContainer}> + <a + className={styles.hyperLink__text} + href={ad.callToActionUrl} + target="_blank" + rel="noreferrer" + > + {ad.callToAction} + </a> + </div> + )} + <Button className={styles.infoIcon__button} onPress={() => openAdvertisementInfo()}> + <InfoCircle className={styles.infoIcon} /> + </Button> + <StoryAdInformation + ad={ad} + isOpen={isAdvertisementInfoOpen} + targetRef={targetRef} + onOpenChange={(open) => !open && closeAdvertisementInfo()} + /> + </div> + ); +}; diff --git a/src/v4/social/internal-components/StoryAd/index.ts b/src/v4/social/internal-components/StoryAd/index.ts new file mode 100644 index 000000000..0a06a0d81 --- /dev/null +++ b/src/v4/social/internal-components/StoryAd/index.ts @@ -0,0 +1 @@ +export { UIStoryAd } from './UIStoryAd'; diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/storyAd.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/storyAd.tsx index 79ee12983..63caee44f 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/storyAd.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/storyAd.tsx @@ -1,30 +1,13 @@ -import React, { useState, useEffect, useCallback, useRef } from 'react'; -import { - CustomRenderer, - Tester, -} from '~/v4/social/internal-components/StoryViewer/Renderers/types'; -import { Button } from '~/v4/core/natives/Button'; - -import styles from './storyAd.module.css'; -import clsx from 'clsx'; - -import { StoryProgressBar } from '~/v4/social/elements/StoryProgressBar/StoryProgressBar'; -import { StoryAdInformation } from '../../StoryAdInformation/StoryAdInformation'; -import InfoCircle from '~/v4/icons/InfoCircle'; -import { AdsBadge } from '../../AdsBadge/AdsBadge'; -import { Avatar, Typography } from '~/v4/core/components/index'; -import Broadcast from '~/v4/icons/Broadcast'; +import { Tester, CustomRenderer } from './types'; + +import React, { useState, useEffect, useCallback } from 'react'; + import useImage from '~/v4/core/hooks/useImage'; -import { PauseIcon, PlayIcon } from '~/icons/index'; -import { CloseButton } from '~/v4/social/elements/index'; +import { UIStoryAd } from '../../StoryAd/UIStoryAd'; export const renderer: CustomRenderer = ({ story, action, config, onClose }) => { - const [loaded, setLoaded] = useState(false); - const [isAdvertisementInfoOpen, setIsAdvertisementInfoOpen] = useState(false); const [isPaused, setIsPaused] = useState(false); const { loader } = config; - const imageRef = useRef<HTMLImageElement>(null); - const targetRef = useRef<HTMLDivElement>(null); const { ad, currentIndex, storiesCount, increaseIndex, pageId, dragEventTarget } = story; @@ -43,21 +26,18 @@ export const renderer: CustomRenderer = ({ story, action, config, onClose }) => const adImageFile = useImage({ fileId: ad?.image9_16?.fileId }); const adImageUrl = adImageFile || ad?.image9_16?.fileUrl || ''; - const imageLoaded = useCallback(() => { - setLoaded(true); + const onImageLoaded = useCallback(() => { if (isPaused) { setIsPaused(false); } action('play', true); }, [action, isPaused]); - const openAdvertisementInfo = () => { + const onAdvertisementInfoOpen = () => { action('pause', true); - setIsAdvertisementInfoOpen(true); }; - const closeAdvertisementInfo = () => { + const onAdvertisementInfoClose = () => { action('play', true); - setIsAdvertisementInfoOpen(false); }; const handleProgressComplete = () => { @@ -90,86 +70,23 @@ export const renderer: CustomRenderer = ({ story, action, config, onClose }) => } return ( - <div className={styles.rendererContainer} ref={targetRef}> - <StoryProgressBar - pageId={pageId} - duration={5000} - currentIndex={currentIndex} - storiesCount={storiesCount} - isPaused={isPaused || isAdvertisementInfoOpen} - onComplete={handleProgressComplete} - /> - - <div className={styles.storyAd__topBar}> - <div className={styles.storyAd__topBar__left}> - <div className={styles.storyAd__topBar__avatar}> - <Avatar defaultImage={<Broadcast />} avatarUrl={avatarUrl} /> - </div> - <div className={styles.storyAd__topBar__text}> - <Typography.BodyBold className={styles.storyAd__topBar__advertiserName}> - {ad.advertiser?.name} - </Typography.BodyBold> - <AdsBadge /> - </div> - </div> - <div className={styles.storyAd__topBar__right}> - {isPaused ? ( - <PlayIcon - className={styles.playStoryButton} - data-qa-anchor="play_button" - onClick={play} - /> - ) : ( - <PauseIcon - className={styles.pauseStoryButton} - data-qa-anchor="pause_button" - onClick={pause} - /> - )} - <CloseButton - defaultClassName={clsx(styles.storyAd__closeButton)} - pageId={pageId} - onPress={onClose} - /> - </div> - </div> - - <div className={clsx(styles.storyImageContainer)}> - <img - ref={imageRef} - className={clsx(styles.storyImage)} - data-qa-anchor="image_view" - src={adImageUrl} - onLoad={imageLoaded} - alt="Story Image" - crossOrigin="anonymous" - /> - </div> - - {!loaded && <div className={styles.loadingOverlay}>{loader || <div>loading...</div>}</div>} - - {ad.callToActionUrl && ( - <div className={styles.hyperLinkContainer}> - <a - className={styles.hyperLink__text} - href={ad.callToActionUrl} - target="_blank" - rel="noreferrer" - > - {ad.callToAction} - </a> - </div> - )} - <Button className={styles.infoIcon__button} onPress={() => openAdvertisementInfo()}> - <InfoCircle className={styles.infoIcon} /> - </Button> - <StoryAdInformation - ad={ad} - isOpen={isAdvertisementInfoOpen} - targetRef={targetRef} - onOpenChange={(open) => !open && closeAdvertisementInfo()} - /> - </div> + <UIStoryAd + pageId={pageId} + ad={ad} + adImageUrl={adImageUrl} + avatarUrl={avatarUrl} + currentIndex={currentIndex} + isPaused={isPaused} + storiesCount={storiesCount} + renderLoader={() => loader} + onComplete={handleProgressComplete} + onPauseClick={pause} + onPlayClick={play} + onClose={onClose} + onImageLoaded={onImageLoaded} + onAdvertisementInfoOpen={onAdvertisementInfoOpen} + onAdvertisementInfoClose={onAdvertisementInfoClose} + /> ); }; @@ -181,6 +98,6 @@ export const tester: Tester = (story) => { }; export default { - renderer, + renderer: renderer, tester, }; From d6396837909420a3fa46651906baecbc86b320dc Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Thu, 11 Jul 2024 17:38:55 +0700 Subject: [PATCH 231/300] fix: reactions count formatting (#519) --- src/v4/social/components/PostComment/PostComment.tsx | 3 ++- src/v4/social/components/PostContent/PostContent.tsx | 5 ++++- .../social/components/PostReplyComment/PostReplyComment.tsx | 3 ++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/v4/social/components/PostComment/PostComment.tsx b/src/v4/social/components/PostComment/PostComment.tsx index 1918f3fcd..77acbdb27 100644 --- a/src/v4/social/components/PostComment/PostComment.tsx +++ b/src/v4/social/components/PostComment/PostComment.tsx @@ -20,6 +20,7 @@ import { CommentOptions } from '../CommentOptions/CommentOptions'; import { CreateCommentParams } from '../PostCommentComposer/PostCommentComposer'; import useCommentSubscription from '~/v4/core/hooks/subscriptions/useCommentSubscription'; import { TextWithMention } from '../../internal-components/TextWithMention/TextWithMention'; +import millify from 'millify'; const EllipsisH = ({ ...props }: React.SVGProps<SVGSVGElement>) => ( <svg @@ -249,7 +250,7 @@ export const PostComment = ({ </div> {comment.reactionsCount > 0 && ( <div className={styles.postComment__secondRow__rightPane}> - <Typography.Caption>{comment.reactionsCount}</Typography.Caption> + <Typography.Caption>{millify(comment.reactionsCount)}</Typography.Caption> <Like className={styles.postComment__secondRow__rightPane__like} /> </div> )} diff --git a/src/v4/social/components/PostContent/PostContent.tsx b/src/v4/social/components/PostContent/PostContent.tsx index f399c6851..623494562 100644 --- a/src/v4/social/components/PostContent/PostContent.tsx +++ b/src/v4/social/components/PostContent/PostContent.tsx @@ -33,6 +33,7 @@ import { PostMenu } from '~/v4/social/internal-components/PostMenu/PostMenu'; import usePostSubscription from '~/v4/core/hooks/subscriptions/usePostSubscription'; import { ReactionList } from '../index'; import { usePostedUserInformation } from '~/v4/core/hooks/usePostedUserInformation'; +import millify from 'millify'; interface PostTitleProps { post: Amity.Post; @@ -357,7 +358,9 @@ export const PostContent = ({ </div> ) : null} <Typography.Caption className={styles.postContent__reactionsBar__reactions__count}> - {`${post?.reactionsCount || 0} ${post?.reactionsCount === 1 ? 'like' : 'likes'}`} + {`${millify(post?.reactionsCount || 0)} ${ + post?.reactionsCount === 1 ? 'like' : 'likes' + }`} </Typography.Caption> </div> diff --git a/src/v4/social/components/PostReplyComment/PostReplyComment.tsx b/src/v4/social/components/PostReplyComment/PostReplyComment.tsx index 022df5e6b..902c83bb9 100644 --- a/src/v4/social/components/PostReplyComment/PostReplyComment.tsx +++ b/src/v4/social/components/PostReplyComment/PostReplyComment.tsx @@ -1,5 +1,6 @@ import { CommentRepository, ReactionRepository } from '@amityco/ts-sdk'; import clsx from 'clsx'; +import millify from 'millify'; import React, { useCallback, useState } from 'react'; import EllipsisH from '~/icons/EllipsisH'; import { BottomSheet, Typography } from '~/v4/core/components/index'; @@ -194,7 +195,7 @@ const PostReplyComment = ({ </div> {comment.reactionsCount > 0 && ( <div className={styles.postReplyComment__secondRow__rightPane}> - <Typography.Caption>{comment.reactionsCount}</Typography.Caption> + <Typography.Caption>{millify(comment.reactionsCount)}</Typography.Caption> <Like className={styles.postReplyComment__secondRow__rightPane__like} /> </div> )} From 05a19a90f159d5eaf1e4ef25e115ed027beb64fc Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Thu, 11 Jul 2024 17:53:29 +0700 Subject: [PATCH 232/300] style: fix height (#523) --- .../ImageThumbnail/ImageThumbnail.module.css | 3 ++- .../VideoThumbnail/VideoThumbnail.module.css | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/v4/social/internal-components/ImageThumbnail/ImageThumbnail.module.css b/src/v4/social/internal-components/ImageThumbnail/ImageThumbnail.module.css index 23f2ee62f..fc634228b 100644 --- a/src/v4/social/internal-components/ImageThumbnail/ImageThumbnail.module.css +++ b/src/v4/social/internal-components/ImageThumbnail/ImageThumbnail.module.css @@ -13,7 +13,7 @@ .thumbnail__container[data-images-amount='2'] { grid-template-columns: repeat(2, 1fr); - height: 21rem; + max-height: 21rem; } .thumbnail__container[data-images-amount='3'] { @@ -24,6 +24,7 @@ position: relative; text-align: center; width: 100%; + max-height: 21rem; } .thumbnail__wrapper[data-images-height='true'] { diff --git a/src/v4/social/internal-components/VideoThumbnail/VideoThumbnail.module.css b/src/v4/social/internal-components/VideoThumbnail/VideoThumbnail.module.css index 0f704bd2e..fe946ebcc 100644 --- a/src/v4/social/internal-components/VideoThumbnail/VideoThumbnail.module.css +++ b/src/v4/social/internal-components/VideoThumbnail/VideoThumbnail.module.css @@ -24,6 +24,7 @@ position: relative; text-align: center; width: 100%; + max-height: 21rem; } .thumbnail__wrapper_item_3 { From 7a00f8239ca3a9c33b9b1c10d6e94cc1008d4fa5 Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Thu, 11 Jul 2024 18:09:18 +0700 Subject: [PATCH 233/300] fix: ASC-24020 - load more mention list (#514) * fix: load more mention list * refactor: load more Co-authored-by: Bonn <pittawat@amity.co> * fix: error ci --------- Co-authored-by: Bonn <pittawat@amity.co> --- .../MentionTextInput/MentionTextInput.tsx | 62 +++++++++++++------ .../PostComposerPage/PostComposerPage.tsx | 4 +- 2 files changed, 44 insertions(+), 22 deletions(-) diff --git a/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx b/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx index 15c5cb593..160438a47 100644 --- a/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx +++ b/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx @@ -13,8 +13,7 @@ import { CommunityMember } from '../CommunityMember'; import { useMemberQueryByDisplayName } from '~/v4/social/hooks/useMemberQueryByDisplayName'; import useCommunity from '~/v4/chat/hooks/useCommunity'; import { useUserQueryByDisplayName } from '~/v4/core/hooks/collections/useUsersCollection'; - -const MAX_LENGTH = 5000; +import useIntersectionObserver from '~/v4/core/hooks/useIntersectionObserver'; const PUNCTUATION = '\\.,\\+\\*\\?\\$\\@\\|#{}\\(\\)\\^\\-\\[\\]\\\\/!%\'"~=<>_:;'; const NAME = '\\b[A-Z][^\\s' + PUNCTUATION + ']'; @@ -132,16 +131,36 @@ function Mention({ const [queryString, setQueryString] = useState<string | null>(null); let options: MentionTypeaheadOption[] = []; - const { users: members } = useMemberQueryByDisplayName({ + const intersectionRef = useRef<HTMLDivElement>(null); + const { + users: members, + hasMore: hasMoreMember, + isLoading: isLoadingMember, + loadMore: loadMoreMember, + } = useMemberQueryByDisplayName({ communityId: communityId || '', displayName: queryString || '', limit: 10, enabled: !!communityId, }); - const { users } = useUserQueryByDisplayName({ + const { users, hasMore, loadMore, isLoading } = useUserQueryByDisplayName({ displayName: queryString || '', limit: 10, }); + useIntersectionObserver({ + onIntersect: () => { + if (communityId) { + if (hasMoreMember && isLoadingMember === false) { + loadMoreMember(); + } + } else { + if (hasMore && isLoading === false) { + loadMore(); + } + } + }, + ref: intersectionRef, + }); const community = useCommunity(communityId || ''); const isPublic = community?.isPublic; @@ -196,22 +215,25 @@ function Mention({ ) => anchorRef.current && options.length > 0 ? ReactDOM.createPortal( - <div className={styles.mentionTextInput_item}> - {options.map((option, i: number) => ( - <CommunityMember - isSelected={selectedIndex === i} - onClick={() => { - setHighlightedIndex(i); - selectOptionAndCleanUp(option); - }} - onMouseEnter={() => { - setHighlightedIndex(i); - }} - key={option.key} - option={option} - /> - ))} - </div>, + <> + <div className={styles.mentionTextInput_item}> + {options.map((option, i: number) => ( + <CommunityMember + isSelected={selectedIndex === i} + onClick={() => { + setHighlightedIndex(i); + selectOptionAndCleanUp(option); + }} + onMouseEnter={() => { + setHighlightedIndex(i); + }} + key={option.key} + option={option} + /> + ))} + </div> + <div ref={intersectionRef} /> + </>, anchorRef.current, ) : null diff --git a/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx b/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx index 0df503dc3..14b000830 100644 --- a/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx +++ b/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx @@ -77,7 +77,7 @@ export type CreatePostParams = { type: string; userIds: string[]; }[]; - attachments: { + attachments?: { fileId: string; type: string; }[]; @@ -248,7 +248,7 @@ const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCr isValid={textValue.text.length > 0 || postImages.length > 0 || postVideos.length > 0} /> </div> - <PostTextField ref={editorRef} onChange={onChange} /> + <PostTextField ref={editorRef} onChange={onChange} communityId={targetId} /> <ImageThumbnail files={incomingImages} uploadedFiles={postImages} From 26222c624a150839f17275c4a7aa08b3d947aa12 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Fri, 12 Jul 2024 11:03:11 +0700 Subject: [PATCH 234/300] fix: button style unset (#524) --- src/v4/core/natives/Button.tsx | 7 ------- src/v4/core/natives/Button/Button.module.css | 4 ++++ src/v4/core/natives/Button/Button.stories.tsx | 15 +++++++++++++++ src/v4/core/natives/Button/Button.tsx | 10 ++++++++++ src/v4/core/natives/Button/index.ts | 1 + 5 files changed, 30 insertions(+), 7 deletions(-) delete mode 100644 src/v4/core/natives/Button.tsx create mode 100644 src/v4/core/natives/Button/Button.module.css create mode 100644 src/v4/core/natives/Button/Button.stories.tsx create mode 100644 src/v4/core/natives/Button/Button.tsx create mode 100644 src/v4/core/natives/Button/index.ts diff --git a/src/v4/core/natives/Button.tsx b/src/v4/core/natives/Button.tsx deleted file mode 100644 index 733aa0f85..000000000 --- a/src/v4/core/natives/Button.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react'; - -import { Button } from 'react-aria-components'; - -export type ButtonProps = React.ComponentProps<typeof Button>; - -export { Button }; diff --git a/src/v4/core/natives/Button/Button.module.css b/src/v4/core/natives/Button/Button.module.css new file mode 100644 index 000000000..257690dee --- /dev/null +++ b/src/v4/core/natives/Button/Button.module.css @@ -0,0 +1,4 @@ +.button { + box-sizing: border-box; + all: unset; +} diff --git a/src/v4/core/natives/Button/Button.stories.tsx b/src/v4/core/natives/Button/Button.stories.tsx new file mode 100644 index 000000000..04324005c --- /dev/null +++ b/src/v4/core/natives/Button/Button.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { Button } from './Button'; + +export default { + title: 'v4/natives/Button', +}; + +export const ButtonStory = { + render: () => { + return <Button>Text</Button>; + }, + + name: 'Button', +}; diff --git a/src/v4/core/natives/Button/Button.tsx b/src/v4/core/natives/Button/Button.tsx new file mode 100644 index 000000000..06102fdbc --- /dev/null +++ b/src/v4/core/natives/Button/Button.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import clsx from 'clsx'; +import { Button as ReactAriaButton } from 'react-aria-components'; +import styles from './Button.module.css'; + +export type ButtonProps = React.ComponentProps<typeof ReactAriaButton>; + +export const Button: React.FC<ButtonProps> = ({ className, ...props }) => { + return <ReactAriaButton className={clsx(styles.button, className)} {...props} />; +}; diff --git a/src/v4/core/natives/Button/index.ts b/src/v4/core/natives/Button/index.ts new file mode 100644 index 000000000..fe9c53c51 --- /dev/null +++ b/src/v4/core/natives/Button/index.ts @@ -0,0 +1 @@ +export { Button } from './Button'; From 8a65e0c2d1072238c716c7b7d25dba109fbb218d Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Fri, 12 Jul 2024 11:24:59 +0700 Subject: [PATCH 235/300] style: change height (#525) --- .../ImageThumbnail/ImageThumbnail.module.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v4/social/internal-components/ImageThumbnail/ImageThumbnail.module.css b/src/v4/social/internal-components/ImageThumbnail/ImageThumbnail.module.css index fc634228b..04a67fa5b 100644 --- a/src/v4/social/internal-components/ImageThumbnail/ImageThumbnail.module.css +++ b/src/v4/social/internal-components/ImageThumbnail/ImageThumbnail.module.css @@ -24,7 +24,7 @@ position: relative; text-align: center; width: 100%; - max-height: 21rem; + height: 21rem; } .thumbnail__wrapper[data-images-height='true'] { From c01da64030ed7dfb26553a73ae525553cedab62c Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Fri, 12 Jul 2024 11:25:15 +0700 Subject: [PATCH 236/300] fix: ASC-00000 - create post menu position (#526) * fix: z-index * style: remove z-index --- .../CreatePostMenu/CreatePostMenu.module.css | 5 +---- .../pages/SocialHomePage/SocialHomePage.module.css | 7 +++++++ src/v4/social/pages/SocialHomePage/SocialHomePage.tsx | 10 +++++----- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/v4/social/components/CreatePostMenu/CreatePostMenu.module.css b/src/v4/social/components/CreatePostMenu/CreatePostMenu.module.css index 1e3742a27..ca6e6512a 100644 --- a/src/v4/social/components/CreatePostMenu/CreatePostMenu.module.css +++ b/src/v4/social/components/CreatePostMenu/CreatePostMenu.module.css @@ -2,9 +2,6 @@ background-color: var(--asc-color-background-default); padding: 0.75rem 1rem; border-radius: 0.75rem; - position: absolute; - right: 1rem; - top: 3.25rem; - width: 12.5rem; + width: 100%; box-shadow: var(--asc-box-shadow-03); } diff --git a/src/v4/social/pages/SocialHomePage/SocialHomePage.module.css b/src/v4/social/pages/SocialHomePage/SocialHomePage.module.css index 660b8f371..604ab0c51 100644 --- a/src/v4/social/pages/SocialHomePage/SocialHomePage.module.css +++ b/src/v4/social/pages/SocialHomePage/SocialHomePage.module.css @@ -46,3 +46,10 @@ .socialHomePage__contents::-webkit-scrollbar { display: none; } + +.socialHomePage__createPostMenu { + position: absolute; + right: 1rem; + top: 3.25rem; + width: 12.5rem; +} diff --git a/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx b/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx index f0770e2a3..6dc72e42d 100644 --- a/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx +++ b/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx @@ -96,11 +96,6 @@ export function SocialHomePage() { createPostButtonRef={createPostButtonRef} /> </div> - {isShowCreatePostMenu && ( - <div ref={createPostMenuRef}> - <CreatePostMenu pageId={pageId} /> - </div> - )} <div className={styles.socialHomePage__tabs}> <NewsfeedButton pageId={pageId} @@ -120,6 +115,11 @@ export function SocialHomePage() { {activeTab === EnumTabNames.Explore && <div>Explore</div>} {activeTab === EnumTabNames.MyCommunities && <MyCommunities pageId={pageId} />} </div> + {isShowCreatePostMenu && ( + <div ref={createPostMenuRef} className={styles.socialHomePage__createPostMenu}> + <CreatePostMenu pageId={pageId} /> + </div> + )} </div> ); } From dea254d3941d717595fa872724f37c8feeaa1619 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Fri, 12 Jul 2024 14:42:17 +0700 Subject: [PATCH 237/300] feat: ASC-00000 - AmityStoryTargetSelectionPage (#528) * feat: AmityStoryTargetSelectionPage * fix: remove console.log --- src/index.ts | 6 +- .../core/providers/CustomizationProvider.tsx | 6 + src/v4/core/providers/NavigationProvider.tsx | 44 +++++++- .../core/providers/PageBehaviorProvider.tsx | 30 +++++ .../CreatePostMenu/CreatePostMenu.tsx | 2 + .../elements/ClearButton/ClearButton.tsx | 2 +- .../CreateStoryButton.module.css | 2 + .../CreateStoryButton/CreateStoryButton.tsx | 4 +- .../CreateStoryButton.module.css | 2 + .../CreateStoryButtons/CreateStoryButton.tsx | 10 +- src/v4/social/pages/Application/index.tsx | 22 ++-- src/v4/social/pages/DraftsPage/DraftsPage.tsx | 5 +- .../StoryTargetSelectionPage.module.css | 55 +++++++++ .../StoryTargetSelectionPage.tsx | 104 ++++++++++++++++++ .../pages/StoryTargetSelectionPage/index.ts | 1 + src/v4/social/pages/index.ts | 1 + 16 files changed, 275 insertions(+), 21 deletions(-) create mode 100644 src/v4/social/pages/StoryTargetSelectionPage/StoryTargetSelectionPage.module.css create mode 100644 src/v4/social/pages/StoryTargetSelectionPage/StoryTargetSelectionPage.tsx create mode 100644 src/v4/social/pages/StoryTargetSelectionPage/index.ts diff --git a/src/index.ts b/src/index.ts index c87480642..c22ea2c78 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,7 +20,11 @@ export { useSDK as useAmitySDK } from '~/core/hooks/useSDK'; // v4 export { default as AmityUIKitManager } from '~/v4/core/AmityUIKitManager'; -export { AmityDraftStoryPage, ViewStoryPage as AmityViewStoryPage } from '~/v4/social/pages'; +export { + AmityDraftStoryPage, + ViewStoryPage as AmityViewStoryPage, + StoryTargetSelectionPage as AmityStoryTargetSelectionPage, +} from '~/v4/social/pages'; export { CommentTray as AmityCommentTrayComponent, StoryTab as AmityStoryTabComponent, diff --git a/src/v4/core/providers/CustomizationProvider.tsx b/src/v4/core/providers/CustomizationProvider.tsx index 543545ff6..9f11687c2 100644 --- a/src/v4/core/providers/CustomizationProvider.tsx +++ b/src/v4/core/providers/CustomizationProvider.tsx @@ -439,6 +439,12 @@ export const defaultConfig: DefaultConfig = { 'select_post_target_page/*/my_timeline_text': { text: 'My Timeline', }, + 'select_story_target_page/*/close_button': { + image: 'platformValue', + }, + 'select_story_target_page/*/title': { + text: 'Share to', + }, '*/*/community_official_badge': { image: 'platformValue', }, diff --git a/src/v4/core/providers/NavigationProvider.tsx b/src/v4/core/providers/NavigationProvider.tsx index 114a2d0d8..633d5cdc5 100644 --- a/src/v4/core/providers/NavigationProvider.tsx +++ b/src/v4/core/providers/NavigationProvider.tsx @@ -20,6 +20,7 @@ export enum PageTypes { DraftPage = 'DraftPage', PostComposerPage = 'PostComposerPage', MyCommunitiesSearchPage = 'MyCommunitiesSearchPage', + StoryTargetSelectionPage = 'StoryTargetSelectionPage', } type Page = @@ -88,7 +89,6 @@ type Page = } | { type: PageTypes.PostComposerPage; - context: { targetId: string | null; targetType: 'community' | 'user'; @@ -96,6 +96,9 @@ type Page = community?: Amity.Community; post?: Amity.Post; }; + } + | { + type: PageTypes.StoryTargetSelectionPage; }; type ContextValue = { @@ -115,6 +118,7 @@ type ContextValue = { goToSocialGlobalSearchPage: (tab?: string) => void; goToMyCommunitiesSearchPage: () => void; goToSelectPostTargetPage: () => void; + goToStoryTargetSelectionPage: () => void; goToDraftStoryPage: (context: { targetId: string; targetType: string; @@ -143,6 +147,12 @@ type ContextValue = { community?: Amity.Community, post?: Amity.Post, ) => void; + goToStoryCreationPage: (context: { + targetId: string; + targetType: Amity.StoryTargetType; + mediaType: { type: 'image'; url: string } | { type: 'video'; url: string }; + storyType: 'communityFeed' | 'globalFeed'; + }) => void; goToSocialHomePage: () => void; }; @@ -172,6 +182,7 @@ let defaultValue: ContextValue = { goToCommunityProfilePage: (communityId: string) => {}, goToSocialGlobalSearchPage: (tab?: string) => {}, goToSelectPostTargetPage: () => {}, + goToStoryTargetSelectionPage: () => {}, goToPostComposerPage: ( mode: Mode, targetId: string | null, @@ -179,6 +190,7 @@ let defaultValue: ContextValue = { community?: Amity.Community, post?: Amity.Post, ) => {}, + goToStoryCreationPage: () => {}, goToSocialHomePage: () => {}, goToMyCommunitiesSearchPage: () => {}, setNavigationBlocker: () => {}, @@ -211,6 +223,8 @@ if (process.env.NODE_ENV !== 'production') { goToSocialGlobalSearchPage: (tab) => console.log(`NavigationContext goToSocialGlobalSearchPage(${tab})`), goToSelectPostTargetPage: () => console.log('NavigationContext goToTargetPage()'), + goToStoryTargetSelectionPage: () => + console.log('NavigationContext goToStoryTargetSelectionPage()'), goToDraftStoryPage: ({ targetId, targetType, mediaType, storyType }) => console.log( `NavigationContext goToDraftStoryPage(${targetId}, ${targetType}, ${mediaType}), ${storyType})`, @@ -219,6 +233,7 @@ if (process.env.NODE_ENV !== 'production') { console.log( `NavigationContext goToPostComposerPage(${mode} ${targetId}) ${targetType} ${community} ${post}`, ), + goToStoryCreationPage: () => console.log('NavigationContext goToStoryCreationPage()'), goToSocialHomePage: () => console.log('NavigationContext goToSocialHomePage()'), goToMyCommunitiesSearchPage: () => console.log('NavigationContext goToMyCommunitiesSearchPage()'), @@ -499,6 +514,31 @@ export default function NavigationProvider({ pushPage(next); }, [onChangePage, pushPage]); + const goToStoryTargetSelectionPage = useCallback(() => { + const next = { + type: PageTypes.StoryTargetSelectionPage, + }; + + pushPage(next); + }, [onChangePage, pushPage]); + + const goToStoryCreationPage = useCallback( + ({ targetId, targetType, mediaType, storyType }) => { + const next = { + type: PageTypes.DraftPage, + context: { + targetId, + targetType, + mediaType, + storyType, + }, + }; + + pushPage(next); + }, + [onChangePage, pushPage], + ); + const goToDraftStoryPage = useCallback( ({ targetId, targetType, mediaType, storyType }) => { const next = { @@ -570,6 +610,8 @@ export default function NavigationProvider({ goToCommunityProfilePage, goToViewStoryPage, goToSelectPostTargetPage, + goToStoryTargetSelectionPage, + goToStoryCreationPage, goToDraftStoryPage, goToPostComposerPage, goToSocialHomePage, diff --git a/src/v4/core/providers/PageBehaviorProvider.tsx b/src/v4/core/providers/PageBehaviorProvider.tsx index 367985b3f..aa310a690 100644 --- a/src/v4/core/providers/PageBehaviorProvider.tsx +++ b/src/v4/core/providers/PageBehaviorProvider.tsx @@ -32,6 +32,7 @@ export interface PageBehavior { }; AmityCreatePostMenuComponentBehavior: { goToSelectPostTargetPage(): void; + goToStoryTargetSelectionPage(): void; }; AmityPostTargetSelectionPage: { goToPostComposerPage: (context: { @@ -42,6 +43,14 @@ export interface PageBehavior { post?: Amity.Post; }) => void; }; + AmityStoryTargetSelectionPage: { + goToStoryCreationPage(context: { + targetId: string | null; + targetType: Amity.StoryTargetType; + mediaType: { type: 'image'; url: string } | { type: 'video'; url: string }; + storyType: 'communityFeed' | 'globalFeed'; + }): void; + }; AmityPostComposerPageBehavior: { goToSocialHomePage(): void; }; @@ -67,6 +76,8 @@ export const PageBehaviorProvider: React.FC<PageBehaviorProviderProps> = ({ goToViewStoryPage, onChangePage, goToSelectPostTargetPage, + goToStoryTargetSelectionPage, + goToStoryCreationPage, goToPostComposerPage, goToSocialHomePage, } = useNavigation(); @@ -154,6 +165,12 @@ export const PageBehaviorProvider: React.FC<PageBehaviorProviderProps> = ({ } goToSelectPostTargetPage(); }, + goToStoryTargetSelectionPage() { + if (pageBehavior?.AmityCreatePostMenuComponentBehavior?.goToStoryTargetSelectionPage) { + return pageBehavior.AmityCreatePostMenuComponentBehavior.goToStoryTargetSelectionPage(); + } + goToStoryTargetSelectionPage(); + }, }, AmityPostTargetSelectionPage: { goToPostComposerPage: (context: { @@ -175,6 +192,19 @@ export const PageBehaviorProvider: React.FC<PageBehaviorProviderProps> = ({ ); }, }, + AmityStoryTargetSelectionPage: { + goToStoryCreationPage: (context: { + targetId: string; + targetType: Amity.StoryTargetType; + mediaType: { type: 'image'; url: string } | { type: 'video'; url: string }; + storyType: 'communityFeed' | 'globalFeed'; + }) => { + if (pageBehavior?.AmityStoryTargetSelectionPage?.goToStoryCreationPage) { + return pageBehavior.AmityStoryTargetSelectionPage.goToStoryCreationPage(context); + } + goToStoryCreationPage(context); + }, + }, AmityPostComposerPageBehavior: { goToSocialHomePage() { if (pageBehavior?.AmityPostComposerPageBehavior?.goToSocialHomePage) { diff --git a/src/v4/social/components/CreatePostMenu/CreatePostMenu.tsx b/src/v4/social/components/CreatePostMenu/CreatePostMenu.tsx index 86801fa8b..e57b3f687 100644 --- a/src/v4/social/components/CreatePostMenu/CreatePostMenu.tsx +++ b/src/v4/social/components/CreatePostMenu/CreatePostMenu.tsx @@ -1,6 +1,7 @@ import React from 'react'; import styles from './CreatePostMenu.module.css'; import { CreatePostButton } from '~/v4/social/elements/CreatePostButton'; +import { CreateStoryButton } from '~/v4/social/elements/CreateStoryButton'; interface CreatePostMenuProps { pageId: string; @@ -11,6 +12,7 @@ export function CreatePostMenu({ pageId }: CreatePostMenuProps) { return ( <div className={styles.createPostMenu}> <CreatePostButton pageId={pageId} componentId={componentId} /> + <CreateStoryButton pageId={pageId} componentId={componentId} /> </div> ); } diff --git a/src/v4/social/elements/ClearButton/ClearButton.tsx b/src/v4/social/elements/ClearButton/ClearButton.tsx index 02b6a5310..e97d838fb 100644 --- a/src/v4/social/elements/ClearButton/ClearButton.tsx +++ b/src/v4/social/elements/ClearButton/ClearButton.tsx @@ -3,7 +3,7 @@ import { IconComponent } from '~/v4/core/IconComponent'; import { useAmityElement } from '~/v4/core/hooks/uikit'; import styles from './ClearButton.module.css'; -import { Button, ButtonProps } from '~/v4/core/natives/Button'; +import { Button } from '~/v4/core/natives/Button'; function ClearButtonSvg(props: React.SVGProps<SVGSVGElement>) { return ( diff --git a/src/v4/social/elements/CreateStoryButton/CreateStoryButton.module.css b/src/v4/social/elements/CreateStoryButton/CreateStoryButton.module.css index 8a15884ee..f1c9eeda5 100644 --- a/src/v4/social/elements/CreateStoryButton/CreateStoryButton.module.css +++ b/src/v4/social/elements/CreateStoryButton/CreateStoryButton.module.css @@ -1,6 +1,8 @@ .createStoryButton { display: flex; padding: 0.75rem 0; + width: 100%; + cursor: pointer; } .createStoryButton__text { diff --git a/src/v4/social/elements/CreateStoryButton/CreateStoryButton.tsx b/src/v4/social/elements/CreateStoryButton/CreateStoryButton.tsx index 1078a7a76..1c8ac0c99 100644 --- a/src/v4/social/elements/CreateStoryButton/CreateStoryButton.tsx +++ b/src/v4/social/elements/CreateStoryButton/CreateStoryButton.tsx @@ -4,6 +4,7 @@ import { useAmityElement } from '~/v4/core/hooks/uikit'; import { Typography } from '~/v4/core/components'; import styles from './CreateStoryButton.module.css'; import clsx from 'clsx'; +import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; const CreateStoryButtonSvg = (props: React.SVGProps<SVGSVGElement>) => ( <svg width="17" height="18" viewBox="0 0 17 18" xmlns="http://www.w3.org/2000/svg" {...props}> @@ -36,12 +37,13 @@ export function CreateStoryButton({ componentId, elementId, }); + const { AmityCreatePostMenuComponentBehavior } = usePageBehavior(); if (isExcluded) return null; return ( <div className={styles.createStoryButton} - onClick={() => {}} //TODO : Add event create story + onClick={() => AmityCreatePostMenuComponentBehavior.goToStoryTargetSelectionPage()} data-qa-anchor={accessibilityId} style={themeStyles} > diff --git a/src/v4/social/elements/CreateStoryButtons/CreateStoryButton.module.css b/src/v4/social/elements/CreateStoryButtons/CreateStoryButton.module.css index 8a15884ee..600d3bae8 100644 --- a/src/v4/social/elements/CreateStoryButtons/CreateStoryButton.module.css +++ b/src/v4/social/elements/CreateStoryButtons/CreateStoryButton.module.css @@ -1,6 +1,8 @@ .createStoryButton { display: flex; padding: 0.75rem 0; + cursor: pointer; + width: 100%; } .createStoryButton__text { diff --git a/src/v4/social/elements/CreateStoryButtons/CreateStoryButton.tsx b/src/v4/social/elements/CreateStoryButtons/CreateStoryButton.tsx index 1078a7a76..540c5c231 100644 --- a/src/v4/social/elements/CreateStoryButtons/CreateStoryButton.tsx +++ b/src/v4/social/elements/CreateStoryButtons/CreateStoryButton.tsx @@ -2,8 +2,10 @@ import React from 'react'; import { IconComponent } from '~/v4/core/IconComponent'; import { useAmityElement } from '~/v4/core/hooks/uikit'; import { Typography } from '~/v4/core/components'; +import { Button } from '~/v4/core/natives/Button'; import styles from './CreateStoryButton.module.css'; import clsx from 'clsx'; +import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; const CreateStoryButtonSvg = (props: React.SVGProps<SVGSVGElement>) => ( <svg width="17" height="18" viewBox="0 0 17 18" xmlns="http://www.w3.org/2000/svg" {...props}> @@ -37,11 +39,13 @@ export function CreateStoryButton({ elementId, }); + const { AmityCreatePostMenuComponentBehavior } = usePageBehavior(); + if (isExcluded) return null; return ( - <div + <Button className={styles.createStoryButton} - onClick={() => {}} //TODO : Add event create story + onPress={() => AmityCreatePostMenuComponentBehavior.goToStoryTargetSelectionPage()} data-qa-anchor={accessibilityId} style={themeStyles} > @@ -56,7 +60,7 @@ export function CreateStoryButton({ defaultIconName={defaultConfig.image} /> <Typography.Body className={styles.createStoryButton__text}>{config.text}</Typography.Body> - </div> + </Button> ); } diff --git a/src/v4/social/pages/Application/index.tsx b/src/v4/social/pages/Application/index.tsx index 124c1c230..6afaecd33 100644 --- a/src/v4/social/pages/Application/index.tsx +++ b/src/v4/social/pages/Application/index.tsx @@ -12,6 +12,7 @@ import { MyCommunitiesSearchPage } from '../MyCommunitiesSearchPage/MyCommunitie import styles from './Application.module.css'; import { AmityDraftStoryPage } from '..'; +import { StoryTargetSelectionPage } from '~/v4/social/pages/StoryTargetSelectionPage'; const Application = () => { const { page } = useNavigation(); @@ -21,24 +22,25 @@ const Application = () => { <div className={styles.applicationContainer}> {page.type === PageTypes.SocialHomePage && <SocialHomePage />} {page.type === PageTypes.SocialGlobalSearchPage && <SocialGlobalSearchPage />} - {page.type === PageTypes.PostDetailPage && <PostDetailPage id={page.context.postId} />} + {page.type === PageTypes.PostDetailPage && <PostDetailPage id={page.context?.postId} />} + {page.type === PageTypes.StoryTargetSelectionPage && <StoryTargetSelectionPage />} {page.type === PageTypes.ViewStoryPage && ( - <ViewStoryPage type="globalFeed" targetId={page.context.targetId} /> + <ViewStoryPage type="globalFeed" targetId={page.context?.targetId} /> )} {page.type === PageTypes.DraftPage && ( <AmityDraftStoryPage - targetId={page.context.targetId} - targetType={page.context.targetType} - mediaType={page.context.mediaType} + targetId={page.context?.targetId} + targetType={page.context?.targetType} + mediaType={page.context?.mediaType} /> )} {page.type === PageTypes.PostComposerPage && ( <PostComposerPage - mode={page.context.mode} - targetId={page.context.targetId} - targetType={page.context.targetType} - community={page.context.community} - post={page.context.post} + mode={page.context?.mode} + targetId={page.context?.targetId} + targetType={page.context?.targetType} + community={page.context?.community} + post={page.context?.post} /> )} {page.type === PageTypes.SelectPostTargetPage && <SelectPostTargetPage />} diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index f0ff834cf..8f778b5e2 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -48,12 +48,10 @@ export const PlainDraftStoryPage = ({ }) => { const { page } = useNavigation(); const pageId = 'create_story_page'; - const { accessibilityId, themeStyles, isExcluded } = useAmityPage({ + const { accessibilityId, themeStyles } = useAmityPage({ pageId, }); - if (isExcluded) return null; - const { file, setFile } = useStoryContext(); const { community } = useCommunityInfo(targetId); const [isHyperLinkBottomSheetOpen, setIsHyperLinkBottomSheetOpen] = useState(false); @@ -293,7 +291,6 @@ export const PlainDraftStoryPage = ({ }; export const AmityDraftStoryPage = (props: AmityDraftStoryPageProps) => { - const { page } = useNavigation(); const { AmityDraftStoryPageBehavior } = usePageBehavior(); return ( diff --git a/src/v4/social/pages/StoryTargetSelectionPage/StoryTargetSelectionPage.module.css b/src/v4/social/pages/StoryTargetSelectionPage/StoryTargetSelectionPage.module.css new file mode 100644 index 000000000..b0413461a --- /dev/null +++ b/src/v4/social/pages/StoryTargetSelectionPage/StoryTargetSelectionPage.module.css @@ -0,0 +1,55 @@ +.selectStoryTargetPage { + max-width: 100vw; + flex: 1; + padding: var(--asc-spacing-none) var(--asc-spacing-m1); + background-color: var(--asc-color-background-default); +} + +.selectStoryTargetPage__topBar { + display: flex; + justify-content: space-between; + align-items: center; + padding: var(--asc-spacing-m1); + background-color: var(--asc-color-background-default); +} + +.selectStoryTargetPage__timeline { + display: flex; + align-items: center; + padding: var(--asc-spacing-s1) var(--asc-spacing-none); +} + +.selectStoryTargetPage__line { + margin-top: var(--asc-spacing-s1); + border-top: 1px solid var(--asc-color-base-shade4); +} + +.selectStoryTargetPage__myCommunities__container { + display: flex; + flex-direction: column; + padding: var(--asc-spacing-m1) var(--asc-spacing-none); + flex: 1; + height: 100%; +} + +.selectStoryTargetPage__myCommunities { + display: flex; + padding: 0.5rem 3.25rem 0.5rem 1rem; + gap: 0.75rem; + cursor: pointer; + align-items: center; +} + +.selectStoryTargetPage__communityAvatar { + margin-right: var(--asc-spacing-s1); +} + +.selectStoryTargetPage__closeButton { + display: flex; + fill: var(--asc-color-base-default); +} + +.selectStoryTargetPage__title { + color: var(--asc-color-base-default); + text-align: center; +} diff --git a/src/v4/social/pages/StoryTargetSelectionPage/StoryTargetSelectionPage.tsx b/src/v4/social/pages/StoryTargetSelectionPage/StoryTargetSelectionPage.tsx new file mode 100644 index 000000000..9d7318157 --- /dev/null +++ b/src/v4/social/pages/StoryTargetSelectionPage/StoryTargetSelectionPage.tsx @@ -0,0 +1,104 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { useAmityPage } from '~/v4/core/hooks/uikit'; +import { CloseButton } from '~/v4/social/elements/CloseButton/CloseButton'; +import { Title } from '~/v4/social/elements/Title/Title'; +import { useNavigation } from '~/v4/core/providers/NavigationProvider'; +import { CommunityOfficialBadge } from '~/v4/social/elements/CommunityOfficialBadge'; +import useCommunitiesCollection from '~/v4/social/hooks/collections/useCommunitiesCollection'; +import { CommunityPrivateBadge } from '~/v4/social/elements/CommunityPrivateBadge'; +import { CommunityDisplayName } from '~/v4/social/elements/CommunityDisplayName'; +import { CommunityAvatar } from '~/v4/social/elements/CommunityAvatar'; +import useIntersectionObserver from '~/v4/core/hooks/useIntersectionObserver'; +import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; +import { FileTrigger } from 'react-aria-components'; +import { Button } from '~/v4/core/natives/Button'; + +import styles from './StoryTargetSelectionPage.module.css'; +import { useStoryContext } from '~/v4/social/providers/StoryProvider'; + +export function StoryTargetSelectionPage() { + const pageId = 'select_story_target_page'; + const { themeStyles } = useAmityPage({ + pageId, + }); + const { onBack } = useNavigation(); + const { communities, hasMore, loadMore, isLoading } = useCommunitiesCollection({ + queryParams: { sortBy: 'displayName', limit: 20, membership: 'member' }, + }); + const { AmityStoryTargetSelectionPage } = usePageBehavior(); + const { file, setFile } = useStoryContext(); + const intersectionRef = useRef<HTMLDivElement>(null); + const [selectedCommunityId, setSelectedCommunityId] = useState<string | null>(null); + + const handleOnClickCommunity = (communityId: string) => { + setSelectedCommunityId(communityId); + }; + + const handleFileSelect = (files: FileList | null) => { + if (files && files.length > 0 && selectedCommunityId) { + setFile(files[0]); + } + }; + + const renderCommunity = communities.map((community) => { + return ( + <FileTrigger key={community.communityId} onSelect={handleFileSelect}> + <Button onPress={() => handleOnClickCommunity(community.communityId)}> + <div className={styles.selectStoryTargetPage__myCommunities}> + <div className={styles.selectStoryTargetPage__communityAvatar}> + <CommunityAvatar pageId={pageId} community={community} /> + </div> + <CommunityDisplayName pageId={pageId} community={community} /> + <div> + {community.isOfficial && <CommunityOfficialBadge />} + {!community.isPublic && <CommunityPrivateBadge />} + </div> + </div> + </Button> + </FileTrigger> + ); + }); + + useIntersectionObserver({ + onIntersect: () => { + if (hasMore && isLoading === false) { + loadMore(); + } + }, + ref: intersectionRef, + }); + + useEffect(() => { + if (file) { + AmityStoryTargetSelectionPage.goToStoryCreationPage({ + targetId: selectedCommunityId, + targetType: 'community', + mediaType: + file && file?.type.includes('image') + ? { type: 'image', url: URL.createObjectURL(file) } + : { type: 'video', url: URL.createObjectURL(file!) }, + storyType: 'globalFeed', + }); + } + }, [file]); + + return ( + <div className={styles.selectStoryTargetPage} style={themeStyles}> + <div className={styles.selectStoryTargetPage__topBar}> + <CloseButton + imgClassName={styles.selectStoryTargetPage__closeButton} + pageId={pageId} + onPress={onBack} + /> + <Title pageId={pageId} titleClassName={styles.selectStoryTargetPage__title} /> + <div /> + </div> + <div className={styles.selectStoryTargetPage__line} /> + <div className={styles.selectStoryTargetPage__myCommunities}>My Communities</div> + <div className={styles.selectStoryTargetPage__myCommunities__container}> + {renderCommunity} + </div> + <div ref={intersectionRef} /> + </div> + ); +} diff --git a/src/v4/social/pages/StoryTargetSelectionPage/index.ts b/src/v4/social/pages/StoryTargetSelectionPage/index.ts new file mode 100644 index 000000000..d963355c0 --- /dev/null +++ b/src/v4/social/pages/StoryTargetSelectionPage/index.ts @@ -0,0 +1 @@ +export { StoryTargetSelectionPage } from './StoryTargetSelectionPage'; diff --git a/src/v4/social/pages/index.ts b/src/v4/social/pages/index.ts index 55c7a0ff8..015555f59 100644 --- a/src/v4/social/pages/index.ts +++ b/src/v4/social/pages/index.ts @@ -1,2 +1,3 @@ export { AmityDraftStoryPage } from './DraftsPage'; export { ViewStoryPage } from './StoryPage'; +export { StoryTargetSelectionPage } from './StoryTargetSelectionPage'; From 80bc06f57275be6c742f8ea5c1cc437e95e809bf Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Fri, 12 Jul 2024 15:15:51 +0700 Subject: [PATCH 238/300] fix: comment ad style (#527) --- .../social/internal-components/CommentAd/UICommentAd.module.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v4/social/internal-components/CommentAd/UICommentAd.module.css b/src/v4/social/internal-components/CommentAd/UICommentAd.module.css index 7376c0f37..9e245d37e 100644 --- a/src/v4/social/internal-components/CommentAd/UICommentAd.module.css +++ b/src/v4/social/internal-components/CommentAd/UICommentAd.module.css @@ -32,7 +32,7 @@ background-color: var(--asc-color-base-shade4); border-radius: 0 0.75rem 0.75rem; padding: 0.75rem; - max-width: max-content; + width: 100%; } .commentAd__content__advertiserName__container { From bd290717ed6bc60d50d65d3a9c19bdc9afcce573 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Fri, 12 Jul 2024 15:32:40 +0700 Subject: [PATCH 239/300] fix: ASC-23586 - livechat customization (#520) * feat: lexical * fix: restructure LiveChat # Conflicts: # src/v4/core/providers/AmityUIKitProvider.tsx # src/v4/social/components/PostContent/ImageContent/ImageContent.tsx # src/v4/social/components/PostContent/VideoContent/VideoContent.tsx * fix: a new MessageComposer * chore: remove packageManager field * fix: fix import * fix: type error * fix: enter to send only on h/w keyboard * chore: update LiveChate storybook * fix: update livechat configuration * chore: update src/v4/chat/elements/SenderMessageBubble/SenderMessageBubble.tsx Co-authored-by: Pitchaya T. <33589608+ptchayap@users.noreply.github.com> * chore: apply suggestions from code review Co-authored-by: Pitchaya T. <33589608+ptchayap@users.noreply.github.com> --------- Co-authored-by: Pitchaya T. <33589608+ptchayap@users.noreply.github.com> --- amity-uikit.config.json | 34 +- package.json | 5 +- pnpm-lock.yaml | 404 ++++++++++-------- src/index.ts | 19 +- src/utils.ts | 33 ++ .../AmityLiveChatMessageComposeBar/index.tsx | 167 -------- .../styles.module.css | 75 ---- .../messageReceiver.stories.tsx | 39 -- .../ChatHeader.module.css} | 0 .../ChatHeader.stories.tsx} | 14 +- .../index.tsx => ChatHeader/ChatHeader.tsx} | 23 +- src/v4/chat/components/ChatHeader/index.tsx | 1 + .../MessageComposer.module.css | 187 ++++++++ .../MessageComposer.stories.tsx} | 14 +- .../MessageComposer/MessageComposer.tsx | 329 ++++++++++++++ .../chat/components/MessageComposer/index.tsx | 1 + .../LiveChatLoadingIndicator.module.css | 13 + .../LiveChatLoadingIndicator.tsx} | 28 +- .../MessageList.module.css} | 0 .../MessageList.stories.tsx} | 8 +- .../index.tsx => MessageList/MessageList.tsx} | 52 ++- src/v4/chat/components/MessageList/index.tsx | 1 + ...le.css => MessageQuickReaction.module.css} | 0 .../MessageQuickReaction.tsx | 46 ++ .../components/MessageQuickReaction/index.tsx | 47 +- src/v4/chat/components/index.ts | 12 - .../ReceiverMessageBubble.tsx} | 26 +- .../elements/ReceiverMessageBubble/index.tsx | 1 + .../SenderMessageBubble.stories.tsx} | 8 +- .../SenderMessageBubble.tsx} | 20 +- .../elements/SenderMessageBubble/index.tsx | 1 + .../hooks/collections/useSearchChannelUser.ts | 26 +- src/v4/chat/hooks/useChannel.ts | 12 +- src/v4/chat/hooks/useChannelPermission.ts | 12 +- src/v4/chat/hooks/useCommunity.ts | 24 +- .../hooks/useCurrentUserChannelMembership.ts | 10 +- src/v4/chat/hooks/useMention.ts | 4 +- .../HomeIndicator/index.tsx | 4 +- .../HomeIndicator/styles.module.css | 0 .../MessageAction/index.tsx | 36 +- .../MessageAction/styles.module.css | 0 .../MessageBubble/index.tsx | 4 +- .../MessageBubble/styles.module.css | 0 .../MessageBubbleContainer/index.tsx | 0 .../MessageBubbleContainer/styles.module.css | 0 .../MessageReaction/index.tsx | 0 .../MessageReaction/styles.module.css | 0 .../MessageTextWithMention/index.tsx | 0 .../MessageTextWithMention/styles.module.css | 0 .../LiveChatMessageContent/index.tsx | 29 +- .../LiveChatMessageContent/styles.module.css | 0 .../LiveChatNotification.module.css} | 2 - .../LiveChatNotification.tsx} | 7 +- .../LiveChatNotification/index.tsx | 1 + .../pages/AmityLiveChatPage/styles.module.css | 24 -- .../ChatContainer/ChatCustomState.tsx | 0 .../ChatContainer/ChatLoadingState.tsx | 7 +- .../ChatContainer/ChatReadyState.tsx | 40 +- .../ChatContainer/ReplyMessagePlaceholder.tsx | 15 +- .../ChatContainer/index.tsx | 0 .../ChatContainer/styles.module.css | 0 .../chat/pages/LiveChat/LiveChat.module.css | 11 + .../LiveChat.stories.tsx} | 24 +- .../index.tsx => LiveChat/LiveChat.tsx} | 21 +- src/v4/chat/pages/LiveChat/index.tsx | 1 + src/v4/chat/pages/index.ts | 2 +- src/v4/core/AmityUIKitManager.ts | 4 +- src/v4/core/hooks/objects/useUser.ts | 4 +- .../useCommunityReactionSubscription.ts | 4 +- .../subscriptions/useReactionSubscription.ts | 8 +- .../useUserReactionSubscription.ts | 4 +- src/v4/core/hooks/useImage.ts | 4 +- src/v4/core/providers/AmityUIKitProvider.tsx | 2 +- src/v4/helpers/utils.ts | 32 ++ .../PostCommentComposer.tsx | 4 +- .../PostCommentComposer/PostCommentInput.tsx | 14 +- .../PostContent/ImageContent/ImageContent.tsx | 2 +- .../components/PostContent/PostContent.tsx | 2 +- .../PostContent/VideoContent/VideoContent.tsx | 2 +- .../components/StoryTab/StoryTabCommunity.tsx | 2 +- .../components/StoryTab/StoryTabItem.tsx | 2 +- src/v4/social/hooks/useCommunityInfo.ts | 2 +- src/v4/social/hooks/useGetStoryByStoryId.ts | 2 +- .../hooks/useMemberQueryByDisplayName.ts | 2 +- .../internal-components/Comment/index.tsx | 2 +- .../CommentAd/CommentAd.tsx | 2 +- .../Lexical/nodes/MentionNode.module.css | 3 + .../Lexical/nodes/MentionNode.ts | 154 +++++++ .../Lexical/plugins/AutoLinkPlugin.tsx | 24 ++ .../plugins/EnterKeyInterceptorPlugin.tsx | 68 +++ .../Lexical/plugins/LinkPlugin.tsx | 5 + .../Lexical/plugins/MentionPlugin.tsx | 173 ++++++++ .../internal-components/Lexical/utils.ts | 252 +++++++++++ .../MentionTextInput/MentionTextInput.tsx | 2 +- .../internal-components/PostAd/PostAd.tsx | 2 +- .../StoryViewer/Renderers/Image.tsx | 2 +- .../StoryViewer/Renderers/Video.tsx | 2 +- .../StoryViewer/Renderers/storyAd.tsx | 2 +- .../UserAvatar/UserAvatar.tsx | 4 +- .../SelectPostTargetPage.tsx | 2 +- 100 files changed, 1842 insertions(+), 877 deletions(-) delete mode 100644 src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx delete mode 100644 src/v4/chat/components/AmityLiveChatMessageComposeBar/styles.module.css delete mode 100644 src/v4/chat/components/AmityLiveChatMessageReceiverView/messageReceiver.stories.tsx rename src/v4/chat/components/{AmityLiveChatHeader/styles.module.css => ChatHeader/ChatHeader.module.css} (100%) rename src/v4/chat/components/{AmityLiveChatHeader/livechatHeader.stories.tsx => ChatHeader/ChatHeader.stories.tsx} (69%) rename src/v4/chat/components/{AmityLiveChatHeader/index.tsx => ChatHeader/ChatHeader.tsx} (68%) create mode 100644 src/v4/chat/components/ChatHeader/index.tsx create mode 100644 src/v4/chat/components/MessageComposer/MessageComposer.module.css rename src/v4/chat/components/{AmityLiveChatMessageComposeBar/livechatMessageComposeBar.stories.tsx => MessageComposer/MessageComposer.stories.tsx} (64%) create mode 100644 src/v4/chat/components/MessageComposer/MessageComposer.tsx create mode 100644 src/v4/chat/components/MessageComposer/index.tsx create mode 100644 src/v4/chat/components/MessageList/LiveChatLoadingIndicator.module.css rename src/v4/chat/components/{LivechatLoadingIndicator/index.tsx => MessageList/LiveChatLoadingIndicator.tsx} (87%) rename src/v4/chat/components/{AmityLiveChatMessageList/styles.module.css => MessageList/MessageList.module.css} (100%) rename src/v4/chat/components/{AmityLiveChatMessageList/livechatMessageList.stories.tsx => MessageList/MessageList.stories.tsx} (84%) rename src/v4/chat/components/{AmityLiveChatMessageList/index.tsx => MessageList/MessageList.tsx} (75%) create mode 100644 src/v4/chat/components/MessageList/index.tsx rename src/v4/chat/components/MessageQuickReaction/{styles.module.css => MessageQuickReaction.module.css} (100%) create mode 100644 src/v4/chat/components/MessageQuickReaction/MessageQuickReaction.tsx delete mode 100644 src/v4/chat/components/index.ts rename src/v4/chat/{components/AmityLiveChatMessageSenderView/index.tsx => elements/ReceiverMessageBubble/ReceiverMessageBubble.tsx} (51%) create mode 100644 src/v4/chat/elements/ReceiverMessageBubble/index.tsx rename src/v4/chat/{components/AmityLiveChatMessageSenderView/messageSender.stories.tsx => elements/SenderMessageBubble/SenderMessageBubble.stories.tsx} (84%) rename src/v4/chat/{components/AmityLiveChatMessageReceiverView/index.tsx => elements/SenderMessageBubble/SenderMessageBubble.tsx} (64%) create mode 100644 src/v4/chat/elements/SenderMessageBubble/index.tsx rename src/v4/chat/{components => internal-components}/HomeIndicator/index.tsx (76%) rename src/v4/chat/{components => internal-components}/HomeIndicator/styles.module.css (100%) rename src/v4/chat/{components => internal-components}/LiveChatMessageContent/MessageAction/index.tsx (77%) rename src/v4/chat/{components => internal-components}/LiveChatMessageContent/MessageAction/styles.module.css (100%) rename src/v4/chat/{components => internal-components}/LiveChatMessageContent/MessageBubble/index.tsx (93%) rename src/v4/chat/{components => internal-components}/LiveChatMessageContent/MessageBubble/styles.module.css (100%) rename src/v4/chat/{components => internal-components}/LiveChatMessageContent/MessageBubbleContainer/index.tsx (100%) rename src/v4/chat/{components => internal-components}/LiveChatMessageContent/MessageBubbleContainer/styles.module.css (100%) rename src/v4/chat/{components => internal-components}/LiveChatMessageContent/MessageReaction/index.tsx (100%) rename src/v4/chat/{components => internal-components}/LiveChatMessageContent/MessageReaction/styles.module.css (100%) rename src/v4/chat/{components => internal-components}/LiveChatMessageContent/MessageTextWithMention/index.tsx (100%) rename src/v4/chat/{components => internal-components}/LiveChatMessageContent/MessageTextWithMention/styles.module.css (100%) rename src/v4/chat/{components => internal-components}/LiveChatMessageContent/index.tsx (80%) rename src/v4/chat/{components => internal-components}/LiveChatMessageContent/styles.module.css (100%) rename src/v4/chat/{components/LiveChatNotification/styles.module.css => internal-components/LiveChatNotification/LiveChatNotification.module.css} (96%) rename src/v4/chat/{components/LiveChatNotification/index.tsx => internal-components/LiveChatNotification/LiveChatNotification.tsx} (82%) create mode 100644 src/v4/chat/internal-components/LiveChatNotification/index.tsx delete mode 100644 src/v4/chat/pages/AmityLiveChatPage/styles.module.css rename src/v4/chat/pages/{AmityLiveChatPage => LiveChat}/ChatContainer/ChatCustomState.tsx (100%) rename src/v4/chat/pages/{AmityLiveChatPage => LiveChat}/ChatContainer/ChatLoadingState.tsx (76%) rename src/v4/chat/pages/{AmityLiveChatPage => LiveChat}/ChatContainer/ChatReadyState.tsx (69%) rename src/v4/chat/pages/{AmityLiveChatPage => LiveChat}/ChatContainer/ReplyMessagePlaceholder.tsx (69%) rename src/v4/chat/pages/{AmityLiveChatPage => LiveChat}/ChatContainer/index.tsx (100%) rename src/v4/chat/pages/{AmityLiveChatPage => LiveChat}/ChatContainer/styles.module.css (100%) create mode 100644 src/v4/chat/pages/LiveChat/LiveChat.module.css rename src/v4/chat/pages/{AmityLiveChatPage/livechatPage.stories.tsx => LiveChat/LiveChat.stories.tsx} (76%) rename src/v4/chat/pages/{AmityLiveChatPage/index.tsx => LiveChat/LiveChat.tsx} (51%) create mode 100644 src/v4/chat/pages/LiveChat/index.tsx create mode 100644 src/v4/social/internal-components/Lexical/nodes/MentionNode.module.css create mode 100644 src/v4/social/internal-components/Lexical/nodes/MentionNode.ts create mode 100644 src/v4/social/internal-components/Lexical/plugins/AutoLinkPlugin.tsx create mode 100644 src/v4/social/internal-components/Lexical/plugins/EnterKeyInterceptorPlugin.tsx create mode 100644 src/v4/social/internal-components/Lexical/plugins/LinkPlugin.tsx create mode 100644 src/v4/social/internal-components/Lexical/plugins/MentionPlugin.tsx create mode 100644 src/v4/social/internal-components/Lexical/utils.ts diff --git a/amity-uikit.config.json b/amity-uikit.config.json index 3b0980017..8ea623778 100644 --- a/amity-uikit.config.json +++ b/amity-uikit.config.json @@ -177,28 +177,46 @@ "live_chat/*/*": { "theme": { "light": { - "primary_color": "#1054DE", - "secondary_color": "#ebecef", + "primary_color": "#1054de", + "base_inverse_color": "#fff", "base_color": "#ebecef", "base_shade1_color": "#a5a9b5", "base_shade2_color": "#6e7487", "base_shade3_color": "#40434e", "base_shade4_color": "#292b32", - "alert_color": "#FA4D30", + "secondary_color": "#292b32", + "secondary_shade1_color": "#a5a9b5", + "secondary_shade2_color": "#898e9e", + "secondary_shade3_color": "#a5a9b5", + "secondary_shade4_color": "#40434e", + "secondary_shade5_color": "#292b32", + "alert_color": "#fa4d30", + "highlight_color": "#1054de", + "message_bubble_primary_color": "#1054de", + "message_bubble_secondary_color": "#292b32", "background_color": "#191919", - "base_inverse_color": "#FFFFFF" + "background_shade1_color": "#40434e" }, "dark": { - "primary_color": "#1054DE", - "secondary_color": "#ebecef", + "primary_color": "#1054de", + "base_inverse_color": "#fff", "base_color": "#ebecef", "base_shade1_color": "#a5a9b5", "base_shade2_color": "#6e7487", "base_shade3_color": "#40434e", "base_shade4_color": "#292b32", - "alert_color": "#FA4D30", + "secondary_color": "#292b32", + "secondary_shade1_color": "#a5a9b5", + "secondary_shade2_color": "#898e9e", + "secondary_shade3_color": "#a5a9b5", + "secondary_shade4_color": "#40434e", + "secondary_shade5_color": "#292b32", + "alert_color": "#fa4d30", + "highlight_color": "#1054de", + "message_bubble_primary_color": "#1054de", + "message_bubble_secondary_color": "#292b32", "background_color": "#191919", - "base_inverse_color": "#FFFFFF" + "background_shade1_color": "#40434e" } } }, diff --git a/package.json b/package.json index 657007bc6..cdf9d11f0 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,8 @@ "@fortawesome/react-fontawesome": "^0.2.0", "@hookform/error-message": "^2.0.1", "@hookform/resolvers": "^3.3.4", - "@lexical/react": "^0.16.0", + "@lexical/link": "^0.16.1", + "@lexical/react": "^0.16.1", "@radix-ui/react-tabs": "^1.0.4", "@tanstack/react-query": "^5.28.14", "clsx": "^2.1.0", @@ -116,7 +117,7 @@ "filesize": "^9.0.11", "framer-motion": "^11.1.7", "hls.js": "^1.4.14", - "lexical": "^0.16.0", + "lexical": "^0.16.1", "linkify-react": "^4.1.3", "linkifyjs": "^4.1.3", "lodash": "^4.17.21", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9db36036e..7ac64fee4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,9 +23,12 @@ importers: '@hookform/resolvers': specifier: ^3.3.4 version: 3.6.0(react-hook-form@7.52.0(react@18.3.1)) + '@lexical/link': + specifier: ^0.16.1 + version: 0.16.1 '@lexical/react': - specifier: ^0.16.0 - version: 0.16.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(yjs@13.6.17) + specifier: ^0.16.1 + version: 0.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(yjs@13.6.17) '@radix-ui/react-tabs': specifier: ^1.0.4 version: 1.0.4(@types/react-dom@18.3.0)(@types/react@17.0.80)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -51,8 +54,8 @@ importers: specifier: ^1.4.14 version: 1.5.11 lexical: - specifier: ^0.16.0 - version: 0.16.0 + specifier: ^0.16.1 + version: 0.16.1 linkify-react: specifier: ^4.1.3 version: 4.1.3(linkifyjs@4.1.3)(react@18.3.1) @@ -164,7 +167,7 @@ importers: version: 7.6.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@4.9.5) '@storybook/react-vite': specifier: ^7.6.7 - version: 7.6.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.18.0)(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1)) + version: 7.6.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.18.0)(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2)) '@storybook/theming': specifier: ^7.6.7 version: 7.6.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -209,7 +212,7 @@ importers: version: 6.21.0(eslint@8.57.0)(typescript@4.9.5) '@vitejs/plugin-react': specifier: ^4.2.1 - version: 4.3.1(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1)) + version: 4.3.1(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2)) autoprefixer: specifier: ^10.4.19 version: 10.4.19(postcss@8.4.38) @@ -293,10 +296,10 @@ importers: version: 5.1.0(typescript@4.9.5) vite: specifier: ^4.5.1 - version: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1) + version: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2) vite-tsconfig-paths: specifier: ^4.2.3 - version: 4.3.2(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1)) + version: 4.3.2(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2)) packages: @@ -1524,74 +1527,74 @@ packages: '@juggle/resize-observer@3.4.0': resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==} - '@lexical/clipboard@0.16.0': - resolution: {integrity: sha512-eYMJ6jCXpWBVC05Mu9HLMysrBbfi++xFfsm+Yo7A6kYGrqYUhpXqjJkYnw1xdZYL3bV73Oe4ByVJuq42GU+Mqw==} + '@lexical/clipboard@0.16.1': + resolution: {integrity: sha512-0dWs/SwKS5KPpuf6fUVVt9vSCl6HAqcDGhSITw/okv0rrIlXTUT6WhVsMJtXfFxTyVvwMeOecJHvQH3i/jRQtA==} - '@lexical/code@0.16.0': - resolution: {integrity: sha512-1EKCBSFV745UI2zn5v75sKcvVdmd+y2JtZhw8CItiQkRnBLv4l4d/RZYy+cKOuXJGsoBrKtxXn5sl7HebwQbPw==} + '@lexical/code@0.16.1': + resolution: {integrity: sha512-pOC28rRZ2XkmI2nIJm50DbKaCJtk5D0o7r6nORYp4i0z+lxt5Sf2m82DL9ksUHJRqKy87pwJDpoWvJ2SAI0ohw==} - '@lexical/devtools-core@0.16.0': - resolution: {integrity: sha512-Jt8p0J0UoMHf3UMh3VdyrXbLLwpEZuMqihTmbPRpwo+YQ6NGQU35QgwY2K0DpPAThpxL/Cm7uaFqGOy8Kjrhqw==} + '@lexical/devtools-core@0.16.1': + resolution: {integrity: sha512-8CvGERGL7ySDVGLU+YPeq+JupIXsOFlXa3EuJ88koLKqXxYenwMleZgGqayFp6lCP78xqPKnATVeoOZUt/NabQ==} peerDependencies: react: '>=17.x' react-dom: '>=17.x' - '@lexical/dragon@0.16.0': - resolution: {integrity: sha512-Yr29SFZzOPs+S6UrEZaXnnso1fJGVfZOXVJQZbyzlspqJpSHXVH7InOXYHWN6JSWQ8Hs/vU3ksJXwqz+0TCp2g==} + '@lexical/dragon@0.16.1': + resolution: {integrity: sha512-Rvd60GIYN5kpjjBumS34EnNbBaNsoseI0AlzOdtIV302jiHPCLH0noe9kxzu9nZy+MZmjZy8Dx2zTbQT2mueRw==} - '@lexical/hashtag@0.16.0': - resolution: {integrity: sha512-2EdAvxYVYqb0nv6vgxCRgE8ip7yez5p0y0oeUyxmdbcfZdA+Jl90gYH3VdevmZ5Bk3wE0/fIqiLD+Bb5smqjCQ==} + '@lexical/hashtag@0.16.1': + resolution: {integrity: sha512-G+YOxStAKs3q1utqm9KR4D4lCkwIH52Rctm4RgaVTI+4lvTaybeDRGFV75P/pI/qlF7/FvAYHTYEzCjtC3GNMQ==} - '@lexical/history@0.16.0': - resolution: {integrity: sha512-xwFxgDZGviyGEqHmgt6A6gPhsyU/yzlKRk9TBUVByba3khuTknlJ1a80H5jb+OYcrpiElml7iVuGYt+oC7atCA==} + '@lexical/history@0.16.1': + resolution: {integrity: sha512-WQhScx0TJeKSQAnEkRpIaWdUXqirrNrom2MxbBUc/32zEUMm9FzV7nRGknvUabEFUo7vZq6xTZpOExQJqHInQA==} - '@lexical/html@0.16.0': - resolution: {integrity: sha512-okxn3q/1qkUpCZNEFRI39XeJj4YRjb6prm3WqZgP4d39DI1W24feeTZJjYRCW+dc3NInwFaolU3pNA2MGkjRtg==} + '@lexical/html@0.16.1': + resolution: {integrity: sha512-vbtAdCvQ3PaAqa5mFmtmrvbiAvjCu1iXBAJ0bsHqFXCF2Sba5LwHVe8dUAOTpfEZEMbiHfjul6b5fj4vNPGF2A==} - '@lexical/link@0.16.0': - resolution: {integrity: sha512-ppvJSh/XGqlzbeymOiwcXJcUcrqgQqTK2QXTBAZq7JThtb0WsJxYd2CSLSN+Ycu23prnwqOqILcU0+34+gAVFw==} + '@lexical/link@0.16.1': + resolution: {integrity: sha512-zG36gEnEqbIe6tK/MhXi7wn/XMY/zdivnPcOY5WyC3derkEezeLSSIFsC1u5UNeK5pbpNMSy4LDpLhi1Ww4Y5w==} - '@lexical/list@0.16.0': - resolution: {integrity: sha512-nBx/DMM7nCgnOzo1JyNnVaIrk/Xi5wIPNi8jixrEV6w9Om2K6dHutn/79Xzp2dQlNGSLHEDjky6N2RyFgmXh0g==} + '@lexical/list@0.16.1': + resolution: {integrity: sha512-i9YhLAh5N6YO9dP+R1SIL9WEdCKeTiQQYVUzj84vDvX5DIBxMPUjTmMn3LXu9T+QO3h1s2L/vJusZASrl45eAw==} - '@lexical/mark@0.16.0': - resolution: {integrity: sha512-WMR4nqygSgIQ6Vdr5WAzohxBGjH+m44dBNTbWTGZGVlRvPzvBT6tieCoxFqpceIq/ko67HGTCNoFj2cMKVwgIA==} + '@lexical/mark@0.16.1': + resolution: {integrity: sha512-CZRGMLcxn5D+jzf1XnH+Z+uUugmpg1mBwTbGybCPm8UWpBrKDHkrscfMgWz62iRWz0cdVjM5+0zWpNElxFTRjQ==} - '@lexical/markdown@0.16.0': - resolution: {integrity: sha512-7HQLFrBbpY68mcq4A6C1qIGmjgA+fAByditi2WRe7tD2eoIKb/B5baQAnDKis0J+m5kTaCBmdlT6csSzyOPzeQ==} + '@lexical/markdown@0.16.1': + resolution: {integrity: sha512-0sBLttMvfQO/hVaIqpHdvDowpgV2CoRuWo2CNwvRLZPPWvPVjL4Nkb73wmi8zAZsAOTbX2aw+g4m/+k5oJqNig==} - '@lexical/offset@0.16.0': - resolution: {integrity: sha512-4TqPEC2qA7sgO8Tm65nOWnhJ8dkl22oeuGv9sUB+nhaiRZnw3R45mDelg23r56CWE8itZnvueE7TKvV+F3OXtQ==} + '@lexical/offset@0.16.1': + resolution: {integrity: sha512-/i2J04lQmFeydUZIF8tKXLQTXiJDTQ6GRnkfv1OpxU4amc0rwGa7+qAz/PuF1n58rP6InpLmSHxgY5JztXa2jw==} - '@lexical/overflow@0.16.0': - resolution: {integrity: sha512-a7gtIRxleEuMN9dj2yO4CdezBBfIr9Mq+m7G5z62+xy7VL7cfMfF+xWjy3EmDYDXS4vOQgAXAUgO4oKz2AKGhQ==} + '@lexical/overflow@0.16.1': + resolution: {integrity: sha512-xh5YpoxwA7K4wgMQF/Sjl8sdjaxqesLCtH5ZrcMsaPlmucDIEEs+i8xxk+kDUTEY7y+3FvRxs4lGNgX8RVWkvQ==} - '@lexical/plain-text@0.16.0': - resolution: {integrity: sha512-BK7/GSOZUHRJTbNPkpb9a/xN9z+FBCdunTsZhnOY8pQ7IKws3kuMO2Tk1zXfTd882ZNAxFdDKNdLYDSeufrKpw==} + '@lexical/plain-text@0.16.1': + resolution: {integrity: sha512-GjY4ylrBZIaAVIF8IFnmW0XGyHAuRmWA6gKB8iTTlsjgFrCHFIYC74EeJSp309O0Hflg9rRBnKoX1TYruFHVwA==} - '@lexical/react@0.16.0': - resolution: {integrity: sha512-WKFQbI0/m1YkLjL5t90YLJwjGcl5QRe6mkfm3ljQuL7Ioj3F92ZN/J2gHFVJ9iC8/lJs6Zzw6oFjiP8hQxJf9Q==} + '@lexical/react@0.16.1': + resolution: {integrity: sha512-SsGgLt9iKfrrMRy9lFb6ROVPUYOgv6b+mCn9Al+TLqs/gBReDBi3msA7m526nrtBUKYUnjHdQ1QXIJzuKgOxcg==} peerDependencies: react: '>=17.x' react-dom: '>=17.x' - '@lexical/rich-text@0.16.0': - resolution: {integrity: sha512-AGTD6yJZ+kj2TNah1r7/6vyufs6fZANeSvv9x5eG+WjV4uyUJYkd1qR8C5gFZHdkyr+bhAcsAXvS039VzAxRrQ==} + '@lexical/rich-text@0.16.1': + resolution: {integrity: sha512-4uEVXJur7tdSbqbmsToCW4YVm0AMh4y9LK077Yq2O9hSuA5dqpI8UbTDnxZN2D7RfahNvwlqp8eZKFB1yeiJGQ==} - '@lexical/selection@0.16.0': - resolution: {integrity: sha512-trT9gQVJ2j6AwAe7tHJ30SRuxCpV6yR9LFtggxphHsXSvJYnoHC0CXh1TF2jHl8Gd5OsdWseexGLBE4Y0V3gwQ==} + '@lexical/selection@0.16.1': + resolution: {integrity: sha512-+nK3RvXtyQvQDq7AZ46JpphmM33pwuulwiRfeXR5T9iFQTtgWOEjsAi/KKX7vGm70BxACfiSxy5QCOgBWFwVJg==} - '@lexical/table@0.16.0': - resolution: {integrity: sha512-A66K779kxdr0yH2RwT2itsMnkzyFLFNPXyiWGLobCH8ON4QPuBouZvjbRHBe8Pe64yJ0c1bRDxSbTqUi9Wt3Gg==} + '@lexical/table@0.16.1': + resolution: {integrity: sha512-GWb0/MM1sVXpi1p2HWWOBldZXASMQ4c6WRNYnRmq7J/aB5N66HqQgJGKp3m66Kz4k1JjhmZfPs7F018qIBhnFQ==} - '@lexical/text@0.16.0': - resolution: {integrity: sha512-9ilaOhuNIIGHKC8g8j3K/mEvJ09af9B6RKbm3GNoRcf/WNHD4dEFWNTEvgo/3zCzAS8EUBI6UINmfQQWlMjdIQ==} + '@lexical/text@0.16.1': + resolution: {integrity: sha512-Os/nKQegORTrKKN6vL3/FMVszyzyqaotlisPynvTaHTUC+yY4uyjM2hlF93i5a2ixxyiPLF9bDroxUP96TMPXg==} - '@lexical/utils@0.16.0': - resolution: {integrity: sha512-GWmFEmd7o3GHqJBaEwzuZQbfTNI3Gg8ReGuHMHABgrkhZ8j2NggoRBlxsQLG0f7BewfTMVwbye22yBPq78775w==} + '@lexical/utils@0.16.1': + resolution: {integrity: sha512-BVyJxDQi/rIxFTDjf2zE7rMDKSuEaeJ4dybHRa/hRERt85gavGByQawSLeQlTjLaYLVsy+x7wCcqh2fNhlLf0g==} - '@lexical/yjs@0.16.0': - resolution: {integrity: sha512-YIJr87DfAXTwoVHDjR7cci//hr4r/a61Nn95eo2JNwbTqQo65Gp8rwJivqVxNfvKZmRdwHTKgvdEDoBmI/tGog==} + '@lexical/yjs@0.16.1': + resolution: {integrity: sha512-QHw1bmzB/IypIV1tRWMH4hhwE1xX7wV+HxbzBS8oJAkoU5AYXM/kyp/sQicgqiwVfpai1Px7zatOoUDFgbyzHQ==} peerDependencies: yjs: '>=13.5.22' @@ -2944,12 +2947,12 @@ packages: '@types/node@18.19.36': resolution: {integrity: sha512-tX1BNmYSWEvViftB26VLNxT6mEr37M7+ldUtq7rlKnv4/2fKYsJIOmqJAjT6h1DNuwQjIKgw3VJ/Dtw3yiTIQw==} + '@types/node@20.14.10': + resolution: {integrity: sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==} + '@types/node@20.14.4': resolution: {integrity: sha512-1ChboN+57suCT2t/f8lwtPY/k3qTpuD/qnqQuYoBg6OQOcPyaw7PiZVdGpaZYAvhDDtqrt0oAaM8+oSu1xsUGw==} - '@types/node@20.14.8': - resolution: {integrity: sha512-DO+2/jZinXfROG7j7WKFn/3C6nFwxy2lLpgLjEXJz+0XKphZlTLJ14mo8Vfg8X5BWN6XjyESXq+LcYdT7tR3bA==} - '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -3225,6 +3228,11 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + acorn@8.12.1: + resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} + engines: {node: '>=0.4.0'} + hasBin: true + add-stream@1.0.0: resolution: {integrity: sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==} @@ -3530,6 +3538,11 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + browserslist@4.23.2: + resolution: {integrity: sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + bs-logger@0.2.6: resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} engines: {node: '>= 6'} @@ -3593,6 +3606,9 @@ packages: caniuse-lite@1.0.30001636: resolution: {integrity: sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg==} + caniuse-lite@1.0.30001641: + resolution: {integrity: sha512-Phv5thgl67bHYo1TtMY/MurjkHhV4EDaCosezRXgZ8jzA/Ub+wjxAvbGvjoFENStinwi5kCyOYV3mi5tOGykwA==} + caseless@0.12.0: resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} @@ -4159,6 +4175,9 @@ packages: electron-to-chromium@1.4.805: resolution: {integrity: sha512-8W4UJwX/w9T0QSzINJckTKG6CYpAUTqsaWcWIsdud3I1FYJcMgW9QqT1/4CBff/pP/TihWh13OmiyY8neto6vw==} + electron-to-chromium@1.4.825: + resolution: {integrity: sha512-OCcF+LwdgFGcsYPYC5keEEFC2XT0gBhrYbeGzHCx7i9qRFbzO/AqTmc/C/1xNhJj+JA7rzlN7mpBuStshh96Cg==} + element-resize-detector@1.2.4: resolution: {integrity: sha512-Fl5Ftk6WwXE0wqCgNoseKWndjzZlDCwuPTcoVZfCP9R3EHQF8qUtr3YUPNETegRBOKqQKPW3n4kiIWngGi8tKg==} @@ -5478,8 +5497,8 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} - lexical@0.16.0: - resolution: {integrity: sha512-Skn45Qhriazq4fpAtwnAB11U//GKc4vjzx54xsV3TkDLDvWpbL4Z9TNRwRoN3g7w8AkWnqjeOSODKkrjgfRSrg==} + lexical@0.16.1: + resolution: {integrity: sha512-+R05d3+N945OY8pTUjTqQrWoApjC+ctzvjnmNETtx9WmVAaiW0tQVG+AYLt5pDGY8dQXtd4RPorvnxBTECt9SA==} lib0@0.2.94: resolution: {integrity: sha512-hZ3p54jL4Wpu7IOg26uC7dnEWiMyNlUrb9KoG7+xYs45WkQwpVvKFndVq2+pqLYKe1u8Fp3+zAfZHVvTK34PvQ==} @@ -7129,8 +7148,8 @@ packages: uglify-js: optional: true - terser@5.31.1: - resolution: {integrity: sha512-37upzU1+viGvuFtBo9NPufCb9dwM0+l9hMxYyWfBA+fbwrPqNJAhbZ6W47bBFnZHKHTUBnMvi87434qq+qnxOg==} + terser@5.31.2: + resolution: {integrity: sha512-LGyRZVFm/QElZHy/CPr/O4eNZOZIzsrQ92y4v9UJe/pFJjypje2yI3C2FmPtvUEnhadlSbmG2nXtdcjHOjCfxw==} engines: {node: '>=10'} hasBin: true @@ -7453,6 +7472,12 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + update-browserslist-db@1.1.0: + resolution: {integrity: sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -9162,13 +9187,13 @@ snapshots: '@types/yargs': 17.0.32 chalk: 4.1.2 - '@joshwooding/vite-plugin-react-docgen-typescript@0.3.0(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1))': + '@joshwooding/vite-plugin-react-docgen-typescript@0.3.0(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2))': dependencies: glob: 7.2.3 glob-promise: 4.2.2(glob@7.2.3) magic-string: 0.27.0 react-docgen-typescript: 2.2.2(typescript@4.9.5) - vite: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1) + vite: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2) optionalDependencies: typescript: 4.9.5 @@ -9196,149 +9221,149 @@ snapshots: '@juggle/resize-observer@3.4.0': {} - '@lexical/clipboard@0.16.0': + '@lexical/clipboard@0.16.1': dependencies: - '@lexical/html': 0.16.0 - '@lexical/list': 0.16.0 - '@lexical/selection': 0.16.0 - '@lexical/utils': 0.16.0 - lexical: 0.16.0 + '@lexical/html': 0.16.1 + '@lexical/list': 0.16.1 + '@lexical/selection': 0.16.1 + '@lexical/utils': 0.16.1 + lexical: 0.16.1 - '@lexical/code@0.16.0': + '@lexical/code@0.16.1': dependencies: - '@lexical/utils': 0.16.0 - lexical: 0.16.0 + '@lexical/utils': 0.16.1 + lexical: 0.16.1 prismjs: 1.29.0 - '@lexical/devtools-core@0.16.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@lexical/devtools-core@0.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@lexical/html': 0.16.0 - '@lexical/link': 0.16.0 - '@lexical/mark': 0.16.0 - '@lexical/table': 0.16.0 - '@lexical/utils': 0.16.0 - lexical: 0.16.0 + '@lexical/html': 0.16.1 + '@lexical/link': 0.16.1 + '@lexical/mark': 0.16.1 + '@lexical/table': 0.16.1 + '@lexical/utils': 0.16.1 + lexical: 0.16.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@lexical/dragon@0.16.0': + '@lexical/dragon@0.16.1': dependencies: - lexical: 0.16.0 + lexical: 0.16.1 - '@lexical/hashtag@0.16.0': + '@lexical/hashtag@0.16.1': dependencies: - '@lexical/utils': 0.16.0 - lexical: 0.16.0 + '@lexical/utils': 0.16.1 + lexical: 0.16.1 - '@lexical/history@0.16.0': + '@lexical/history@0.16.1': dependencies: - '@lexical/utils': 0.16.0 - lexical: 0.16.0 + '@lexical/utils': 0.16.1 + lexical: 0.16.1 - '@lexical/html@0.16.0': + '@lexical/html@0.16.1': dependencies: - '@lexical/selection': 0.16.0 - '@lexical/utils': 0.16.0 - lexical: 0.16.0 + '@lexical/selection': 0.16.1 + '@lexical/utils': 0.16.1 + lexical: 0.16.1 - '@lexical/link@0.16.0': + '@lexical/link@0.16.1': dependencies: - '@lexical/utils': 0.16.0 - lexical: 0.16.0 + '@lexical/utils': 0.16.1 + lexical: 0.16.1 - '@lexical/list@0.16.0': + '@lexical/list@0.16.1': dependencies: - '@lexical/utils': 0.16.0 - lexical: 0.16.0 + '@lexical/utils': 0.16.1 + lexical: 0.16.1 - '@lexical/mark@0.16.0': + '@lexical/mark@0.16.1': dependencies: - '@lexical/utils': 0.16.0 - lexical: 0.16.0 + '@lexical/utils': 0.16.1 + lexical: 0.16.1 - '@lexical/markdown@0.16.0': + '@lexical/markdown@0.16.1': dependencies: - '@lexical/code': 0.16.0 - '@lexical/link': 0.16.0 - '@lexical/list': 0.16.0 - '@lexical/rich-text': 0.16.0 - '@lexical/text': 0.16.0 - '@lexical/utils': 0.16.0 - lexical: 0.16.0 + '@lexical/code': 0.16.1 + '@lexical/link': 0.16.1 + '@lexical/list': 0.16.1 + '@lexical/rich-text': 0.16.1 + '@lexical/text': 0.16.1 + '@lexical/utils': 0.16.1 + lexical: 0.16.1 - '@lexical/offset@0.16.0': + '@lexical/offset@0.16.1': dependencies: - lexical: 0.16.0 + lexical: 0.16.1 - '@lexical/overflow@0.16.0': + '@lexical/overflow@0.16.1': dependencies: - lexical: 0.16.0 + lexical: 0.16.1 - '@lexical/plain-text@0.16.0': + '@lexical/plain-text@0.16.1': dependencies: - '@lexical/clipboard': 0.16.0 - '@lexical/selection': 0.16.0 - '@lexical/utils': 0.16.0 - lexical: 0.16.0 + '@lexical/clipboard': 0.16.1 + '@lexical/selection': 0.16.1 + '@lexical/utils': 0.16.1 + lexical: 0.16.1 - '@lexical/react@0.16.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(yjs@13.6.17)': + '@lexical/react@0.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(yjs@13.6.17)': dependencies: - '@lexical/clipboard': 0.16.0 - '@lexical/code': 0.16.0 - '@lexical/devtools-core': 0.16.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@lexical/dragon': 0.16.0 - '@lexical/hashtag': 0.16.0 - '@lexical/history': 0.16.0 - '@lexical/link': 0.16.0 - '@lexical/list': 0.16.0 - '@lexical/mark': 0.16.0 - '@lexical/markdown': 0.16.0 - '@lexical/overflow': 0.16.0 - '@lexical/plain-text': 0.16.0 - '@lexical/rich-text': 0.16.0 - '@lexical/selection': 0.16.0 - '@lexical/table': 0.16.0 - '@lexical/text': 0.16.0 - '@lexical/utils': 0.16.0 - '@lexical/yjs': 0.16.0(yjs@13.6.17) - lexical: 0.16.0 + '@lexical/clipboard': 0.16.1 + '@lexical/code': 0.16.1 + '@lexical/devtools-core': 0.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@lexical/dragon': 0.16.1 + '@lexical/hashtag': 0.16.1 + '@lexical/history': 0.16.1 + '@lexical/link': 0.16.1 + '@lexical/list': 0.16.1 + '@lexical/mark': 0.16.1 + '@lexical/markdown': 0.16.1 + '@lexical/overflow': 0.16.1 + '@lexical/plain-text': 0.16.1 + '@lexical/rich-text': 0.16.1 + '@lexical/selection': 0.16.1 + '@lexical/table': 0.16.1 + '@lexical/text': 0.16.1 + '@lexical/utils': 0.16.1 + '@lexical/yjs': 0.16.1(yjs@13.6.17) + lexical: 0.16.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) react-error-boundary: 3.1.4(react@18.3.1) transitivePeerDependencies: - yjs - '@lexical/rich-text@0.16.0': + '@lexical/rich-text@0.16.1': dependencies: - '@lexical/clipboard': 0.16.0 - '@lexical/selection': 0.16.0 - '@lexical/utils': 0.16.0 - lexical: 0.16.0 + '@lexical/clipboard': 0.16.1 + '@lexical/selection': 0.16.1 + '@lexical/utils': 0.16.1 + lexical: 0.16.1 - '@lexical/selection@0.16.0': + '@lexical/selection@0.16.1': dependencies: - lexical: 0.16.0 + lexical: 0.16.1 - '@lexical/table@0.16.0': + '@lexical/table@0.16.1': dependencies: - '@lexical/utils': 0.16.0 - lexical: 0.16.0 + '@lexical/utils': 0.16.1 + lexical: 0.16.1 - '@lexical/text@0.16.0': + '@lexical/text@0.16.1': dependencies: - lexical: 0.16.0 + lexical: 0.16.1 - '@lexical/utils@0.16.0': + '@lexical/utils@0.16.1': dependencies: - '@lexical/list': 0.16.0 - '@lexical/selection': 0.16.0 - '@lexical/table': 0.16.0 - lexical: 0.16.0 + '@lexical/list': 0.16.1 + '@lexical/selection': 0.16.1 + '@lexical/table': 0.16.1 + lexical: 0.16.1 - '@lexical/yjs@0.16.0(yjs@13.6.17)': + '@lexical/yjs@0.16.1(yjs@13.6.17)': dependencies: - '@lexical/offset': 0.16.0 - lexical: 0.16.0 + '@lexical/offset': 0.16.1 + lexical: 0.16.1 yjs: 13.6.17 '@lokesh.dhakar/quantize@1.3.0': {} @@ -10912,7 +10937,7 @@ snapshots: - encoding - supports-color - '@storybook/builder-vite@7.6.19(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1))': + '@storybook/builder-vite@7.6.19(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2))': dependencies: '@storybook/channels': 7.6.19 '@storybook/client-logger': 7.6.19 @@ -10930,7 +10955,7 @@ snapshots: fs-extra: 11.2.0 magic-string: 0.30.10 rollup: 3.29.4 - vite: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1) + vite: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2) optionalDependencies: typescript: 4.9.5 transitivePeerDependencies: @@ -11255,18 +11280,18 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@storybook/react-vite@7.6.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.18.0)(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1))': + '@storybook/react-vite@7.6.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.18.0)(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2))': dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.3.0(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1)) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.3.0(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2)) '@rollup/pluginutils': 5.1.0(rollup@4.18.0) - '@storybook/builder-vite': 7.6.19(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1)) + '@storybook/builder-vite': 7.6.19(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2)) '@storybook/react': 7.6.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@4.9.5) - '@vitejs/plugin-react': 3.1.0(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1)) + '@vitejs/plugin-react': 3.1.0(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2)) magic-string: 0.30.10 react: 18.3.1 react-docgen: 7.0.3 react-dom: 18.3.1(react@18.3.1) - vite: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1) + vite: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2) transitivePeerDependencies: - '@preact/preset-vite' - encoding @@ -11505,11 +11530,11 @@ snapshots: dependencies: undici-types: 5.26.5 - '@types/node@20.14.4': + '@types/node@20.14.10': dependencies: undici-types: 5.26.5 - '@types/node@20.14.8': + '@types/node@20.14.4': dependencies: undici-types: 5.26.5 @@ -11723,25 +11748,25 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vitejs/plugin-react@3.1.0(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1))': + '@vitejs/plugin-react@3.1.0(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2))': dependencies: '@babel/core': 7.24.7 '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.24.7) '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.24.7) magic-string: 0.27.0 react-refresh: 0.14.2 - vite: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1) + vite: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2) transitivePeerDependencies: - supports-color - '@vitejs/plugin-react@4.3.1(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1))': + '@vitejs/plugin-react@4.3.1(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2))': dependencies: '@babel/core': 7.24.7 '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.24.7) '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.24.7) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1) + vite: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2) transitivePeerDependencies: - supports-color @@ -11852,9 +11877,9 @@ snapshots: mime-types: 2.1.35 negotiator: 0.6.3 - acorn-import-attributes@1.9.5(acorn@8.12.0): + acorn-import-attributes@1.9.5(acorn@8.12.1): dependencies: - acorn: 8.12.0 + acorn: 8.12.1 acorn-jsx@5.3.2(acorn@7.4.1): dependencies: @@ -11870,6 +11895,8 @@ snapshots: acorn@8.12.0: {} + acorn@8.12.1: {} + add-stream@1.0.0: {} address@1.2.2: {} @@ -12229,6 +12256,13 @@ snapshots: node-releases: 2.0.14 update-browserslist-db: 1.0.16(browserslist@4.23.1) + browserslist@4.23.2: + dependencies: + caniuse-lite: 1.0.30001641 + electron-to-chromium: 1.4.825 + node-releases: 2.0.14 + update-browserslist-db: 1.1.0(browserslist@4.23.2) + bs-logger@0.2.6: dependencies: fast-json-stable-stringify: 2.1.0 @@ -12283,6 +12317,8 @@ snapshots: caniuse-lite@1.0.30001636: {} + caniuse-lite@1.0.30001641: {} + caseless@0.12.0: {} chalk@2.4.2: @@ -12869,6 +12905,8 @@ snapshots: electron-to-chromium@1.4.805: {} + electron-to-chromium@1.4.825: {} + element-resize-detector@1.2.4: dependencies: batch-processor: 1.0.0 @@ -14407,7 +14445,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 20.14.8 + '@types/node': 20.14.10 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -14565,7 +14603,7 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - lexical@0.16.0: {} + lexical@0.16.1: {} lib0@0.2.94: dependencies: @@ -16477,15 +16515,15 @@ snapshots: jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 - terser: 5.31.1 + terser: 5.31.2 webpack: 5.92.0(esbuild@0.19.12) optionalDependencies: esbuild: 0.19.12 - terser@5.31.1: + terser@5.31.2: dependencies: '@jridgewell/source-map': 0.3.6 - acorn: 8.12.0 + acorn: 8.12.1 commander: 2.20.3 source-map-support: 0.5.21 @@ -16795,6 +16833,12 @@ snapshots: escalade: 3.1.2 picocolors: 1.0.1 + update-browserslist-db@1.1.0(browserslist@4.23.2): + dependencies: + browserslist: 4.23.2 + escalade: 3.1.2 + picocolors: 1.0.1 + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -16887,18 +16931,18 @@ snapshots: core-util-is: 1.0.2 extsprintf: 1.3.0 - vite-tsconfig-paths@4.3.2(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1)): + vite-tsconfig-paths@4.3.2(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2)): dependencies: debug: 4.3.5 globrex: 0.1.2 tsconfck: 3.1.0(typescript@4.9.5) optionalDependencies: - vite: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1) + vite: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2) transitivePeerDependencies: - supports-color - typescript - vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.1): + vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2): dependencies: esbuild: 0.18.20 postcss: 8.4.38 @@ -16909,7 +16953,7 @@ snapshots: less: 4.2.0 sass: 1.77.6 stylus: 0.62.0 - terser: 5.31.1 + terser: 5.31.2 walker@1.0.8: dependencies: @@ -16939,9 +16983,9 @@ snapshots: '@webassemblyjs/ast': 1.12.1 '@webassemblyjs/wasm-edit': 1.12.1 '@webassemblyjs/wasm-parser': 1.12.1 - acorn: 8.12.0 - acorn-import-attributes: 1.9.5(acorn@8.12.0) - browserslist: 4.23.1 + acorn: 8.12.1 + acorn-import-attributes: 1.9.5(acorn@8.12.1) + browserslist: 4.23.2 chrome-trace-event: 1.0.4 enhanced-resolve: 5.17.0 es-module-lexer: 1.5.4 diff --git a/src/index.ts b/src/index.ts index c22ea2c78..4ef0265de 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,7 +19,7 @@ export { default as AmityExpandableText } from '~/social/components/Comment/Comm export { useSDK as useAmitySDK } from '~/core/hooks/useSDK'; // v4 -export { default as AmityUIKitManager } from '~/v4/core/AmityUIKitManager'; +export { AmityUIKitManager } from '~/v4/core/AmityUIKitManager'; export { AmityDraftStoryPage, ViewStoryPage as AmityViewStoryPage, @@ -31,26 +31,23 @@ export { } from '~/v4/social/components'; // Chat v4 -export { - AmityLiveChatHeader, - AmityLiveChatMessageList, - AmityLiveChatMessageReceiverView, - AmityLiveChatMessageSenderView, - AmityLiveChatMessageComposeBar, -} from '~/v4/chat/components'; + +export { ChatHeader as AmityLiveChatHeader } from '~/v4/chat/components/ChatHeader'; +export { MessageList as AmityLiveChatMessageList } from '~/v4/chat/components/MessageList'; +export { MessageComposer as AmityLiveChatMessageComposeBar } from '~/v4/chat/components/MessageComposer'; export { MessageReactionPreview as AmityLiveChatMessageReactionPreview } from '~/v4/chat/components/MessageReactionPreview'; export { MessageReactionPicker as AmityLiveChatMessageReactionPicker } from '~/v4/chat/components/MessageReactionPicker'; export { MessageQuickReaction as AmityLiveChatMessageQuickReaction } from '~/v4/chat/components/MessageQuickReaction'; export { ReactionList as AmityReactionList } from '~/v4/social/components/ReactionList'; -import type { AmityMessageActionType } from '~/v4/chat/components'; +import type { MessageActionType } from '~/v4/chat/internal-components/LiveChatMessageContent/MessageAction'; import type { ReactionListProps } from '~/v4/social/components/ReactionList'; -export type { AmityMessageActionType }; +export type { MessageActionType as AmityMessageActionType }; export type { ReactionListProps as AmityReactionListProps }; -export { AmityLiveChatPage } from '~/v4/chat/pages'; +export { LiveChat as AmityLiveChatPage } from '~/v4/chat/pages/LiveChat'; // v4 internal use only (Amity Console) export { diff --git a/src/utils.ts b/src/utils.ts index f334aebde..5b97409f0 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -112,3 +112,36 @@ export const getMarkedMessageTopic = ({ return `${getNetworkId(user)}/marker/channel/${channelId}/message/${subChannelId}`; }; + +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +const SUPPORTED_URL_PROTOCOLS = new Set(['http:', 'https:', 'mailto:', 'sms:', 'tel:']); + +export function sanitizeUrl(url: string): string { + try { + const parsedUrl = new URL(url); + // eslint-disable-next-line no-script-url + if (!SUPPORTED_URL_PROTOCOLS.has(parsedUrl.protocol)) { + return 'about:blank'; + } + } catch { + return url; + } + return url; +} + +// Source: https://stackoverflow.com/a/8234912/2013580 +const urlRegExp = new RegExp( + /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)/, +); +export function validateUrl(url: string): boolean { + // TODO Fix UI for link insertion; it should never default to an invalid URL such as https://. + // Maybe show a dialog where they user can type the URL before inserting it. + return url === 'https://' || urlRegExp.test(url); +} diff --git a/src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx b/src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx deleted file mode 100644 index cb118f8a9..000000000 --- a/src/v4/chat/components/AmityLiveChatMessageComposeBar/index.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import { MessageRepository } from '@amityco/ts-sdk'; -import React, { RefObject, useEffect, useRef, useState } from 'react'; -import ArrowTop from '~/v4/icons/ArrowTop'; -import HomeIndicator from '../HomeIndicator'; -import styles from './styles.module.css'; -import InputText from '~/v4/core/components/InputText'; -import useMention from '~/v4/chat/hooks/useMention'; -import { useIntl } from 'react-intl'; -import useChannelPermission from '~/v4/chat/hooks/useChannelPermission'; -import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; -import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; -import { useLiveChatNotifications } from '~/v4/chat/providers/LiveChatNotificationProvider'; -import { useAmityComponent } from '~/v4/core/hooks/uikit'; - -const COMPOSEBAR_MAX_CHARACTER_LIMIT = 200; - -type ComposeActionTypes = { - replyMessage?: Amity.Message; - mentionMessage?: Amity.Message; - clearReplyMessage?: () => void; - clearMention?: () => void; -}; - -interface AmityLiveChatMessageComposeBarProps { - channel: Amity.Channel; - composeAction: ComposeActionTypes; - suggestionRef?: RefObject<HTMLDivElement>; - disabled?: boolean; - pageId?: string; -} - -type ComposeBarMention = { - id: string; - display: string; - childIndex: number; - index: number; - plainTextIndex: number; -}; - -export const AmityLiveChatMessageComposeBar = ({ - pageId = '*', - channel, - suggestionRef, - composeAction: { replyMessage, mentionMessage, clearReplyMessage, clearMention }, - disabled, -}: AmityLiveChatMessageComposeBarProps) => { - const [mentionList, setMentionList] = useState<{ - [key: ComposeBarMention['id']]: ComposeBarMention; - }>({}); - - const componentId = 'message_composer'; - const { themeStyles, config } = useAmityComponent({ pageId, componentId }); - - const { confirm } = useConfirmContext(); - const notification = useLiveChatNotifications(); - - const commentInputRef = useRef<HTMLInputElement | HTMLTextAreaElement | null>(null); - - const { queryMentionees, mentionees, onChange, markup, metadata, text } = useMention({ - targetId: channel.channelId, - targetType: 'channel', - }); - - const { isModerator } = useChannelPermission(channel.channelId); - - const { formatMessage } = useIntl(); - - const sendMessage = async () => { - if (!channel) return; - if (text?.trim().length === 0) return; - - if (text.trim().length > (config?.message_limit || COMPOSEBAR_MAX_CHARACTER_LIMIT)) { - confirm({ - title: formatMessage({ id: 'livechat.error.tooLongMessage.title' }), - content: formatMessage({ id: 'livechat.error.tooLongMessage.description' }), - okText: formatMessage({ id: 'general.action.ok' }), - }); - return; - } - - try { - await MessageRepository.createMessage({ - tags: [], - subChannelId: channel.channelId, - data: { text: text.trim() }, - dataType: 'text', - mentionees, - metadata, - parentId: replyMessage?.messageId || undefined, - }); - - onChange({ text: '', plainText: '', mentions: [] }); - } catch (error) { - const errorMessage = (error as Error).message; - let notificationMessage = formatMessage({ id: 'livechat.error.sendingMessage.error' }); - - if (errorMessage === 'Amity SDK (400308): Text contain blocked word') { - notificationMessage = formatMessage({ id: 'livechat.error.sendingMessage.blockedWord' }); - } else if ( - errorMessage === 'Amity SDK (400309): Data contain link that is not in whitelist' - ) { - notificationMessage = formatMessage({ id: 'livechat.error.sendingMessage.notAllowLink' }); - } else if (errorMessage === 'Amity SDK (400302): User is muted') { - notificationMessage = formatMessage({ id: 'livechat.member.muted.error' }); - } - - notification.error({ - content: notificationMessage, - }); - - return; - } - - setMentionList({}); - clearReplyMessage && clearReplyMessage(); - }; - - useEffect(() => { - commentInputRef.current?.focus(); - }, []); - - return ( - <div className={styles.composeBarContainer} style={themeStyles}> - <div className={styles.composeBar}> - <div className={styles.textInputContainer}> - <InputText - ref={commentInputRef} - suggestionRef={suggestionRef} - data-qa-anchor="live-chat-compose-bar" - multiline - disabled={disabled} - placeholder={ - (typeof config?.placeholder_text === 'string' && config?.placeholder_text) || - formatMessage({ - id: 'livechat.composebar.placeholder', - }) - } - mentionAllowed - isModerator={isModerator} - loadMoreMentionees={(query) => queryMentionees(query)} - onChange={(e) => { - onChange({ - text: e.text, - plainText: e.plainText, - mentions: e.mentions, - }); - }} - value={markup} - queryMentionees={queryMentionees} - mentionColor={styles.mentionText} - /> - </div> - <div className={styles.sendButtonContainer}> - <ArrowTop className={styles.sendButton} onClick={() => sendMessage()} /> - {/* {message.length > 0 ? ( - <ArrowTop className={styles.sendButton} onClick={sendMessage} /> - ) : ( - <HeartReaction className={styles.reactionButton} /> - )} */} - </div> - </div> - <HomeIndicator /> - </div> - ); -}; - -export default AmityLiveChatMessageComposeBar; diff --git a/src/v4/chat/components/AmityLiveChatMessageComposeBar/styles.module.css b/src/v4/chat/components/AmityLiveChatMessageComposeBar/styles.module.css deleted file mode 100644 index 5c24547a1..000000000 --- a/src/v4/chat/components/AmityLiveChatMessageComposeBar/styles.module.css +++ /dev/null @@ -1,75 +0,0 @@ -.sendButton { - border-radius: var(--asc-border-radius-xxl); - width: 1rem; - height: 1rem; - background-color: var(--asc-color-primary-default); - cursor: pointer; - padding: var(--asc-spacing-xxs2); - fill: white; -} - -.reactionButton { - width: 2rem; - height: 2rem; - cursor: pointer; -} - -.sendButtonContainer { - display: flex; - align-items: center; - gap: var(--asc-spacing-s2); - width: 2rem; - height: 2rem; -} - -.composeBarContainer { - min-height: 3.5rem; - z-index: 99; - background-color: var(--asc-color-background-default); -} - -.composeBar { - display: flex; - align-items: center; - gap: var(--asc-spacing-s2); - padding: var(--asc-spacing-s1) var(--asc-spacing-s2); - background-color: inherit; - box-shadow: 0 -1px 0 0 var(--asc-color-base-shade4); -} - -.textInputContainer { - display: flex; - width: calc(100% - 2.8rem); - - & > div { - width: 100%; - border-radius: var(--asc-border-radius-xxl); - background: var(--asc-color-secondary-shade4); - - textarea { - padding: 0.563rem 1rem; - background-color: transparent; - } - } -} - -.composeBarLoading { - width: 100%; - height: var(--asc-spacing-l1); - padding: var(--asc-spacing-s1) var(--asc-spacing-s2); - border-radius: var(--asc-border-radius-sm); - border: var(--asc-border-radius-none); - background-color: var(--asc-color-secondary-default); - color: var(--asc-color-base-inverse); - display: flex; - align-items: center; - - span { - padding-left: var(--asc-spacing-s1); - font-weight: var(--asc-text-font-weight-light); - } -} - -.mentionText { - color: var(--asc-color-primary-default); -} diff --git a/src/v4/chat/components/AmityLiveChatMessageReceiverView/messageReceiver.stories.tsx b/src/v4/chat/components/AmityLiveChatMessageReceiverView/messageReceiver.stories.tsx deleted file mode 100644 index cc6817283..000000000 --- a/src/v4/chat/components/AmityLiveChatMessageReceiverView/messageReceiver.stories.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React, { useState, useRef, useEffect } from 'react'; -import useChannelsCollection from '~/chat/hooks/collections/useChannelsCollection'; -import useMessagesCollection from '~/chat/hooks/collections/useMessagesCollection'; -import AmityLiveChatMessageReceiverView from '.'; - -export default { - title: 'V4/AmityMessageReceiverView', -}; - -const SampleMessageReceiverView = () => { - const ref = useRef<HTMLDivElement>(null); - const { channels, isLoading: isLoadingChannel } = useChannelsCollection({ - membership: 'all', - sortBy: 'lastActivity', - types: ['live'], - limit: 1, - }); - - const { messages, isLoading: isLoadingMessage } = useMessagesCollection({ - subChannelId: channels.length > 0 ? channels[0].defaultSubChannelId : '', - limit: 1, - }); - - if (isLoadingChannel) return <div style={{ background: 'white' }}>Loading...</div>; - if (channels.length === 0) return <div style={{ background: 'white' }}>No channels</div>; - if (isLoadingMessage) return <div style={{ background: 'white' }}>Loading...</div>; - if (messages.length === 0) return <div style={{ background: 'white' }}>No messages</div>; - - return ( - <div style={{ background: 'white' }}> - <AmityLiveChatMessageReceiverView containerRef={ref} message={messages[0]} action={{}} /> - </div> - ); -}; - -export const LiveChatStory = { - render: () => <SampleMessageReceiverView />, - name: 'AmityLiveChatMessageReceiver', -}; diff --git a/src/v4/chat/components/AmityLiveChatHeader/styles.module.css b/src/v4/chat/components/ChatHeader/ChatHeader.module.css similarity index 100% rename from src/v4/chat/components/AmityLiveChatHeader/styles.module.css rename to src/v4/chat/components/ChatHeader/ChatHeader.module.css diff --git a/src/v4/chat/components/AmityLiveChatHeader/livechatHeader.stories.tsx b/src/v4/chat/components/ChatHeader/ChatHeader.stories.tsx similarity index 69% rename from src/v4/chat/components/AmityLiveChatHeader/livechatHeader.stories.tsx rename to src/v4/chat/components/ChatHeader/ChatHeader.stories.tsx index 317f5ac81..48e95a9fa 100644 --- a/src/v4/chat/components/AmityLiveChatHeader/livechatHeader.stories.tsx +++ b/src/v4/chat/components/ChatHeader/ChatHeader.stories.tsx @@ -1,12 +1,12 @@ import React from 'react'; import useChannelsCollection from '~/chat/hooks/collections/useChannelsCollection'; -import { AmityLiveChatHeader } from '.'; +import { ChatHeader } from './index'; export default { - title: 'V4/AmityLiveChatHeader', + title: 'V4/ChatHeader', }; -const SampleLiveChatHeader = () => { +const SampleChatHeader = () => { const { channels, hasMore, loadMore, isLoading } = useChannelsCollection({ membership: 'all', sortBy: 'lastActivity', @@ -19,12 +19,12 @@ const SampleLiveChatHeader = () => { return <div style={{ background: 'white', minWidth: '320px' }}>No channels</div>; return ( <div style={{ background: 'white', minWidth: '320px' }}> - <AmityLiveChatHeader channel={channels[0]} /> + <ChatHeader channel={channels[0]} /> </div> ); }; -export const LiveChatHeader = { - render: () => <SampleLiveChatHeader />, - name: 'AmityLiveChatHeader', +export const ChatHeaderStory = { + render: () => <SampleChatHeader />, + name: 'AmityChatHeader', }; diff --git a/src/v4/chat/components/AmityLiveChatHeader/index.tsx b/src/v4/chat/components/ChatHeader/ChatHeader.tsx similarity index 68% rename from src/v4/chat/components/AmityLiveChatHeader/index.tsx rename to src/v4/chat/components/ChatHeader/ChatHeader.tsx index 0b48ce96d..dd4903b7f 100644 --- a/src/v4/chat/components/AmityLiveChatHeader/index.tsx +++ b/src/v4/chat/components/ChatHeader/ChatHeader.tsx @@ -1,27 +1,25 @@ import React from 'react'; import millify from 'millify'; -import { FormattedMessage, useIntl } from 'react-intl'; import Chat from '~/v4/icons/Chat'; import UserRegular from '~/v4/icons/UserRegular'; import useConnectionStates from '~/social/hooks/useConnectionStates'; import ConnectionSpinner from '~/v4/icons/ConnectionSpinner'; import { Typography } from '~/v4/core/components'; -import styles from './styles.module.css'; import useChatInfo from '~/v4/chat/hooks/useChatInfo'; import { Avatar } from '~/v4/core/components/Avatar/Avatar'; import { useAmityComponent } from '~/v4/core/hooks/uikit/index'; +import styles from './ChatHeader.module.css'; -interface AmityLiveChatHeaderProps { +interface ChatHeaderProps { channel: Amity.Channel | null; pageId?: string; componentId?: string; } -export const AmityLiveChatHeader = ({ channel, pageId = '*' }: AmityLiveChatHeaderProps) => { +export const ChatHeader = ({ channel, pageId = '*' }: ChatHeaderProps) => { const componentId = 'chat_header'; const { themeStyles } = useAmityComponent({ pageId, componentId }); const { chatName, chatAvatar } = useChatInfo({ channel }); - const { formatMessage } = useIntl(); const isOnline = useConnectionStates(); return ( @@ -31,7 +29,7 @@ export const AmityLiveChatHeader = ({ channel, pageId = '*' }: AmityLiveChatHead </div> <div> <div className={styles.displayName}> - <Typography.Title>{chatName || formatMessage({ id: 'loading' })}</Typography.Title> + <Typography.Title>{chatName || 'Loading...'}</Typography.Title> </div> <div className={styles.memberCount}> {isOnline ? ( @@ -39,18 +37,15 @@ export const AmityLiveChatHeader = ({ channel, pageId = '*' }: AmityLiveChatHead <UserRegular className={styles.memberIcon} /> <Typography.Caption> {millify(channel?.memberCount || 0)}{' '} - <FormattedMessage - id="plural.member" - values={{ amount: channel?.memberCount || 0 }} - /> + {`${channel?.memberCount || 0} ${ + channel?.memberCount === 1 ? 'member' : 'members' + }`} </Typography.Caption> </> ) : ( <> <ConnectionSpinner width={20} height={20} /> - <Typography.Caption> - <FormattedMessage id="general.connection.waiting" /> - </Typography.Caption> + <Typography.Caption>Waiting for connection...</Typography.Caption> </> )} </div> @@ -58,5 +53,3 @@ export const AmityLiveChatHeader = ({ channel, pageId = '*' }: AmityLiveChatHead </div> ); }; - -export default AmityLiveChatHeader; diff --git a/src/v4/chat/components/ChatHeader/index.tsx b/src/v4/chat/components/ChatHeader/index.tsx new file mode 100644 index 000000000..10ec11369 --- /dev/null +++ b/src/v4/chat/components/ChatHeader/index.tsx @@ -0,0 +1 @@ +export { ChatHeader } from './ChatHeader'; diff --git a/src/v4/chat/components/MessageComposer/MessageComposer.module.css b/src/v4/chat/components/MessageComposer/MessageComposer.module.css new file mode 100644 index 000000000..558f7f5bc --- /dev/null +++ b/src/v4/chat/components/MessageComposer/MessageComposer.module.css @@ -0,0 +1,187 @@ +.sendButton { + width: 100%; + height: 100%; + border-radius: var(--asc-border-radius-xxl); + background-color: var(--asc-color-primary-default); + cursor: pointer; + padding: var(--asc-spacing-xxs2); + fill: var(--asc-color-white); +} + +.reactionButton { + width: 2rem; + height: 2rem; + cursor: pointer; +} + +.sendButtonContainer { + display: flex; + align-items: center; + justify-content: center; + width: 2rem; + height: 2rem; +} + +.composeBarContainer { + height: 3.5rem; + width: 100%; + position: relative; + background-color: var(--asc-color-background-default); +} + +.optionContainer { + position: absolute; + top: 0; + left: 0; + width: 100%; + transform: translateY(-100%); +} + +.composeBar { + /* display: flex; + align-items: center; + gap: var(--asc-spacing-s2); + padding: var(--asc-spacing-s1) var(--asc-spacing-s2); + box-shadow: 0 -1px 0 0 var(--asc-color-base-shade4); */ + height: 3.5rem; + width: 100%; + padding: 0.5rem; + display: grid; + align-items: center; + grid-template-columns: minmax(0, 1fr) 2rem; + gap: var(--asc-spacing-s2); + box-sizing: border-box; + position: relative; +} + +.composeBarLoading { + width: 100%; + height: var(--asc-spacing-l1); + padding: var(--asc-spacing-s1) var(--asc-spacing-s2); + border-radius: var(--asc-border-radius-sm); + border: var(--asc-border-radius-none); + background-color: var(--asc-color-secondary-default); + color: var(--asc-color-base-inverse); + display: flex; + align-items: center; + + span { + padding-left: var(--asc-spacing-s1); + font-weight: var(--asc-text-font-weight-light); + } +} + +.mentionText { + color: var(--asc-color-primary-default); +} + +.editorPlaceholder { + color: #999; + overflow: hidden; + position: absolute; + top: 0.625rem; + left: 1.75rem; + background-color: transparent; + user-select: none; + pointer-events: none; +} + +.editorContainer { + height: 100%; + font-size: var(--asc-text-font-size-md); + color: var(--asc-color-base-default); + padding: 0 0 0 1rem; + position: relative; +} + +.editorRoot { + width: 100%; + background-color: var(--asc-color-secondary-shade4); + border-radius: 1.25rem; + min-height: 2.5rem; + padding: 0.625rem 0.75rem; + border: 0 solid var(--asc-color-background-default); + outline: 0; +} + +.editorParagraph { + margin: 0; + position: relative; +} + +.editorContainer:focus { + border: none; + outline: none; +} + +.editorContainer__mentionTextInput_item { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 12.5rem; + overflow-y: scroll; +} + +.editorContainer__mentionTextInput_item::-webkit-scrollbar { + display: none; +} + +.editorLink { + color: var(--asc-color-primary-shade1); + text-decoration: none; +} + +.editorLink:hover { + text-decoration: underline; +} + +.userMentionItem__item, +.allMentionItem__item { + width: 100%; + display: flex; + align-items: center; + padding: 0.5rem 1rem; + background-color: var(--asc-color-background-default); + color: var(--asc-color-base-default); +} + +.userMentionItem__item[data-is-selected='true'], +.allMentionItem__item[data-is-selected='true'] { + background-color: var(--asc-color-base-shade4); +} + +.userMentionItem__item:focus, +.allMentionItem__item:focus { + background-color: var(--asc-color-base-shade4); +} + +.userMentionItem__avatar { + width: 2.5rem; + height: 2.5rem; +} + +.allMentionItem__atSign { + width: 2rem; + height: 2rem; + background-color: var(--asc-color-primary-default); + color: var(--asc-color-white); + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + text-align: center; + line-height: 1.2rem; + font-size: 1rem; +} + +.userMentionItem__displayName, +.allMentionItem__displayName { + margin-left: 0.5rem; + font-size: 1rem; + display: block; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + color: var(--asc-color-base-default); +} diff --git a/src/v4/chat/components/AmityLiveChatMessageComposeBar/livechatMessageComposeBar.stories.tsx b/src/v4/chat/components/MessageComposer/MessageComposer.stories.tsx similarity index 64% rename from src/v4/chat/components/AmityLiveChatMessageComposeBar/livechatMessageComposeBar.stories.tsx rename to src/v4/chat/components/MessageComposer/MessageComposer.stories.tsx index 693604f08..4f1be2e74 100644 --- a/src/v4/chat/components/AmityLiveChatMessageComposeBar/livechatMessageComposeBar.stories.tsx +++ b/src/v4/chat/components/MessageComposer/MessageComposer.stories.tsx @@ -1,12 +1,12 @@ import React from 'react'; import useChannelsCollection from '~/chat/hooks/collections/useChannelsCollection'; -import { AmityLiveChatMessageComposeBar } from '.'; +import { MessageComposer } from './MessageComposer'; export default { - title: 'V4/AmityLiveChatMessageComposeBar', + title: 'V4/MessageComposer', }; -const SampleLiveChatHeader = () => { +const SampleMessageComposer = () => { const { channels, hasMore, loadMore, isLoading } = useChannelsCollection({ membership: 'all', sortBy: 'lastActivity', @@ -19,12 +19,12 @@ const SampleLiveChatHeader = () => { return <div style={{ background: 'white', minWidth: '320px' }}>No channels</div>; return ( <div style={{ background: 'white', minWidth: '320px' }}> - <AmityLiveChatMessageComposeBar channel={channels[0]} composeAction={{}} /> + <MessageComposer channel={channels[0]} composeAction={{}} /> </div> ); }; -export const LiveChatHeader = { - render: () => <SampleLiveChatHeader />, - name: 'AmityLiveChatMessageComposeBar', +export const MessageComposerStory = { + render: () => <SampleMessageComposer />, + name: 'MessageComposer', }; diff --git a/src/v4/chat/components/MessageComposer/MessageComposer.tsx b/src/v4/chat/components/MessageComposer/MessageComposer.tsx new file mode 100644 index 000000000..d3bd68539 --- /dev/null +++ b/src/v4/chat/components/MessageComposer/MessageComposer.tsx @@ -0,0 +1,329 @@ +import React, { RefObject, useCallback, useMemo, useRef, useState } from 'react'; +import ReactDOM from 'react-dom'; +import { MessageRepository } from '@amityco/ts-sdk'; +import ArrowTop from '~/v4/icons/ArrowTop'; +import { HomeIndicator } from '../../internal-components/HomeIndicator/index'; +import { useChannelPermission } from '~/v4/chat/hooks/useChannelPermission'; +import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; +import { useLiveChatNotifications } from '~/v4/chat/providers/LiveChatNotificationProvider'; +import { useAmityComponent } from '~/v4/core/hooks/uikit'; + +import styles from './MessageComposer.module.css'; +import { useSearchChannelUser } from '../../hooks/collections/useSearchChannelUser'; +import { + $getRoot, + COMMAND_PRIORITY_HIGH, + COMMAND_PRIORITY_LOW, + Klass, + LexicalEditor, + LexicalNode, +} from 'lexical'; +import { AutoLinkNode, LinkNode } from '@lexical/link'; +import { + MentionPlugin, + MentionTypeaheadOption, +} from '~/v4/social/internal-components/Lexical/plugins/MentionPlugin'; +import { UserAvatar } from '~/v4/social/internal-components/UserAvatar/UserAvatar'; +import { useMutation } from '@tanstack/react-query'; +import { + editorStateToText, + getEditorConfig, + MentionData, +} from '~/v4/social/internal-components/Lexical/utils'; +import { + $createMentionNode, + MentionNode, +} from '~/v4/social/internal-components/Lexical/nodes/MentionNode'; +import { LexicalComposer } from '@lexical/react/LexicalComposer'; +import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'; +import { ContentEditable } from '@lexical/react/LexicalContentEditable'; +import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary'; +import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'; +import { AutoFocusPlugin } from '@lexical/react/LexicalAutoFocusPlugin'; +import { LinkPlugin } from '~/v4/social/internal-components/Lexical/plugins/LinkPlugin'; +import { AutoLinkPlugin } from '~/v4/social/internal-components/Lexical/plugins/AutoLinkPlugin'; +import { EditorRefPlugin } from '@lexical/react/LexicalEditorRefPlugin'; +import { EnterKeyInterceptorPlugin } from '~/v4/social/internal-components/Lexical/plugins/EnterKeyInterceptorPlugin'; + +const COMPOSEBAR_MAX_CHARACTER_LIMIT = 200; + +type ComposeActionTypes = { + replyMessage?: Amity.Message; + mentionMessage?: Amity.Message; + clearReplyMessage?: () => void; + clearMention?: () => void; +}; + +interface MessageComposerProps { + channel: Amity.Channel; + composeAction: ComposeActionTypes; + suggestionRef?: RefObject<HTMLDivElement>; + disabled?: boolean; + pageId?: string; +} + +const useSuggestions = (channelId?: string | null) => { + const [queryString, setQueryString] = useState<string | null>(null); + + const { isModerator } = useChannelPermission(channelId || undefined); + + const { channelMembers } = useSearchChannelUser({ + search: queryString, + channelId: channelId as string, + memberships: ['member'], + limit: 20, + }); + + const onQueryChange = (newQuery: string | null) => { + setQueryString(newQuery); + }; + + const suggestions = useMemo(() => { + const baseSuggestion = channelMembers.map(({ user, userId }) => ({ + userId: user?.userId || userId, + displayName: user?.displayName, + })); + + return isModerator && (queryString === '' || /^al*l*$/i.test(queryString || '')) + ? [{ userId: 'all', displayName: 'All' }, ...baseSuggestion] + : baseSuggestion; + }, [isModerator, channelMembers]); + + return { suggestions, queryString, onQueryChange }; +}; + +interface MentionItemProps { + isSelected: boolean; + onClick: () => void; + onMouseEnter: () => void; + option: MentionTypeaheadOption<MentionData>; +} + +function MentionItem({ option, isSelected, onClick, onMouseEnter }: MentionItemProps) { + return ( + <li + key={option.key} + tabIndex={-1} + data-is-selected={isSelected} + className={styles.userMentionItem__item} + ref={option.setRefElement} + role="option" + aria-selected={isSelected} + onMouseEnter={onMouseEnter} + onClick={onClick} + > + <div> + <UserAvatar className={styles.userMentionItem__avatar} userId={option.data.userId} /> + </div> + <p className={styles.userMentionItem__displayName}>{option.data.displayName}</p> + </li> + ); +} + +function AllMentionItem({ option, isSelected, onClick, onMouseEnter }: MentionItemProps) { + return ( + <li + key={option.key} + tabIndex={-1} + data-is-selected={isSelected} + className={styles.allMentionItem__item} + ref={option.setRefElement} + role="option" + aria-selected={isSelected} + onMouseEnter={onMouseEnter} + onClick={onClick} + > + <div className={styles.allMentionItem__atSign}>@</div> + <p className={styles.allMentionItem__displayName}>{option.data.displayName}</p> + </li> + ); +} + +const nodes = [AutoLinkNode, LinkNode, MentionNode] as Array<Klass<LexicalNode>>; + +export const MessageComposer = ({ + pageId = '*', + channel, + composeAction: { replyMessage, mentionMessage, clearReplyMessage, clearMention }, +}: MessageComposerProps) => { + const componentId = 'message_composer'; + + const optionsRef = useRef<HTMLDivElement>(null); + const editorRef = useRef<LexicalEditor | null>(null); + const { themeStyles, uiReference, config, accessibilityId } = useAmityComponent({ + pageId, + componentId, + }); + + const { confirm } = useConfirmContext(); + const notification = useLiveChatNotifications(); + + const { onQueryChange, suggestions } = useSuggestions(channel.channelId); + + const { mutateAsync } = useMutation({ + mutationFn: (params: Parameters<typeof MessageRepository.createMessage>[0]) => { + return MessageRepository.createMessage(params); + }, + onSuccess: () => { + editorRef.current?.update(() => { + const root = $getRoot(); + root.clear(); + }); + + clearReplyMessage && clearReplyMessage(); + }, + onError: (error) => { + const errorMessage = error.message; + let notificationMessage = "Your message wasn't sent. Please try again."; + + if (errorMessage === 'Amity SDK (400308): Text contain blocked word') { + notificationMessage = "Your message wasn't sent as it contains a blocked word."; + } else if ( + errorMessage === 'Amity SDK (400309): Data contain link that is not in whitelist' + ) { + notificationMessage = 'Your message wasn’t sent as it contained a link that’s not allowed.'; + } else if (errorMessage === 'Amity SDK (400302): User is muted') { + notificationMessage = 'User is muted'; + } + + notification.error({ + content: notificationMessage, + }); + }, + }); + + const sendMessage = () => { + if (!channel) return; + if (!editorRef.current) return; + + const { mentioned, mentionees, text } = editorStateToText(editorRef.current); + + if (text?.trim().length === 0) return; + + if (text.trim().length > (config?.message_limit || COMPOSEBAR_MAX_CHARACTER_LIMIT)) { + confirm({ + title: 'Unable to send message', + content: 'Your message is too long. Please shorten your message and try again.', + okText: 'Ok', + }); + return; + } + + mutateAsync({ + tags: [], + subChannelId: channel.channelId, + data: { text: text.trim() }, + dataType: 'text', + mentionees, + metadata: { mentioned }, + parentId: replyMessage?.messageId || undefined, + }); + }; + + const editorConfig = getEditorConfig({ + namespace: uiReference, + theme: { + root: styles.editorRoot, + placeholder: styles.editorPlaceholder, + paragraph: styles.editorParagraph, + link: styles.editorLink, + }, + nodes, + }); + + return ( + <div + className={styles.composeBarContainer} + data-qa-anchor={accessibilityId} + style={themeStyles} + > + <div className={styles.composeBar}> + <div ref={optionsRef} className={styles.optionContainer}></div> + <div className={styles.editorContainer}> + <LexicalComposer initialConfig={editorConfig}> + <RichTextPlugin + contentEditable={<ContentEditable />} + placeholder={ + <span className={styles.editorPlaceholder}>{config.placeholder_text}</span> ?? null + } + ErrorBoundary={LexicalErrorBoundary} + /> + <HistoryPlugin /> + <AutoFocusPlugin /> + <LinkPlugin /> + <AutoLinkPlugin /> + <EditorRefPlugin editorRef={editorRef} /> + <EnterKeyInterceptorPlugin + onEnter={sendMessage} + commandPriority={COMMAND_PRIORITY_LOW} + /> + <MentionPlugin<MentionData, MentionNode<MentionData>> + suggestions={suggestions} + getSuggestionId={(suggestion) => suggestion.userId} + onQueryChange={onQueryChange} + $createNode={(data) => + $createMentionNode({ + text: `@${data.displayName}` || '', + data, + }) + } + menuRenderFn={( + _, + { options, selectedIndex, setHighlightedIndex, selectOptionAndCleanUp }, + ) => { + if (!optionsRef.current || options.length === 0) { + return null; + } + return ReactDOM.createPortal( + <> + {options.map((option, i: number) => { + if (option.data.userId === 'all') { + return ( + <AllMentionItem + isSelected={selectedIndex === i} + onClick={() => { + setHighlightedIndex(i); + selectOptionAndCleanUp(option); + }} + onMouseEnter={() => { + setHighlightedIndex(i); + }} + key={option.key} + option={option} + /> + ); + } + + return ( + <MentionItem + isSelected={selectedIndex === i} + onClick={() => { + setHighlightedIndex(i); + selectOptionAndCleanUp(option); + }} + onMouseEnter={() => { + setHighlightedIndex(i); + }} + key={option.key} + option={option} + /> + ); + })} + </>, + optionsRef.current, + ); + }} + commandPriority={COMMAND_PRIORITY_HIGH} + /> + </LexicalComposer> + </div> + <div className={styles.sendButtonContainer}> + <ArrowTop className={styles.sendButton} onClick={() => sendMessage()} /> + </div> + </div> + <HomeIndicator /> + </div> + ); +}; + +export default MessageComposer; diff --git a/src/v4/chat/components/MessageComposer/index.tsx b/src/v4/chat/components/MessageComposer/index.tsx new file mode 100644 index 000000000..9733e9caa --- /dev/null +++ b/src/v4/chat/components/MessageComposer/index.tsx @@ -0,0 +1 @@ +export { MessageComposer } from './MessageComposer'; diff --git a/src/v4/chat/components/MessageList/LiveChatLoadingIndicator.module.css b/src/v4/chat/components/MessageList/LiveChatLoadingIndicator.module.css new file mode 100644 index 000000000..198ee508d --- /dev/null +++ b/src/v4/chat/components/MessageList/LiveChatLoadingIndicator.module.css @@ -0,0 +1,13 @@ +.spinner { + animation: rotate 2s linear infinite; +} + +@keyframes rotate { + from { + transform: rotate(0deg); + } + + to { + transform: rotate(360deg); + } +} diff --git a/src/v4/chat/components/LivechatLoadingIndicator/index.tsx b/src/v4/chat/components/MessageList/LiveChatLoadingIndicator.tsx similarity index 87% rename from src/v4/chat/components/LivechatLoadingIndicator/index.tsx rename to src/v4/chat/components/MessageList/LiveChatLoadingIndicator.tsx index c21b59a6a..8fbfed7d3 100644 --- a/src/v4/chat/components/LivechatLoadingIndicator/index.tsx +++ b/src/v4/chat/components/MessageList/LiveChatLoadingIndicator.tsx @@ -1,28 +1,20 @@ +import clsx from 'clsx'; import React from 'react'; -import styled, { keyframes } from 'styled-components'; +import styles from './LiveChatLoadingIndicator.module.css'; -const rotateAnimation = keyframes` - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -`; - -const StyledSpinner = styled.svg` - animation: ${rotateAnimation} 2s linear infinite; -`; - -const LivechatLoadingIndicator = (props: React.SVGProps<SVGSVGElement>) => { +export const LiveChatLoadingIndicator = ({ + className, + ...props +}: React.SVGProps<SVGSVGElement>) => { return ( - <StyledSpinner + <svg width="25" height="25" xmlns="http://www.w3.org/2000/svg" aria-label="Loading..." viewBox="0 0 100 100" version="1.1" + className={clsx(styles.spinner, className)} {...props} > <defs id="defs3821" /> @@ -171,8 +163,6 @@ const LivechatLoadingIndicator = (props: React.SVGProps<SVGSVGElement>) => { y="47" id="rect3815" /> - </StyledSpinner> + </svg> ); }; - -export default LivechatLoadingIndicator; diff --git a/src/v4/chat/components/AmityLiveChatMessageList/styles.module.css b/src/v4/chat/components/MessageList/MessageList.module.css similarity index 100% rename from src/v4/chat/components/AmityLiveChatMessageList/styles.module.css rename to src/v4/chat/components/MessageList/MessageList.module.css diff --git a/src/v4/chat/components/AmityLiveChatMessageList/livechatMessageList.stories.tsx b/src/v4/chat/components/MessageList/MessageList.stories.tsx similarity index 84% rename from src/v4/chat/components/AmityLiveChatMessageList/livechatMessageList.stories.tsx rename to src/v4/chat/components/MessageList/MessageList.stories.tsx index 40567970b..e554f68e4 100644 --- a/src/v4/chat/components/AmityLiveChatMessageList/livechatMessageList.stories.tsx +++ b/src/v4/chat/components/MessageList/MessageList.stories.tsx @@ -1,9 +1,9 @@ import React from 'react'; import useChannelsCollection from '~/chat/hooks/collections/useChannelsCollection'; -import { AmityLiveChatMessageList } from '.'; +import { MessageList } from './MessageList'; export default { - title: 'V4/AmityLiveChatMessageList', + title: 'V4/MessageList', }; const SampleLiveChatMessageList = () => { @@ -19,7 +19,7 @@ const SampleLiveChatMessageList = () => { return <div style={{ background: 'white', minWidth: '320px' }}>No channels</div>; return ( <div style={{ background: 'white', minWidth: '320px' }}> - <AmityLiveChatMessageList + <MessageList channel={channels[0]} replyMessage={(message) => console.log('reply', message)} /> @@ -29,5 +29,5 @@ const SampleLiveChatMessageList = () => { export const LiveChatMessageList = { render: () => <SampleLiveChatMessageList />, - name: 'AmityLiveChatMessageList', + name: 'MessageList', }; diff --git a/src/v4/chat/components/AmityLiveChatMessageList/index.tsx b/src/v4/chat/components/MessageList/MessageList.tsx similarity index 75% rename from src/v4/chat/components/AmityLiveChatMessageList/index.tsx rename to src/v4/chat/components/MessageList/MessageList.tsx index 84e7a7e69..a25823c60 100644 --- a/src/v4/chat/components/AmityLiveChatMessageList/index.tsx +++ b/src/v4/chat/components/MessageList/MessageList.tsx @@ -1,13 +1,12 @@ -import React from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import InfiniteScroll from 'react-infinite-scroll-component'; -import styles from './styles.module.css'; -import LivechatLoadingIndicator from '~/v4/chat/components/LivechatLoadingIndicator'; + +import { LiveChatLoadingIndicator } from './LiveChatLoadingIndicator'; import useSDK from '~/core/hooks/useSDK'; -import AmityLiveChatMessageSenderView from '../AmityLiveChatMessageSenderView'; -import AmityLiveChatMessageReceiverView from '../AmityLiveChatMessageReceiverView'; +import { SenderMessageBubble } from '~/v4/chat/elements/SenderMessageBubble'; +import { ReceiverMessageBubble } from '~/v4/chat/elements/ReceiverMessageBubble'; import { deleteMessage, flagMessage } from '~/v4/utils'; import useMessagesCollection from '~/chat/hooks/collections/useMessagesCollection'; -import { FormattedMessage, useIntl } from 'react-intl'; import { Typography } from '~/v4/core/components'; import Redo from '~/v4/icons/Redo'; import { unFlagMessage } from '~/v4/utils/unFlagMessage'; @@ -16,13 +15,15 @@ import { useLiveChatNotifications } from '~/v4/chat/providers/LiveChatNotificati import { useCopyMessage } from '~/v4/core/hooks'; import { useAmityComponent } from '~/v4/core/hooks/uikit'; +import styles from './MessageList.module.css'; + interface AmityLiveChatMessageListProps { pageId?: string; channel: Amity.Channel; replyMessage: (message: Amity.Message<'text'>) => void; } -export const AmityLiveChatMessageList = ({ +export const MessageList = ({ pageId = '*', channel, replyMessage, @@ -30,9 +31,8 @@ export const AmityLiveChatMessageList = ({ const componentId = 'message_list'; const sdk = useSDK(); - const containerRef = React.useRef<HTMLDivElement | null>(null); - const { formatMessage } = useIntl(); - const [height, setHeight] = React.useState<number | undefined>(undefined); + const containerRef = useRef<HTMLDivElement | null>(null); + const [height, setHeight] = useState<number | undefined>(undefined); const { confirm } = useConfirmContext(); const notification = useLiveChatNotifications(); const copyMessage = useCopyMessage(); @@ -56,21 +56,21 @@ export const AmityLiveChatMessageList = ({ const onDeleteMessage = (messageId: string) => { confirm({ - title: formatMessage({ id: 'livechat.modal.delete.message.title' }), - content: formatMessage({ id: 'livechat.modal.delete.message.content' }), - cancelText: formatMessage({ id: 'cancel' }), - okText: formatMessage({ id: 'delete' }), + title: 'Delete this message?', + content: 'This message will also be removed from your friend’s devices.', + cancelText: 'Cancel', + okText: 'Delete', onOk: () => deleteMessage(messageId, { onError: () => notification.error({ - content: formatMessage({ id: 'livechat.delete.message.error' }), + content: 'Unable to delete message. Please try again.', }), }), }); }; - React.useEffect(() => { + useEffect(() => { const updateHeight = () => { if (containerRef.current) { setHeight(containerRef.current.clientHeight); @@ -95,9 +95,7 @@ export const AmityLiveChatMessageList = ({ <div className={styles.iconContainer}> <Redo /> </div> - <Typography.Caption> - <FormattedMessage id="chat.loading.error" /> - </Typography.Caption> + <Typography.Caption>Couldn't load chat</Typography.Caption> </div> ); } @@ -114,7 +112,7 @@ export const AmityLiveChatMessageList = ({ loader={ isLoading ? ( <div className={styles.loadingIndicatorWrap}> - <LivechatLoadingIndicator className={styles.loadingIndicator} /> + <LiveChatLoadingIndicator className={styles.loadingIndicator} /> </div> ) : null } @@ -125,7 +123,7 @@ export const AmityLiveChatMessageList = ({ {messages.map((message, i) => { if (message.creatorId === sdk.currentUserId) return ( - <AmityLiveChatMessageSenderView + <SenderMessageBubble message={message} action={ !channel.isMuted @@ -144,7 +142,7 @@ export const AmityLiveChatMessageList = ({ ); return ( - <AmityLiveChatMessageReceiverView + <ReceiverMessageBubble message={message} action={{ onReply: () => replyMessage(message), @@ -155,11 +153,11 @@ export const AmityLiveChatMessageList = ({ message.messageId, () => notification.success({ - content: formatMessage({ id: 'livechat.report.message.success' }), + content: 'Message reported', }), () => notification.error({ - content: formatMessage({ id: 'livechat.report.message.error' }), + content: 'This message failed to be reported. Please try again.', }), ), onUnflag: () => @@ -167,11 +165,11 @@ export const AmityLiveChatMessageList = ({ message.messageId, () => notification.success({ - content: formatMessage({ id: 'livechat.unReport.message.success' }), + content: 'Message unreported', }), () => notification.error({ - content: formatMessage({ id: 'livechat.unReport.message.error' }), + content: 'This message failed to be unreported. Please try again.', }), ), }} @@ -187,5 +185,3 @@ export const AmityLiveChatMessageList = ({ </div> ); }; - -export default AmityLiveChatMessageList; diff --git a/src/v4/chat/components/MessageList/index.tsx b/src/v4/chat/components/MessageList/index.tsx new file mode 100644 index 000000000..751517368 --- /dev/null +++ b/src/v4/chat/components/MessageList/index.tsx @@ -0,0 +1 @@ +export { MessageList } from './MessageList'; diff --git a/src/v4/chat/components/MessageQuickReaction/styles.module.css b/src/v4/chat/components/MessageQuickReaction/MessageQuickReaction.module.css similarity index 100% rename from src/v4/chat/components/MessageQuickReaction/styles.module.css rename to src/v4/chat/components/MessageQuickReaction/MessageQuickReaction.module.css diff --git a/src/v4/chat/components/MessageQuickReaction/MessageQuickReaction.tsx b/src/v4/chat/components/MessageQuickReaction/MessageQuickReaction.tsx new file mode 100644 index 000000000..9d680d502 --- /dev/null +++ b/src/v4/chat/components/MessageQuickReaction/MessageQuickReaction.tsx @@ -0,0 +1,46 @@ +import React, { useCallback } from 'react'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { AmityReactionType, useCustomReaction } from '~/v4/core/providers/CustomReactionProvider'; +import { QuickReactionIcon } from '~/v4/icons/QuickReactionIcon'; +import { selectMessageReaction } from '~/v4/utils/selectMessageReaction'; +import styles from './MessageQuickReaction.module.css'; + +interface MessageQuickReactionProps { + pageId?: string; + componentId?: string; + message: Amity.Message; + onSelectReaction?: () => void; +} + +export const MessageQuickReaction = ({ + pageId = '*', + componentId = '*', + message, + onSelectReaction, +}: MessageQuickReactionProps) => { + const elementId = 'message_quick_reaction'; + + const { config } = useAmityElement({ pageId, componentId, elementId }); + const { config: reactionConfig } = useCustomReaction(); + + const onClickQuickReaction = useCallback(() => { + if ( + reactionConfig && + config.reaction && + reactionConfig.find((reaction) => reaction.name === config.reaction) + ) { + selectMessageReaction({ + reactionName: config.reaction as AmityReactionType['name'], + message, + }); + } + + onSelectReaction && onSelectReaction(); + }, [reactionConfig, config, message]); + + return ( + <div className={styles.quickReactionIconContainer}> + <QuickReactionIcon className={styles.quickReactionIcon} onClick={onClickQuickReaction} /> + </div> + ); +}; diff --git a/src/v4/chat/components/MessageQuickReaction/index.tsx b/src/v4/chat/components/MessageQuickReaction/index.tsx index 7ff9939e9..7cb1e7e3a 100644 --- a/src/v4/chat/components/MessageQuickReaction/index.tsx +++ b/src/v4/chat/components/MessageQuickReaction/index.tsx @@ -1,46 +1 @@ -import React, { useCallback } from 'react'; -import { useAmityElement } from '~/v4/core/hooks/uikit'; -import { AmityReactionType, useCustomReaction } from '~/v4/core/providers/CustomReactionProvider'; -import { QuickReactionIcon } from '~/v4/icons/QuickReactionIcon'; -import { selectMessageReaction } from '~/v4/utils/selectMessageReaction'; -import styles from './styles.module.css'; - -interface MessageQuickReactionProps { - pageId?: string; - componentId?: string; - message: Amity.Message; - onSelectReaction?: () => void; -} - -export const MessageQuickReaction = ({ - pageId = '*', - componentId = '*', - message, - onSelectReaction, -}: MessageQuickReactionProps) => { - const elementId = 'message_quick_reaction'; - - const { config } = useAmityElement({ pageId, componentId, elementId }); - const { config: reactionConfig } = useCustomReaction(); - - const onClickQuickReaction = useCallback(() => { - if ( - reactionConfig && - config.reaction && - reactionConfig.find((reaction) => reaction.name === config.reaction) - ) { - selectMessageReaction({ - reactionName: config.reaction as AmityReactionType['name'], - message, - }); - } - - onSelectReaction && onSelectReaction(); - }, [reactionConfig, config, message]); - - return ( - <div className={styles.quickReactionIconContainer}> - <QuickReactionIcon className={styles.quickReactionIcon} onClick={onClickQuickReaction} /> - </div> - ); -}; +export { MessageQuickReaction } from './MessageQuickReaction'; diff --git a/src/v4/chat/components/index.ts b/src/v4/chat/components/index.ts deleted file mode 100644 index 9b139945e..000000000 --- a/src/v4/chat/components/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -export { AmityLiveChatHeader } from './AmityLiveChatHeader'; -export { AmityLiveChatMessageReceiverView } from './AmityLiveChatMessageReceiverView'; -export { AmityLiveChatMessageSenderView } from './AmityLiveChatMessageSenderView'; -export { AmityLiveChatMessageList } from './AmityLiveChatMessageList'; -export { AmityLiveChatMessageComposeBar } from './AmityLiveChatMessageComposeBar'; - -export { MessageReactionPicker } from './MessageReactionPicker'; -export { MessageQuickReaction } from './MessageQuickReaction'; -export { MessageReactionPreview } from './MessageReactionPreview'; - -import type { AmityMessageActionType } from './LiveChatMessageContent/MessageAction'; -export type { AmityMessageActionType }; diff --git a/src/v4/chat/components/AmityLiveChatMessageSenderView/index.tsx b/src/v4/chat/elements/ReceiverMessageBubble/ReceiverMessageBubble.tsx similarity index 51% rename from src/v4/chat/components/AmityLiveChatMessageSenderView/index.tsx rename to src/v4/chat/elements/ReceiverMessageBubble/ReceiverMessageBubble.tsx index 1b5f8f870..7deadc5ac 100644 --- a/src/v4/chat/components/AmityLiveChatMessageSenderView/index.tsx +++ b/src/v4/chat/elements/ReceiverMessageBubble/ReceiverMessageBubble.tsx @@ -1,38 +1,38 @@ import React from 'react'; -import useUser from '~/core/hooks/useUser'; -import useImage from '~/core/hooks/useImage'; -import LiveChatMessageContent from '../LiveChatMessageContent'; -import { AmityMessageActionType } from '../LiveChatMessageContent/MessageAction'; +import LiveChatMessageContent from '~/v4/chat/internal-components/LiveChatMessageContent'; +import { MessageActionType } from '~/v4/chat/internal-components/LiveChatMessageContent/MessageAction'; +import { useUser } from '~/v4/core/hooks/objects/useUser'; +import { useImage } from '~/v4/core/hooks/useImage'; -interface AmityLiveChatMessageSenderViewProps { +interface ReceiverMessageBubbleProps { pageId?: string; componentId?: string; message: Amity.Message; containerRef: React.RefObject<HTMLDivElement>; - action?: AmityMessageActionType; + action: MessageActionType; } -export const AmityLiveChatMessageSenderView = ({ +export const ReceiverMessageBubble = ({ pageId = '*', componentId = '*', message, containerRef, action, -}: AmityLiveChatMessageSenderViewProps) => { - const user = useUser(message.creatorId); +}: ReceiverMessageBubbleProps) => { + const elementId = 'receiver_message_bubble'; + const { user } = useUser(message.creatorId); const avatarFileUrl = useImage({ fileId: user?.avatarFileId, imageSize: 'small' }); return ( <LiveChatMessageContent + pageId={pageId} + componentId={componentId} + elementId={elementId} message={message as Amity.Message<'text'>} userDisplayName={user?.displayName} avatarUrl={avatarFileUrl} containerRef={containerRef} action={action} - pageId={pageId} - componentId={componentId} /> ); }; - -export default AmityLiveChatMessageSenderView; diff --git a/src/v4/chat/elements/ReceiverMessageBubble/index.tsx b/src/v4/chat/elements/ReceiverMessageBubble/index.tsx new file mode 100644 index 000000000..340fcd8a2 --- /dev/null +++ b/src/v4/chat/elements/ReceiverMessageBubble/index.tsx @@ -0,0 +1 @@ +export { ReceiverMessageBubble } from './ReceiverMessageBubble'; diff --git a/src/v4/chat/components/AmityLiveChatMessageSenderView/messageSender.stories.tsx b/src/v4/chat/elements/SenderMessageBubble/SenderMessageBubble.stories.tsx similarity index 84% rename from src/v4/chat/components/AmityLiveChatMessageSenderView/messageSender.stories.tsx rename to src/v4/chat/elements/SenderMessageBubble/SenderMessageBubble.stories.tsx index 65e4cc4c2..c5b61f3e1 100644 --- a/src/v4/chat/components/AmityLiveChatMessageSenderView/messageSender.stories.tsx +++ b/src/v4/chat/elements/SenderMessageBubble/SenderMessageBubble.stories.tsx @@ -1,10 +1,10 @@ import React, { useRef } from 'react'; import useChannelsCollection from '~/chat/hooks/collections/useChannelsCollection'; import useMessagesCollection from '~/chat/hooks/collections/useMessagesCollection'; -import AmityLiveChatMessageSenderView from '.'; +import { SenderMessageBubble } from './SenderMessageBubble'; export default { - title: 'V4/AmityMessageSenderView', + title: 'V4/SenderMessageBubble', }; const SampleMessageSenderView = () => { @@ -28,12 +28,12 @@ const SampleMessageSenderView = () => { return ( <div style={{ background: 'white' }}> - <AmityLiveChatMessageSenderView containerRef={ref} message={messages[0]} action={{}} /> + <SenderMessageBubble containerRef={ref} message={messages[0]} action={{}} /> </div> ); }; export const LiveChatStory = { render: () => <SampleMessageSenderView />, - name: 'AmityLiveChatMessageSender', + name: 'SenderMessageBubble', }; diff --git a/src/v4/chat/components/AmityLiveChatMessageReceiverView/index.tsx b/src/v4/chat/elements/SenderMessageBubble/SenderMessageBubble.tsx similarity index 64% rename from src/v4/chat/components/AmityLiveChatMessageReceiverView/index.tsx rename to src/v4/chat/elements/SenderMessageBubble/SenderMessageBubble.tsx index 74859a42a..4f478111b 100644 --- a/src/v4/chat/components/AmityLiveChatMessageReceiverView/index.tsx +++ b/src/v4/chat/elements/SenderMessageBubble/SenderMessageBubble.tsx @@ -1,38 +1,38 @@ import React from 'react'; import useUser from '~/core/hooks/useUser'; import useImage from '~/core/hooks/useImage'; -import LiveChatMessageContent from '../LiveChatMessageContent'; -import { AmityMessageActionType } from '../LiveChatMessageContent/MessageAction'; +import LiveChatMessageContent from '~/v4/chat/internal-components/LiveChatMessageContent'; +import { MessageActionType } from '~/v4/chat/internal-components/LiveChatMessageContent/MessageAction'; -interface AmityLiveChatMessageReceiverViewProps { +interface SenderMessageBubbleProps { pageId?: string; componentId?: string; message: Amity.Message; containerRef: React.RefObject<HTMLDivElement>; - action: AmityMessageActionType; + action?: MessageActionType; } -export const AmityLiveChatMessageReceiverView = ({ +export const SenderMessageBubble = ({ pageId = '*', componentId = '*', message, containerRef, action, -}: AmityLiveChatMessageReceiverViewProps) => { +}: SenderMessageBubbleProps) => { + const elementId = 'sender_message_bubble'; const user = useUser(message.creatorId); const avatarFileUrl = useImage({ fileId: user?.avatarFileId, imageSize: 'small' }); return ( <LiveChatMessageContent - pageId={pageId} - componentId={componentId} message={message as Amity.Message<'text'>} userDisplayName={user?.displayName} avatarUrl={avatarFileUrl} containerRef={containerRef} action={action} + pageId={pageId} + componentId={componentId} + elementId={elementId} /> ); }; - -export default AmityLiveChatMessageReceiverView; diff --git a/src/v4/chat/elements/SenderMessageBubble/index.tsx b/src/v4/chat/elements/SenderMessageBubble/index.tsx new file mode 100644 index 000000000..18d737492 --- /dev/null +++ b/src/v4/chat/elements/SenderMessageBubble/index.tsx @@ -0,0 +1 @@ +export { SenderMessageBubble } from './SenderMessageBubble'; diff --git a/src/v4/chat/hooks/collections/useSearchChannelUser.ts b/src/v4/chat/hooks/collections/useSearchChannelUser.ts index ae4da933b..429bacb9c 100644 --- a/src/v4/chat/hooks/collections/useSearchChannelUser.ts +++ b/src/v4/chat/hooks/collections/useSearchChannelUser.ts @@ -1,15 +1,23 @@ -import useLiveCollection from '~/core/hooks/useLiveCollection'; +import useLiveCollection from '~/v4/core/hooks/useLiveCollection'; import { ChannelRepository } from '@amityco/ts-sdk'; -const useSearchChannelUser = ( - channelId: Amity.Channel['channelId'], - memberships: Amity.QueryChannelMembers['memberships'], - search?: string | null, -) => { +export const useSearchChannelUser = ({ + channelId, + memberships, + search, + limit = 20, + shouldCall = true, +}: { + channelId: Amity.Channel['channelId']; + memberships: Amity.QueryChannelMembers['memberships']; + limit?: number; + search?: string | null; + shouldCall?: boolean; +}) => { const { items, ...rest } = useLiveCollection({ fetcher: ChannelRepository.Membership.searchMembers, - params: { channelId, search: search || '', memberships }, - shouldCall: () => !!channelId, + params: { channelId, search: search || '', memberships, limit }, + shouldCall: !!channelId && shouldCall, }); return { @@ -17,5 +25,3 @@ const useSearchChannelUser = ( ...rest, }; }; - -export default useSearchChannelUser; diff --git a/src/v4/chat/hooks/useChannel.ts b/src/v4/chat/hooks/useChannel.ts index e90e36ade..44494ddcc 100644 --- a/src/v4/chat/hooks/useChannel.ts +++ b/src/v4/chat/hooks/useChannel.ts @@ -1,12 +1,12 @@ import { ChannelRepository } from '@amityco/ts-sdk'; -import useLiveObject from '~/core/hooks/useLiveObject'; +import useLiveObject from '~/v4/core/hooks/useLiveObject'; -const useChannel = (channelId?: string) => { - return useLiveObject({ +export const useChannel = ({ channelId }: { channelId?: string }) => { + const { item, ...rest } = useLiveObject({ fetcher: ChannelRepository.getChannel, params: channelId, - shouldCall: () => !!channelId, + shouldCall: !!channelId, }); -}; -export default useChannel; + return { channel: item, ...rest }; +}; diff --git a/src/v4/chat/hooks/useChannelPermission.ts b/src/v4/chat/hooks/useChannelPermission.ts index 1d42662c1..a1ae6bf54 100644 --- a/src/v4/chat/hooks/useChannelPermission.ts +++ b/src/v4/chat/hooks/useChannelPermission.ts @@ -1,20 +1,18 @@ -import { useEffect, useState } from 'react'; +import { useMemo } from 'react'; import useSDK from '~/core/hooks/useSDK'; -const useChannelPermission = (subChannelId: Amity.SubChannel['subChannelId']) => { +export const useChannelPermission = (subChannelId?: Amity.SubChannel['subChannelId']) => { const { client } = useSDK(); - const [isModerator, setIsModerator] = useState(false); - useEffect(() => { + const isModerator = useMemo(() => { + if (!subChannelId) return false; const currentUser = client?.hasPermission('MUTE_CHANNEL').currentUser() || false; const currentUserInChannel = client?.hasPermission('MUTE_CHANNEL').channel(subChannelId) || false; - setIsModerator(currentUser || currentUserInChannel); + return currentUser || currentUserInChannel; }, [subChannelId]); return { isModerator, }; }; - -export default useChannelPermission; diff --git a/src/v4/chat/hooks/useCommunity.ts b/src/v4/chat/hooks/useCommunity.ts index 5757ddd9e..cdac6c13f 100644 --- a/src/v4/chat/hooks/useCommunity.ts +++ b/src/v4/chat/hooks/useCommunity.ts @@ -1,15 +1,21 @@ import { CommunityRepository } from '@amityco/ts-sdk'; +import useLiveObject from '~/v4/core/hooks/useLiveObject'; -import useLiveObject from '~/core/hooks/useLiveObject'; - -// breaking changes - -const useCommunity = (communityId: string | null | undefined) => { - return useLiveObject({ +export const useCommunity = ({ + communityId, + shouldCall = true, +}: { + communityId: string | null | undefined; + shouldCall?: boolean; +}) => { + const { item, ...rest } = useLiveObject({ fetcher: CommunityRepository.getCommunity, params: communityId, - shouldCall: () => !!communityId, + shouldCall: !!communityId && shouldCall, }); -}; -export default useCommunity; + return { + community: item, + ...rest, + }; +}; diff --git a/src/v4/chat/hooks/useCurrentUserChannelMembership.ts b/src/v4/chat/hooks/useCurrentUserChannelMembership.ts index bb50901a4..8f0d4de95 100644 --- a/src/v4/chat/hooks/useCurrentUserChannelMembership.ts +++ b/src/v4/chat/hooks/useCurrentUserChannelMembership.ts @@ -1,13 +1,13 @@ import useSDK from '~/core/hooks/useSDK'; -import useSearchChannelUser from '~/v4/chat/hooks/collections/useSearchChannelUser'; +import { useSearchChannelUser } from '~/v4/chat/hooks/collections/useSearchChannelUser'; const useCurrentUserChannelMembership = (channelId: Amity.Channel['channelId']) => { const { currentUserId } = useSDK(); - const { channelMembers, isLoading } = useSearchChannelUser( + const { channelMembers, isLoading } = useSearchChannelUser({ channelId, - ['member', 'banned', 'muted'], - currentUserId, - ); + memberships: ['member', 'banned', 'muted'], + search: currentUserId, + }); if (isLoading) return null; diff --git a/src/v4/chat/hooks/useMention.ts b/src/v4/chat/hooks/useMention.ts index c721fbfdc..952686daa 100644 --- a/src/v4/chat/hooks/useMention.ts +++ b/src/v4/chat/hooks/useMention.ts @@ -1,7 +1,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { ChannelRepository, CommunityRepository, UserRepository } from '@amityco/ts-sdk'; import { extractMetadata, formatMentionees, isNonNullable } from '~/v4/helpers/utils'; -import useCommunity from './useCommunity'; +import { useCommunity } from './useCommunity'; interface UseMentionProps { targetId?: string; @@ -22,7 +22,7 @@ export type QueryMentioneesFnType = (query?: string) => Promise< const useMention = ({ targetId, targetType, remoteText, remoteMarkup }: UseMentionProps) => { const isCommunityFeed = targetType === 'community'; const isChannel = targetType === 'channel'; - const community = useCommunity(targetId); + const { community } = useCommunity({ communityId: targetId }); const [text, setText] = useState(remoteText ?? ''); const [markup, setMarkup] = useState(remoteMarkup ?? remoteText); diff --git a/src/v4/chat/components/HomeIndicator/index.tsx b/src/v4/chat/internal-components/HomeIndicator/index.tsx similarity index 76% rename from src/v4/chat/components/HomeIndicator/index.tsx rename to src/v4/chat/internal-components/HomeIndicator/index.tsx index 3e848887e..9c139e84b 100644 --- a/src/v4/chat/components/HomeIndicator/index.tsx +++ b/src/v4/chat/internal-components/HomeIndicator/index.tsx @@ -1,12 +1,10 @@ import React from 'react'; import styles from './styles.module.css'; -const HomeIndecator = () => { +export const HomeIndicator = () => { return ( <div className={styles.homeIndicatorContainer}> <div className={styles.homeIndicator} /> </div> ); }; - -export default HomeIndecator; diff --git a/src/v4/chat/components/HomeIndicator/styles.module.css b/src/v4/chat/internal-components/HomeIndicator/styles.module.css similarity index 100% rename from src/v4/chat/components/HomeIndicator/styles.module.css rename to src/v4/chat/internal-components/HomeIndicator/styles.module.css diff --git a/src/v4/chat/components/LiveChatMessageContent/MessageAction/index.tsx b/src/v4/chat/internal-components/LiveChatMessageContent/MessageAction/index.tsx similarity index 77% rename from src/v4/chat/components/LiveChatMessageContent/MessageAction/index.tsx rename to src/v4/chat/internal-components/LiveChatMessageContent/MessageAction/index.tsx index 38bfc9136..6f7d480e4 100644 --- a/src/v4/chat/components/LiveChatMessageContent/MessageAction/index.tsx +++ b/src/v4/chat/internal-components/LiveChatMessageContent/MessageAction/index.tsx @@ -1,7 +1,6 @@ -import React, { useRef } from 'react'; +import React, { useRef, useState } from 'react'; import styles from './styles.module.css'; import Kebub from '~/v4/icons/Kebub'; -import { useIntl } from 'react-intl'; import Popover from '~/v4/core/components/Popover'; import Reply from '~/v4/icons/Reply'; import Copy from '~/v4/icons/Copy'; @@ -9,7 +8,7 @@ import Bin from '~/v4/icons/Bin'; import Flag from '~/v4/icons/Flag'; import { Typography } from '~/v4/core/components'; -export type AmityMessageActionType = { +export type MessageActionType = { onCopy?: () => void; onFlag?: () => void; onUnflag?: () => void; @@ -22,19 +21,18 @@ interface MessageActionProps { isOwner: boolean; isModerator: boolean; isFlagged?: boolean; - action: AmityMessageActionType; + action: MessageActionType; containerRef: React.RefObject<HTMLDivElement>; } -const MessageAction = ({ +export const MessageAction = ({ isOwner, isModerator, isFlagged, action, containerRef, }: MessageActionProps) => { - const [isPopoverOpen, setIsPopoverOpen] = React.useState(false); - const { formatMessage } = useIntl(); + const [isPopoverOpen, setIsPopoverOpen] = useState(false); const clickMeButtonRef = useRef<HTMLDivElement | null>(null); const onCopyMessage = () => { @@ -74,17 +72,13 @@ const MessageAction = ({ <> <div className={styles.messageActionButton} onClick={onReplyMessage}> <div className={styles.messageActionButtonText}> - <Typography.Body> - {formatMessage({ id: 'livechat.messageBubble.reply.button' })} - </Typography.Body> + <Typography.Body>Reply</Typography.Body> </div> <Reply className={styles.replyIcon} /> </div> <div className={styles.messageActionButton} onClick={onCopyMessage}> <div className={styles.messageActionButtonText}> - <Typography.Body> - {formatMessage({ id: 'livechat.messageBubble.copy.button' })} - </Typography.Body> + <Typography.Body>Copy</Typography.Body> </div> <Copy className={styles.copyIcon} /> </div> @@ -110,15 +104,7 @@ const MessageAction = ({ onClick={isFlagged ? onUnFlagMessage : onFlagMessage} > <div className={styles.messageDangerActionButtonText}> - <Typography.Body> - {formatMessage({ - id: `${ - isFlagged - ? 'livechat.messageBubble.unReport.button' - : 'livechat.messageBubble.report.button' - }`, - })} - </Typography.Body> + <Typography.Body>{isFlagged ? 'Unreport' : 'Report'}</Typography.Body> </div> <Flag className={styles.flagIcon} /> </div> @@ -126,9 +112,7 @@ const MessageAction = ({ {(isOwner || isModerator) && ( <div className={styles.messageActionButton} onClick={onDeleteMessage}> <div className={styles.messageDangerActionButtonText}> - <Typography.Body> - {formatMessage({ id: 'livechat.messageBubble.delete.button' })} - </Typography.Body> + <Typography.Body>Delete</Typography.Body> </div> <Bin className={styles.binIcon} /> </div> @@ -150,5 +134,3 @@ const MessageAction = ({ </> ); }; - -export default MessageAction; diff --git a/src/v4/chat/components/LiveChatMessageContent/MessageAction/styles.module.css b/src/v4/chat/internal-components/LiveChatMessageContent/MessageAction/styles.module.css similarity index 100% rename from src/v4/chat/components/LiveChatMessageContent/MessageAction/styles.module.css rename to src/v4/chat/internal-components/LiveChatMessageContent/MessageAction/styles.module.css diff --git a/src/v4/chat/components/LiveChatMessageContent/MessageBubble/index.tsx b/src/v4/chat/internal-components/LiveChatMessageContent/MessageBubble/index.tsx similarity index 93% rename from src/v4/chat/components/LiveChatMessageContent/MessageBubble/index.tsx rename to src/v4/chat/internal-components/LiveChatMessageContent/MessageBubble/index.tsx index e117e257d..524e7758f 100644 --- a/src/v4/chat/components/LiveChatMessageContent/MessageBubble/index.tsx +++ b/src/v4/chat/internal-components/LiveChatMessageContent/MessageBubble/index.tsx @@ -2,7 +2,7 @@ import React from 'react'; import styles from './styles.module.css'; import useUser from '~/core/hooks/useUser'; import useMessage from '~/chat/hooks/useMessage'; -import MessageTextWithMention from '../MessageTextWithMention'; +import MessageTextWithMention from '../MessageTextWithMention/index'; import { Typography } from '~/v4/core/components'; import useSDK from '~/core/hooks/useSDK'; @@ -11,7 +11,7 @@ interface MessageBubbleProps { } const MessageBubble = ({ message }: MessageBubbleProps) => { - const userId = useSDK().currentUserId; + const { currentUserId: userId } = useSDK(); const isMentionToMe = message.metadata?.mentioned?.some( (mention: { index: number; userId: string; type: 'user' | 'channel'; length: number }) => mention.userId === userId || mention.type === 'channel', diff --git a/src/v4/chat/components/LiveChatMessageContent/MessageBubble/styles.module.css b/src/v4/chat/internal-components/LiveChatMessageContent/MessageBubble/styles.module.css similarity index 100% rename from src/v4/chat/components/LiveChatMessageContent/MessageBubble/styles.module.css rename to src/v4/chat/internal-components/LiveChatMessageContent/MessageBubble/styles.module.css diff --git a/src/v4/chat/components/LiveChatMessageContent/MessageBubbleContainer/index.tsx b/src/v4/chat/internal-components/LiveChatMessageContent/MessageBubbleContainer/index.tsx similarity index 100% rename from src/v4/chat/components/LiveChatMessageContent/MessageBubbleContainer/index.tsx rename to src/v4/chat/internal-components/LiveChatMessageContent/MessageBubbleContainer/index.tsx diff --git a/src/v4/chat/components/LiveChatMessageContent/MessageBubbleContainer/styles.module.css b/src/v4/chat/internal-components/LiveChatMessageContent/MessageBubbleContainer/styles.module.css similarity index 100% rename from src/v4/chat/components/LiveChatMessageContent/MessageBubbleContainer/styles.module.css rename to src/v4/chat/internal-components/LiveChatMessageContent/MessageBubbleContainer/styles.module.css diff --git a/src/v4/chat/components/LiveChatMessageContent/MessageReaction/index.tsx b/src/v4/chat/internal-components/LiveChatMessageContent/MessageReaction/index.tsx similarity index 100% rename from src/v4/chat/components/LiveChatMessageContent/MessageReaction/index.tsx rename to src/v4/chat/internal-components/LiveChatMessageContent/MessageReaction/index.tsx diff --git a/src/v4/chat/components/LiveChatMessageContent/MessageReaction/styles.module.css b/src/v4/chat/internal-components/LiveChatMessageContent/MessageReaction/styles.module.css similarity index 100% rename from src/v4/chat/components/LiveChatMessageContent/MessageReaction/styles.module.css rename to src/v4/chat/internal-components/LiveChatMessageContent/MessageReaction/styles.module.css diff --git a/src/v4/chat/components/LiveChatMessageContent/MessageTextWithMention/index.tsx b/src/v4/chat/internal-components/LiveChatMessageContent/MessageTextWithMention/index.tsx similarity index 100% rename from src/v4/chat/components/LiveChatMessageContent/MessageTextWithMention/index.tsx rename to src/v4/chat/internal-components/LiveChatMessageContent/MessageTextWithMention/index.tsx diff --git a/src/v4/chat/components/LiveChatMessageContent/MessageTextWithMention/styles.module.css b/src/v4/chat/internal-components/LiveChatMessageContent/MessageTextWithMention/styles.module.css similarity index 100% rename from src/v4/chat/components/LiveChatMessageContent/MessageTextWithMention/styles.module.css rename to src/v4/chat/internal-components/LiveChatMessageContent/MessageTextWithMention/styles.module.css diff --git a/src/v4/chat/components/LiveChatMessageContent/index.tsx b/src/v4/chat/internal-components/LiveChatMessageContent/index.tsx similarity index 80% rename from src/v4/chat/components/LiveChatMessageContent/index.tsx rename to src/v4/chat/internal-components/LiveChatMessageContent/index.tsx index 01bed88fc..cb46e6f57 100644 --- a/src/v4/chat/components/LiveChatMessageContent/index.tsx +++ b/src/v4/chat/internal-components/LiveChatMessageContent/index.tsx @@ -1,39 +1,40 @@ import React, { useState } from 'react'; -import styles from './styles.module.css'; +import dayjs from 'dayjs'; import { Typography } from '~/v4/core/components'; -import MessageAction, { AmityMessageActionType } from './MessageAction'; -import MessageBubbleContainer from './MessageBubbleContainer'; -import { FormattedTime, useIntl } from 'react-intl'; +import { MessageAction, MessageActionType } from './MessageAction'; +import MessageBubbleContainer from './MessageBubbleContainer/index'; import Bin from '~/v4/icons/Bin'; import useSDK from '~/core/hooks/useSDK'; -import MessageBubble from './MessageBubble'; -import useChannelPermission from '~/v4/chat/hooks/useChannelPermission'; +import MessageBubble from './MessageBubble/index'; +import { useChannelPermission } from '~/v4/chat/hooks/useChannelPermission'; import Flag from '~/v4/icons/Flag'; -import { MessageReaction } from './MessageReaction'; +import { MessageReaction } from './MessageReaction/index'; import { MessageReactionPreview } from '~/v4/chat/components/MessageReactionPreview'; import Sheet from 'react-modal-sheet'; import { ReactionList } from '~/v4/social/components'; +import styles from './styles.module.css'; interface MessageItemProps { pageId?: string; componentId?: string; + elementId?: string; message: Amity.Message<'text'>; userDisplayName?: string; avatarUrl?: string; containerRef: React.RefObject<HTMLDivElement>; - action?: AmityMessageActionType; + action?: MessageActionType; } const LiveChatMessageContent = ({ pageId = '*', componentId = '*', + elementId = '*', message, avatarUrl, userDisplayName, containerRef, action, }: MessageItemProps) => { - const { formatMessage } = useIntl(); const sdk = useSDK(); const isOwner = message.creatorId === sdk.currentUserId; const { isModerator } = useChannelPermission(message.channelId); @@ -47,11 +48,7 @@ const LiveChatMessageContent = ({ <div className={styles.messageDeletedBubble}> <Bin className={styles.binIcon} /> <div> - <Typography.Body> - {formatMessage({ - id: 'livechat.deleted.message', - })} - </Typography.Body> + <Typography.Body>This message was deleted</Typography.Body> </div> </div> ) : ( @@ -79,9 +76,7 @@ const LiveChatMessageContent = ({ componentId={componentId} /> {message.flagCount > 0 && <Flag className={styles.flagIcon} />} - <div className={styles.timestamp}> - <FormattedTime value={new Date(message.createdAt)} /> - </div> + <div className={styles.timestamp}>{dayjs(message.createdAt).format('HH:mm A')}</div> </div> )} </div> diff --git a/src/v4/chat/components/LiveChatMessageContent/styles.module.css b/src/v4/chat/internal-components/LiveChatMessageContent/styles.module.css similarity index 100% rename from src/v4/chat/components/LiveChatMessageContent/styles.module.css rename to src/v4/chat/internal-components/LiveChatMessageContent/styles.module.css diff --git a/src/v4/chat/components/LiveChatNotification/styles.module.css b/src/v4/chat/internal-components/LiveChatNotification/LiveChatNotification.module.css similarity index 96% rename from src/v4/chat/components/LiveChatNotification/styles.module.css rename to src/v4/chat/internal-components/LiveChatNotification/LiveChatNotification.module.css index 336baa6ab..55b3f4c6e 100644 --- a/src/v4/chat/components/LiveChatNotification/styles.module.css +++ b/src/v4/chat/internal-components/LiveChatNotification/LiveChatNotification.module.css @@ -1,5 +1,3 @@ -/* styles.module.css */ - .notifications { position: relative; bottom: var(--asc-spacing-m1); diff --git a/src/v4/chat/components/LiveChatNotification/index.tsx b/src/v4/chat/internal-components/LiveChatNotification/LiveChatNotification.tsx similarity index 82% rename from src/v4/chat/components/LiveChatNotification/index.tsx rename to src/v4/chat/internal-components/LiveChatNotification/LiveChatNotification.tsx index 5b33fde89..a9174bda4 100644 --- a/src/v4/chat/components/LiveChatNotification/index.tsx +++ b/src/v4/chat/internal-components/LiveChatNotification/LiveChatNotification.tsx @@ -1,16 +1,17 @@ import React, { ReactNode } from 'react'; import clsx from 'clsx'; -import styles from './styles.module.css'; import { Typography } from '~/v4/core/components/index'; import { useLiveChatNotificationData } from '~/v4/chat/providers/LiveChatNotificationProvider'; +import styles from './LiveChatNotification.module.css'; + interface NotificationProps { className?: string; content: ReactNode; icon?: ReactNode; } -const LiveChatNotification = ({ className, content, icon }: NotificationProps) => ( +export const LiveChatNotification = ({ className, content, icon }: NotificationProps) => ( <div className={clsx(styles.notificationContainer, className)}> {icon} <Typography.Body>{content}</Typography.Body> @@ -28,5 +29,3 @@ export const LiveChatNotificationsContainer = () => { </div> ); }; - -export default LiveChatNotification; diff --git a/src/v4/chat/internal-components/LiveChatNotification/index.tsx b/src/v4/chat/internal-components/LiveChatNotification/index.tsx new file mode 100644 index 000000000..95293f518 --- /dev/null +++ b/src/v4/chat/internal-components/LiveChatNotification/index.tsx @@ -0,0 +1 @@ +export { LiveChatNotification, LiveChatNotificationsContainer } from './LiveChatNotification'; diff --git a/src/v4/chat/pages/AmityLiveChatPage/styles.module.css b/src/v4/chat/pages/AmityLiveChatPage/styles.module.css deleted file mode 100644 index 176a8d68b..000000000 --- a/src/v4/chat/pages/AmityLiveChatPage/styles.module.css +++ /dev/null @@ -1,24 +0,0 @@ -.messageListSheet { - z-index: 1000 !important; - - @media (width >= 768px) { - width: 375px !important; - } - - .messageListSheetContainer { - height: 100% !important; - background-color: var(--asc-color-background-default) !important; - } -} - -.messageListHeaderWrap { - box-shadow: var(--asc-box-shadow-01); -} - -.amtiyLivechatPage { - display: flex; - flex-direction: column; - background-color: var(--asc-color-background-default); - height: 100%; - overflow-y: hidden; -} diff --git a/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ChatCustomState.tsx b/src/v4/chat/pages/LiveChat/ChatContainer/ChatCustomState.tsx similarity index 100% rename from src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ChatCustomState.tsx rename to src/v4/chat/pages/LiveChat/ChatContainer/ChatCustomState.tsx diff --git a/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ChatLoadingState.tsx b/src/v4/chat/pages/LiveChat/ChatContainer/ChatLoadingState.tsx similarity index 76% rename from src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ChatLoadingState.tsx rename to src/v4/chat/pages/LiveChat/ChatContainer/ChatLoadingState.tsx index 30ea7d9d8..dab804b78 100644 --- a/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ChatLoadingState.tsx +++ b/src/v4/chat/pages/LiveChat/ChatContainer/ChatLoadingState.tsx @@ -1,8 +1,7 @@ import React from 'react'; -import { FormattedMessage } from 'react-intl'; import { Typography } from '~/v4/core/components'; import Spinner from '~/social/components/Spinner'; -import HomeIndicator from '~/v4/chat/components/HomeIndicator'; +import { HomeIndicator } from '~/v4/chat/internal-components/HomeIndicator'; import styles from './styles.module.css'; const ChatLoadingState = ({ children }: { children?: React.ReactNode }) => { @@ -15,9 +14,7 @@ const ChatLoadingState = ({ children }: { children?: React.ReactNode }) => { <div className={styles.composeBarLoading}> <Spinner width={20} height={20} /> <span> - <Typography.Body> - <FormattedMessage id="loading.chat" /> - </Typography.Body> + <Typography.Body>Loading chat...</Typography.Body> </span> </div> </div> diff --git a/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ChatReadyState.tsx b/src/v4/chat/pages/LiveChat/ChatContainer/ChatReadyState.tsx similarity index 69% rename from src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ChatReadyState.tsx rename to src/v4/chat/pages/LiveChat/ChatContainer/ChatReadyState.tsx index 58c46e592..1fac0dcf6 100644 --- a/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ChatReadyState.tsx +++ b/src/v4/chat/pages/LiveChat/ChatContainer/ChatReadyState.tsx @@ -1,20 +1,19 @@ import React, { useEffect, useRef, useState } from 'react'; -import { AmityLiveChatMessageList } from '~/v4/chat/components/AmityLiveChatMessageList'; -import AmityLiveChatMessageComposeBar from '~/v4/chat/components/AmityLiveChatMessageComposeBar'; -import ReplyMessagePlaceholder from '~/v4/chat/pages/AmityLiveChatPage/ChatContainer/ReplyMessagePlaceholder'; +import { MessageList } from '~/v4/chat/components/MessageList'; +import { MessageComposer } from '~/v4/chat/components/MessageComposer'; +import { ReplyMessagePlaceholder } from '~/v4/chat/pages/LiveChat/ChatContainer/ReplyMessagePlaceholder'; import useConnectionStates from '~/social/hooks/useConnectionStates'; -import ChatLoadingState from '~/v4/chat/pages/AmityLiveChatPage/ChatContainer/ChatLoadingState'; -import ChatCustomState from '~/v4/chat/pages/AmityLiveChatPage/ChatContainer/ChatCustomState'; +import ChatLoadingState from '~/v4/chat/pages/LiveChat/ChatContainer/ChatLoadingState'; +import ChatCustomState from '~/v4/chat/pages/LiveChat/ChatContainer/ChatCustomState'; import styles from './styles.module.css'; -import { LiveChatNotificationsContainer } from '~/v4/chat/components/LiveChatNotification'; +import { LiveChatNotificationsContainer } from '~/v4/chat/internal-components/LiveChatNotification'; import MutedIcon from '~/v4/icons/Muted'; import { Typography } from '~/v4/core/components/Typography'; import mentionStyles from '~/v4/core/components/InputText/styles.module.css'; -import { FormattedMessage } from 'react-intl'; import CommentAltExclamation from '~/v4/icons/CommentAltExclamation'; -import useSearchChannelUser from '~/v4/chat/hooks/collections/useSearchChannelUser'; +import { useSearchChannelUser } from '~/v4/chat/hooks/collections/useSearchChannelUser'; import useSDK from '~/core/hooks/useSDK'; -import useChannelPermission from '~/v4/chat/hooks/useChannelPermission'; +import { useChannelPermission } from '~/v4/chat/hooks/useChannelPermission'; const ChatReadyState = ({ pageId = '*', channel }: { pageId?: string; channel: Amity.Channel }) => { const isOnline = useConnectionStates(); @@ -22,7 +21,10 @@ const ChatReadyState = ({ pageId = '*', channel }: { pageId?: string; channel: A const { isModerator } = useChannelPermission(channel.channelId); const currentUserId = useSDK().currentUserId; - const { channelMembers } = useSearchChannelUser(channel.channelId, ['member', 'banned', 'muted']); + const { channelMembers } = useSearchChannelUser({ + channelId: channel.channelId, + memberships: ['member', 'banned', 'muted'], + }); const currentUserMembership = channelMembers.find((member) => member.userId === currentUserId); const [replyMessage, setReplyMessage] = useState<Amity.Message<'text'> | undefined>(undefined); @@ -47,11 +49,9 @@ const ChatReadyState = ({ pageId = '*', channel }: { pageId?: string; channel: A <ChatCustomState> <div className={styles.banStatePanel}> <CommentAltExclamation className={styles.commentAltIcon} /> - <Typography.Heading> - <FormattedMessage id="livechat.member.banned.title" /> - </Typography.Heading> + <Typography.Heading>You are banned from chat</Typography.Heading> <Typography.Body> - <FormattedMessage id="livechat.member.banned.description" /> + You won’t be able to participate in this chat until you’ve been unbanned. </Typography.Body> </div> </ChatCustomState> @@ -59,7 +59,7 @@ const ChatReadyState = ({ pageId = '*', channel }: { pageId?: string; channel: A return ( <> - <AmityLiveChatMessageList channel={channel} replyMessage={setReplyMessage} pageId={pageId} /> + <MessageList channel={channel} replyMessage={setReplyMessage} pageId={pageId} /> {isOnline && ( <> @@ -68,15 +68,13 @@ const ChatReadyState = ({ pageId = '*', channel }: { pageId?: string; channel: A <div className={styles.mutedChannelContainer}> <MutedIcon width={20} height={20} className={styles.mutedIcon} /> <Typography.Body> - <FormattedMessage id="livechat.channel.muted" /> + This channel has been set to read-only by the channel moderator </Typography.Body> </div> ) : currentUserMembership?.isMuted ? ( <div className={styles.mutedChannelContainer}> <MutedIcon width={20} height={20} className={styles.mutedIcon} /> - <Typography.Body> - <FormattedMessage id="livechat.member.muted" /> - </Typography.Body> + <Typography.Body>You’ve been muted by the channel moderator</Typography.Body> </div> ) : null} @@ -97,8 +95,8 @@ const ChatReadyState = ({ pageId = '*', channel }: { pageId?: string; channel: A </div> <div ref={composeBarRef}> - <AmityLiveChatMessageComposeBar - pageId="live_chat" + <MessageComposer + pageId={pageId} disabled={(!isModerator && channel.isMuted) || currentUserMembership?.isMuted} channel={channel} suggestionRef={suggestionRef} diff --git a/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ReplyMessagePlaceholder.tsx b/src/v4/chat/pages/LiveChat/ChatContainer/ReplyMessagePlaceholder.tsx similarity index 69% rename from src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ReplyMessagePlaceholder.tsx rename to src/v4/chat/pages/LiveChat/ChatContainer/ReplyMessagePlaceholder.tsx index 65e8d5604..1fdc5d7af 100644 --- a/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/ReplyMessagePlaceholder.tsx +++ b/src/v4/chat/pages/LiveChat/ChatContainer/ReplyMessagePlaceholder.tsx @@ -1,7 +1,6 @@ import React from 'react'; import useUser from '~/core/hooks/useUser'; import styles from './styles.module.css'; -import { FormattedMessage } from 'react-intl'; import CloseIcon from '~/v4/icons/Close'; import { Avatar } from '~/v4/core/components'; import User from '~/v4/icons/User'; @@ -11,7 +10,10 @@ interface ReplyMessagePlaceholderProps { onDismiss: () => void; } -const ReplyMessagePlaceholder = ({ replyMessage, onDismiss }: ReplyMessagePlaceholderProps) => { +export const ReplyMessagePlaceholder = ({ + replyMessage, + onDismiss, +}: ReplyMessagePlaceholderProps) => { const profile = useUser(replyMessage.creatorId); if (!profile) return null; @@ -22,12 +24,7 @@ const ReplyMessagePlaceholder = ({ replyMessage, onDismiss }: ReplyMessagePlaceh <Avatar avatarUrl={profile.avatar?.fileUrl} defaultImage={<User />} /> </div> <div className={styles.replyProfile}> - <div className={styles.replyProfileName}> - <FormattedMessage - id="CommentComposeBar.replayToUser" - values={{ displayName: profile.displayName }} - /> - </div> + <div className={styles.replyProfileName}>{`Replying to ${profile.displayName}`}</div> <div className={styles.replyProfileMessage}>{replyMessage.data?.text}</div> </div> <div className={styles.replyDismiss}> @@ -36,5 +33,3 @@ const ReplyMessagePlaceholder = ({ replyMessage, onDismiss }: ReplyMessagePlaceh </div> ); }; - -export default ReplyMessagePlaceholder; diff --git a/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/index.tsx b/src/v4/chat/pages/LiveChat/ChatContainer/index.tsx similarity index 100% rename from src/v4/chat/pages/AmityLiveChatPage/ChatContainer/index.tsx rename to src/v4/chat/pages/LiveChat/ChatContainer/index.tsx diff --git a/src/v4/chat/pages/AmityLiveChatPage/ChatContainer/styles.module.css b/src/v4/chat/pages/LiveChat/ChatContainer/styles.module.css similarity index 100% rename from src/v4/chat/pages/AmityLiveChatPage/ChatContainer/styles.module.css rename to src/v4/chat/pages/LiveChat/ChatContainer/styles.module.css diff --git a/src/v4/chat/pages/LiveChat/LiveChat.module.css b/src/v4/chat/pages/LiveChat/LiveChat.module.css new file mode 100644 index 000000000..3a770778a --- /dev/null +++ b/src/v4/chat/pages/LiveChat/LiveChat.module.css @@ -0,0 +1,11 @@ +.messageListHeaderWrap { + box-shadow: var(--asc-box-shadow-01); +} + +.liveChat { + display: flex; + flex-direction: column; + background-color: var(--asc-color-background-default); + height: 100%; + overflow-y: hidden; +} diff --git a/src/v4/chat/pages/AmityLiveChatPage/livechatPage.stories.tsx b/src/v4/chat/pages/LiveChat/LiveChat.stories.tsx similarity index 76% rename from src/v4/chat/pages/AmityLiveChatPage/livechatPage.stories.tsx rename to src/v4/chat/pages/LiveChat/LiveChat.stories.tsx index 7c63683bf..f4c4d4525 100644 --- a/src/v4/chat/pages/AmityLiveChatPage/livechatPage.stories.tsx +++ b/src/v4/chat/pages/LiveChat/LiveChat.stories.tsx @@ -2,17 +2,16 @@ import React, { useRef, useState } from 'react'; import useChannelsCollection from '~/chat/hooks/collections/useChannelsCollection'; import ChatItem from '~/chat/components/ChatItem'; import InfiniteScroll from 'react-infinite-scroll-component'; -import { AmityLiveChatPage } from '.'; -import styles from '~/v4/chat/pages/AmityLiveChatPage/styles.module.css'; import Sheet from 'react-modal-sheet'; +import { LiveChat } from './'; +import styles from './LiveChat.module.css'; export default { - title: 'V4/AmityLiveChatPage', + title: 'V4/LiveChat', }; const LiveChatList = () => { const [selectedChannel, setSelectedChannel] = useState<Amity.Channel['channelId'] | null>(null); - const [open, setOpen] = useState(false); const { channels, hasMore, loadMore, isLoading } = useChannelsCollection({ membership: 'all', sortBy: 'lastActivity', @@ -20,7 +19,6 @@ const LiveChatList = () => { }); const onSelectedChannel = (channelId: string) => { - setOpen(true); setSelectedChannel(channelId); }; @@ -62,20 +60,16 @@ const LiveChatList = () => { ))} </InfiniteScroll> ) : null} - - <Sheet isOpen={open} onClose={() => setOpen(false)} className={styles.messageListSheet}> - <Sheet.Container className={styles.messageListSheetContainer}> - <Sheet.Header /> - <Sheet.Content> - <AmityLiveChatPage channelId={selectedChannel || ''} /> - </Sheet.Content> - </Sheet.Container> - </Sheet> + {selectedChannel && ( + <div style={{ position: 'fixed', top: 0, left: 0, width: '100dvw', height: '100dvh' }}> + <LiveChat channelId={selectedChannel} /> + </div> + )} </div> ); }; export const LiveChatStory = { render: () => <LiveChatList />, - name: 'AmityLiveChatPage', + name: 'LiveChat', }; diff --git a/src/v4/chat/pages/AmityLiveChatPage/index.tsx b/src/v4/chat/pages/LiveChat/LiveChat.tsx similarity index 51% rename from src/v4/chat/pages/AmityLiveChatPage/index.tsx rename to src/v4/chat/pages/LiveChat/LiveChat.tsx index 6aac7f660..e7808d27b 100644 --- a/src/v4/chat/pages/AmityLiveChatPage/index.tsx +++ b/src/v4/chat/pages/LiveChat/LiveChat.tsx @@ -1,31 +1,30 @@ import React, { useRef } from 'react'; -import useChannel from '~/v4/chat/hooks/useChannel'; -import AmityLiveChatHeader from '~/v4/chat/components/AmityLiveChatHeader'; -import ChatContainer from './ChatContainer'; -import styles from './styles.module.css'; +import { useChannel } from '~/v4/chat/hooks/useChannel'; +import { ChatHeader } from '~/v4/chat/components/ChatHeader/index'; +import ChatContainer from './ChatContainer/index'; import { LiveChatNotificationProvider } from '~/v4/chat/providers/LiveChatNotificationProvider'; import { useAmityPage } from '~/v4/core/hooks/uikit'; -interface AmityLiveChatPageProps { +import styles from './LiveChat.module.css'; + +interface LiveChatProps { channelId: Amity.Channel['channelId']; } -export const AmityLiveChatPage = ({ channelId }: AmityLiveChatPageProps) => { - const channel = useChannel(channelId); +export const LiveChat = ({ channelId }: LiveChatProps) => { + const { channel } = useChannel({ channelId }); const pageId = 'live_chat'; const { themeStyles } = useAmityPage({ pageId }); const ref = useRef<HTMLDivElement>(null); return ( <LiveChatNotificationProvider> - <div className={styles.amtiyLivechatPage} ref={ref} style={themeStyles}> + <div className={styles.liveChat} ref={ref} style={themeStyles}> <div className={styles.messageListHeaderWrap}> - <AmityLiveChatHeader channel={channel} pageId={pageId} /> + <ChatHeader channel={channel} pageId={pageId} /> </div> <ChatContainer channel={channel} pageId={pageId} /> </div> </LiveChatNotificationProvider> ); }; - -export default AmityLiveChatPage; diff --git a/src/v4/chat/pages/LiveChat/index.tsx b/src/v4/chat/pages/LiveChat/index.tsx new file mode 100644 index 000000000..e6ab3c035 --- /dev/null +++ b/src/v4/chat/pages/LiveChat/index.tsx @@ -0,0 +1 @@ +export { LiveChat } from './LiveChat'; diff --git a/src/v4/chat/pages/index.ts b/src/v4/chat/pages/index.ts index 6efcdc960..f8c927461 100644 --- a/src/v4/chat/pages/index.ts +++ b/src/v4/chat/pages/index.ts @@ -1 +1 @@ -export { AmityLiveChatPage } from './AmityLiveChatPage'; +export { LiveChat as AmityLiveChatPage } from './LiveChat'; diff --git a/src/v4/core/AmityUIKitManager.ts b/src/v4/core/AmityUIKitManager.ts index b32fa6adc..760a506e8 100644 --- a/src/v4/core/AmityUIKitManager.ts +++ b/src/v4/core/AmityUIKitManager.ts @@ -14,7 +14,7 @@ interface SessionHandler { /** * Manages the Amity SDK client and authentication state. */ -class AmityUIKitManager { +export class AmityUIKitManager { private static instance: AmityUIKitManager | null = null; private client: Amity.Client | null = null; private isConnected: boolean = false; @@ -151,5 +151,3 @@ class AmityUIKitManager { return this.isConnected; } } - -export default AmityUIKitManager; diff --git a/src/v4/core/hooks/objects/useUser.ts b/src/v4/core/hooks/objects/useUser.ts index e8d379382..204cb3b78 100644 --- a/src/v4/core/hooks/objects/useUser.ts +++ b/src/v4/core/hooks/objects/useUser.ts @@ -2,7 +2,7 @@ import { UserRepository } from '@amityco/ts-sdk'; import useLiveObject from '~/v4/core/hooks/useLiveObject'; -const useUser = (userId?: string | null) => { +export const useUser = (userId?: string | null) => { const { item, ...rest } = useLiveObject({ fetcher: UserRepository.getUser, params: userId, @@ -14,5 +14,3 @@ const useUser = (userId?: string | null) => { ...rest, }; }; - -export default useUser; diff --git a/src/v4/core/hooks/subscriptions/useCommunityReactionSubscription.ts b/src/v4/core/hooks/subscriptions/useCommunityReactionSubscription.ts index 2c27916bb..9ee00892f 100644 --- a/src/v4/core/hooks/subscriptions/useCommunityReactionSubscription.ts +++ b/src/v4/core/hooks/subscriptions/useCommunityReactionSubscription.ts @@ -3,11 +3,11 @@ import useCommunitySubscription from './useCommunitySubscription'; export default function useCommunityReactionSubscription({ communityId, - shouldSubscribe = () => true, + shouldSubscribe = true, callback, }: { communityId?: string | null; - shouldSubscribe?: () => boolean; + shouldSubscribe?: boolean; callback?: Amity.Listener; }) { return useCommunitySubscription({ diff --git a/src/v4/core/hooks/subscriptions/useReactionSubscription.ts b/src/v4/core/hooks/subscriptions/useReactionSubscription.ts index 984ef4fc6..54b61f041 100644 --- a/src/v4/core/hooks/subscriptions/useReactionSubscription.ts +++ b/src/v4/core/hooks/subscriptions/useReactionSubscription.ts @@ -5,22 +5,22 @@ export default function useReactionSubscription({ targetId, targetType, callback, - shouldSubscribe = () => true, + shouldSubscribe = true, }: { targetId?: string | null; targetType: 'user' | 'community'; - shouldSubscribe?: () => boolean; + shouldSubscribe?: boolean; callback?: Amity.Listener; }) { const { unsubscribe: userUnsubscribe } = useUserReactionSubscription({ userId: targetId, - shouldSubscribe: () => shouldSubscribe() && targetType === 'user', + shouldSubscribe: shouldSubscribe && targetType === 'user', callback, }); const { unsubscribe: communityUnsubscribe } = useCommunityReactionSubscription({ communityId: targetId, - shouldSubscribe: () => shouldSubscribe() && targetType === 'community', + shouldSubscribe: shouldSubscribe && targetType === 'community', callback, }); diff --git a/src/v4/core/hooks/subscriptions/useUserReactionSubscription.ts b/src/v4/core/hooks/subscriptions/useUserReactionSubscription.ts index 3911f269c..5c106fcf1 100644 --- a/src/v4/core/hooks/subscriptions/useUserReactionSubscription.ts +++ b/src/v4/core/hooks/subscriptions/useUserReactionSubscription.ts @@ -4,10 +4,10 @@ import useUserSubscription from './useUserSubscription'; export default function useUserReactionSubscription({ userId, callback, - shouldSubscribe, + shouldSubscribe = true, }: { userId?: string | null; - shouldSubscribe?: () => boolean; + shouldSubscribe?: boolean; callback?: Amity.Listener; }) { return useUserSubscription({ diff --git a/src/v4/core/hooks/useImage.ts b/src/v4/core/hooks/useImage.ts index 18001a62b..f1f4577b5 100644 --- a/src/v4/core/hooks/useImage.ts +++ b/src/v4/core/hooks/useImage.ts @@ -7,7 +7,7 @@ interface UseImageProps { imageSize?: 'small' | 'medium' | 'large' | 'full'; } -const useImage = ({ fileId, imageSize = 'medium' }: UseImageProps) => { +export const useImage = ({ fileId, imageSize = 'medium' }: UseImageProps) => { const file = useFile(fileId); const [imageUrl, setImageUrl] = useState<string | undefined>(undefined); @@ -27,5 +27,3 @@ const useImage = ({ fileId, imageSize = 'medium' }: UseImageProps) => { return imageUrl; }; - -export default useImage; diff --git a/src/v4/core/providers/AmityUIKitProvider.tsx b/src/v4/core/providers/AmityUIKitProvider.tsx index 7241dfb53..883feb88d 100644 --- a/src/v4/core/providers/AmityUIKitProvider.tsx +++ b/src/v4/core/providers/AmityUIKitProvider.tsx @@ -27,7 +27,7 @@ import { defaultConfig, Config, CustomizationProvider } from './CustomizationPro import { ThemeProvider } from './ThemeProvider'; import { PageBehavior, PageBehaviorProvider } from './PageBehaviorProvider'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import AmityUIKitManager from '../AmityUIKitManager'; +import { AmityUIKitManager } from '../AmityUIKitManager'; import { ConfirmProvider } from '~/v4/core/providers/ConfirmProvider'; import { ConfirmProvider as LegacyConfirmProvider } from '~/core/providers/ConfirmProvider'; import { NotificationProvider } from '~/v4/core/providers/NotificationProvider'; diff --git a/src/v4/helpers/utils.ts b/src/v4/helpers/utils.ts index 74db79310..b7b69ff93 100644 --- a/src/v4/helpers/utils.ts +++ b/src/v4/helpers/utils.ts @@ -197,3 +197,35 @@ export function getCssVariableValue(variable: string) { export function convertRemToPx(rem: number) { return rem * parseFloat(getComputedStyle(document.documentElement).fontSize); } +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +const SUPPORTED_URL_PROTOCOLS = new Set(['http:', 'https:', 'mailto:', 'sms:', 'tel:']); + +export function sanitizeUrl(url: string): string { + try { + const parsedUrl = new URL(url); + // eslint-disable-next-line no-script-url + if (!SUPPORTED_URL_PROTOCOLS.has(parsedUrl.protocol)) { + return 'about:blank'; + } + } catch { + return url; + } + return url; +} + +// Source: https://stackoverflow.com/a/8234912/2013580 +const urlRegExp = new RegExp( + /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)/, +); +export function validateUrl(url: string): boolean { + // TODO Fix UI for link insertion; it should never default to an invalid URL such as https://. + // Maybe show a dialog where they user can type the URL before inserting it. + return url === 'https://' || urlRegExp.test(url); +} diff --git a/src/v4/social/components/PostCommentComposer/PostCommentComposer.tsx b/src/v4/social/components/PostCommentComposer/PostCommentComposer.tsx index 5d1c3c10c..cdf76dd71 100644 --- a/src/v4/social/components/PostCommentComposer/PostCommentComposer.tsx +++ b/src/v4/social/components/PostCommentComposer/PostCommentComposer.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useRef, useState } from 'react'; import { Avatar, Button } from '~/v4/core/components/index'; -import useUser from '~/v4/core/hooks/objects/useUser'; -import useImage from '~/v4/core/hooks/useImage'; +import { useUser } from '~/v4/core/hooks/objects/useUser'; +import { useImage } from '~/v4/core/hooks/useImage'; import useSDK from '~/v4/core/hooks/useSDK'; import User from '~/v4/icons/User'; import { PostCommentInput, PostCommentInputRef } from './PostCommentInput'; diff --git a/src/v4/social/components/PostCommentComposer/PostCommentInput.tsx b/src/v4/social/components/PostCommentComposer/PostCommentInput.tsx index 7116b39ae..81a8f8ae8 100644 --- a/src/v4/social/components/PostCommentComposer/PostCommentInput.tsx +++ b/src/v4/social/components/PostCommentComposer/PostCommentInput.tsx @@ -1,23 +1,16 @@ -import React, { forwardRef, MutableRefObject, useEffect, useImperativeHandle } from 'react'; -import { InitialEditorStateType, LexicalComposer } from '@lexical/react/LexicalComposer'; +import React, { forwardRef, MutableRefObject, useImperativeHandle } from 'react'; +import { LexicalComposer } from '@lexical/react/LexicalComposer'; import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin'; import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'; import { ContentEditable } from '@lexical/react/LexicalContentEditable'; import { EditorRefPlugin } from '@lexical/react/LexicalEditorRefPlugin'; -import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; import { $getRoot, - EditorState, LexicalEditor, SerializedLexicalNode, - $createParagraphNode, - $createTextNode, SerializedTextNode, SerializedRootNode, SerializedParagraphNode, - RootNode, - TextNode, - ParagraphNode, } from 'lexical'; import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary'; import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'; @@ -30,8 +23,7 @@ import styles from './PostCommentInput.module.css'; import { PostCommentMentionInput } from '../PostCommentMentionInput'; import { useMentionUsers } from '../../hooks/useMentionUser'; import { CreateCommentParams } from '../PostCommentComposer/PostCommentComposer'; -import { Mentioned, Mentionees, Metadata } from '~/v4/helpers/utils'; -import { text } from '../../elements/HyperLink/HyperLink.module.css'; +import { Mentioned, Mentionees } from '~/v4/helpers/utils'; const theme = { ltr: 'ltr', diff --git a/src/v4/social/components/PostContent/ImageContent/ImageContent.tsx b/src/v4/social/components/PostContent/ImageContent/ImageContent.tsx index fa9e52774..c3dea18d7 100644 --- a/src/v4/social/components/PostContent/ImageContent/ImageContent.tsx +++ b/src/v4/social/components/PostContent/ImageContent/ImageContent.tsx @@ -1,6 +1,6 @@ import React, { useMemo } from 'react'; -import useImage from '~/v4/core/hooks/useImage'; +import { useImage } from '~/v4/core/hooks/useImage'; import { Typography } from '~/v4/core/components'; import { useAmityElement } from '~/v4/core/hooks/uikit/index'; import styles from './ImageContent.module.css'; diff --git a/src/v4/social/components/PostContent/PostContent.tsx b/src/v4/social/components/PostContent/PostContent.tsx index 623494562..c2d7795c3 100644 --- a/src/v4/social/components/PostContent/PostContent.tsx +++ b/src/v4/social/components/PostContent/PostContent.tsx @@ -6,7 +6,7 @@ import { ModeratorBadge } from '~/v4/social/elements/ModeratorBadge'; import { MenuButton } from '~/v4/social/elements/MenuButton'; import { ShareButton } from '~/v4/social/elements/ShareButton'; import useCommunity from '~/v4/core/hooks/collections/useCommunity'; -import useUser from '~/v4/core/hooks/objects/useUser'; +import { useUser } from '~/v4/core/hooks/objects/useUser'; import { Typography } from '~/v4/core/components'; import AngleRight from '~/v4/icons/AngleRight'; import { UserAvatar } from '~/v4/social/internal-components/UserAvatar'; diff --git a/src/v4/social/components/PostContent/VideoContent/VideoContent.tsx b/src/v4/social/components/PostContent/VideoContent/VideoContent.tsx index be93f1618..668a9bf40 100644 --- a/src/v4/social/components/PostContent/VideoContent/VideoContent.tsx +++ b/src/v4/social/components/PostContent/VideoContent/VideoContent.tsx @@ -1,5 +1,5 @@ import React, { useMemo } from 'react'; -import useImage from '~/v4/core/hooks/useImage'; +import { useImage } from '~/v4/core/hooks/useImage'; import { Typography } from '~/v4/core/components'; import { useAmityElement } from '~/v4/core/hooks/uikit'; import styles from './VideoContent.module.css'; diff --git a/src/v4/social/components/StoryTab/StoryTabCommunity.tsx b/src/v4/social/components/StoryTab/StoryTabCommunity.tsx index 38a709e67..7d446a0be 100644 --- a/src/v4/social/components/StoryTab/StoryTabCommunity.tsx +++ b/src/v4/social/components/StoryTab/StoryTabCommunity.tsx @@ -5,7 +5,7 @@ import { StoryRing } from '~/v4/social/elements/StoryRing/StoryRing'; import clsx from 'clsx'; import { useGetActiveStoriesByTarget } from '~/v4/social/hooks/useGetActiveStories'; import useSDK from '~/v4/core/hooks/useSDK'; -import useUser from '~/v4/core/hooks/objects/useUser'; +import { useUser } from '~/v4/core/hooks/objects/useUser'; import { isAdmin, isModerator } from '~/v4/utils/permissions'; import { checkStoryPermission } from '~/v4/social/utils'; import { useCommunityInfo } from '~/v4/social/hooks/useCommunityInfo'; diff --git a/src/v4/social/components/StoryTab/StoryTabItem.tsx b/src/v4/social/components/StoryTab/StoryTabItem.tsx index 5fc1de699..cb2ec12c6 100644 --- a/src/v4/social/components/StoryTab/StoryTabItem.tsx +++ b/src/v4/social/components/StoryTab/StoryTabItem.tsx @@ -6,7 +6,7 @@ import { Typography } from '~/v4/core/components'; import Verified from '~/v4/icons/Verified'; import clsx from 'clsx'; import useCommunity from '~/v4/core/hooks/collections/useCommunity'; -import useImage from '~/v4/core/hooks/useImage'; +import { useImage } from '~/v4/core/hooks/useImage'; import styles from './StoryTabItem.module.css'; diff --git a/src/v4/social/hooks/useCommunityInfo.ts b/src/v4/social/hooks/useCommunityInfo.ts index 327d69903..300d63707 100644 --- a/src/v4/social/hooks/useCommunityInfo.ts +++ b/src/v4/social/hooks/useCommunityInfo.ts @@ -2,7 +2,7 @@ import { useMemo } from 'react'; import { CommunityRepository } from '@amityco/ts-sdk'; import useCommunityPermission from '~/social/hooks/useCommunityPermission'; import usePostsCollection from '~/social/hooks/collections/usePostsCollection'; -import useImage from '~/v4/core/hooks/useImage'; +import { useImage } from '~/v4/core/hooks/useImage'; import useCommunity from '~/v4/core/hooks/collections/useCommunity'; import useCategoriesByIds from '~/v4/social/hooks/useCategoriesByIds'; import { useNavigation } from '~/v4/core/providers/NavigationProvider'; diff --git a/src/v4/social/hooks/useGetStoryByStoryId.ts b/src/v4/social/hooks/useGetStoryByStoryId.ts index 6839e752d..8b26d7af2 100644 --- a/src/v4/social/hooks/useGetStoryByStoryId.ts +++ b/src/v4/social/hooks/useGetStoryByStoryId.ts @@ -5,7 +5,7 @@ const useGetStoryByStoryId = (storyId: string | undefined) => { const story = useLiveObject({ fetcher: StoryRepository.getStoryByStoryId, params: storyId, - shouldCall: () => !!storyId, + shouldCall: !!storyId, }); return story; diff --git a/src/v4/social/hooks/useMemberQueryByDisplayName.ts b/src/v4/social/hooks/useMemberQueryByDisplayName.ts index fc33f4649..bbbbb3191 100644 --- a/src/v4/social/hooks/useMemberQueryByDisplayName.ts +++ b/src/v4/social/hooks/useMemberQueryByDisplayName.ts @@ -57,7 +57,7 @@ export const useMemberQueryByDisplayName = ({ }, [communityId, displayName, enabled]); return { - users: items, + members: items, isLoading, hasMore, loadMore, diff --git a/src/v4/social/internal-components/Comment/index.tsx b/src/v4/social/internal-components/Comment/index.tsx index 6ea2e1964..cb641b407 100644 --- a/src/v4/social/internal-components/Comment/index.tsx +++ b/src/v4/social/internal-components/Comment/index.tsx @@ -24,7 +24,7 @@ import { LoadingIndicator } from '~/v4/social/internal-components/LoadingIndicat import useCommunityMembersCollection from '~/v4/social/hooks/collections/useCommunityMembersCollection'; import { useCommentFlaggedByMe } from '~/v4/social/hooks'; -import useImage from '~/v4/core/hooks/useImage'; +import { useImage } from '~/v4/core/hooks/useImage'; import useSDK from '~/v4/core/hooks/useSDK'; import styles from './Comment.module.css'; diff --git a/src/v4/social/internal-components/CommentAd/CommentAd.tsx b/src/v4/social/internal-components/CommentAd/CommentAd.tsx index 92f9bfbe6..bb8a3214b 100644 --- a/src/v4/social/internal-components/CommentAd/CommentAd.tsx +++ b/src/v4/social/internal-components/CommentAd/CommentAd.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { useAmityComponent } from '~/v4/core/hooks/uikit/index'; -import useImage from '~/v4/core/hooks/useImage'; import { UICommentAd } from './UICommentAd'; +import { useImage } from '~/v4/core/hooks/useImage'; interface CommentAdProps { pageId?: string; diff --git a/src/v4/social/internal-components/Lexical/nodes/MentionNode.module.css b/src/v4/social/internal-components/Lexical/nodes/MentionNode.module.css new file mode 100644 index 000000000..f9b8e7753 --- /dev/null +++ b/src/v4/social/internal-components/Lexical/nodes/MentionNode.module.css @@ -0,0 +1,3 @@ +.mention { + color: var(--asc-color-primary-default); +} diff --git a/src/v4/social/internal-components/Lexical/nodes/MentionNode.ts b/src/v4/social/internal-components/Lexical/nodes/MentionNode.ts new file mode 100644 index 000000000..f4366ea54 --- /dev/null +++ b/src/v4/social/internal-components/Lexical/nodes/MentionNode.ts @@ -0,0 +1,154 @@ +import type { Spread } from 'lexical'; + +import { + DOMConversionMap, + DOMConversionOutput, + DOMExportOutput, + EditorConfig, + LexicalNode, + NodeKey, + SerializedTextNode, + TextNode, +} from 'lexical'; + +import styles from './MentionNode.module.css'; + +export type SerializedMentionNode<T> = Spread< + { + data: T; + type: 'mention'; + version: 1; + }, + SerializedTextNode +>; + +function convertMentionElement<T>({ + domNode, + data, +}: { + domNode: HTMLElement; + data: T; +}): DOMConversionOutput | null { + const textContent = domNode.textContent; + + if (textContent !== null) { + const node = $createMentionNode<T>({ + text: textContent, + data, + }); + return { + node, + }; + } + + return null; +} + +export class MentionNode<T> extends TextNode { + __data: T; + + static getType(): string { + return 'mention'; + } + + static clone<T>(node: MentionNode<T>): MentionNode<T> { + return new MentionNode<T>({ + data: node.__data, + text: node.__text, + key: node.__key, + }); + } + + static importJSON<T>(serializedNode: SerializedMentionNode<T>): MentionNode<T> { + const node = $createMentionNode({ + text: serializedNode.text, + data: serializedNode.data, + }); + node.setTextContent(serializedNode.text); + node.setFormat(serializedNode.format); + node.setDetail(serializedNode.detail); + node.setMode(serializedNode.mode); + node.setStyle(serializedNode.style); + return node; + } + + constructor({ text, data, key }: { text: string; data: T; key?: NodeKey }) { + super(text, key); + this.__data = data; + } + + exportJSON(): SerializedMentionNode<T> { + return { + ...super.exportJSON(), + data: this.__data, + type: 'mention', + version: 1, + }; + } + + createDOM(config: EditorConfig): HTMLElement { + const dom = super.createDOM(config); + dom.className = styles.mention; //create css + return dom; + } + + exportDOM(): DOMExportOutput { + const element = document.createElement('span'); + element.setAttribute('data-lexical-mention', 'true'); + element.setAttribute('data-mention-data', JSON.stringify(this.__data)); + element.textContent = this.__text; + return { element }; + } + + isSegmented(): false { + return false; + } + + static importDOM<T>(): DOMConversionMap | null { + return { + span: (domNode: HTMLElement) => { + if (!domNode.hasAttribute('data-lexical-mention')) { + return null; + } + return { + conversion: (domNode: HTMLElement) => + convertMentionElement({ + domNode, + data: JSON.parse(domNode.getAttribute('data-mention-data') ?? '{}'), + }), + priority: 1, + }; + }, + }; + } + + isTextEntity(): true { + return true; + } + + isToken(): true { + return true; + } + + canInsertTextBefore(): boolean { + return false; + } + + canInsertTextAfter(): boolean { + return false; + } +} + +export function $createMentionNode<T>({ text, data }: { text: string; data: T }): MentionNode<T> { + const mentionNode = new MentionNode<T>({ + data, + text, + }) + .setMode('segmented') + .toggleDirectionless(); + return mentionNode; +} + +export function $isMentionNode<T>(node: LexicalNode | null | undefined): node is MentionNode<T> { + return node instanceof MentionNode; +} diff --git a/src/v4/social/internal-components/Lexical/plugins/AutoLinkPlugin.tsx b/src/v4/social/internal-components/Lexical/plugins/AutoLinkPlugin.tsx new file mode 100644 index 000000000..8d32761c7 --- /dev/null +++ b/src/v4/social/internal-components/Lexical/plugins/AutoLinkPlugin.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { AutoLinkPlugin as LexicalAutoLinkPlugin } from '@lexical/react/LexicalAutoLinkPlugin'; + +const URL_MATCHER = + /((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/; + +const MATCHERS = [ + (text: string) => { + const match = URL_MATCHER.exec(text); + if (match === null) { + return null; + } + const fullMatch = match[0]; + return { + index: match.index, + length: fullMatch.length, + text: fullMatch, + url: fullMatch.startsWith('http') ? fullMatch : `https://${fullMatch}`, + // attributes: { rel: 'noreferrer', target: '_blank' }, // Optional link attributes + }; + }, +]; + +export const AutoLinkPlugin = () => <LexicalAutoLinkPlugin matchers={MATCHERS} />; diff --git a/src/v4/social/internal-components/Lexical/plugins/EnterKeyInterceptorPlugin.tsx b/src/v4/social/internal-components/Lexical/plugins/EnterKeyInterceptorPlugin.tsx new file mode 100644 index 000000000..12b4aa514 --- /dev/null +++ b/src/v4/social/internal-components/Lexical/plugins/EnterKeyInterceptorPlugin.tsx @@ -0,0 +1,68 @@ +import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; +import { + $getSelection, + $isRangeSelection, + CommandListenerPriority, + INSERT_PARAGRAPH_COMMAND, + KEY_ENTER_COMMAND, +} from 'lexical'; +import { useEffect } from 'react'; + +const $isVisualKeyboardOpen = (window: Window) => { + const ratio = (() => { + switch (screen.orientation.type) { + case 'landscape-primary': + case 'landscape-secondary': + return 0.5; + case 'portrait-secondary': + case 'portrait-primary': + return 0.75; + default: + return 0.5; + } + })(); + + if (!window.visualViewport) { + return false; + } + + return ( + (window.visualViewport?.height * window.visualViewport?.scale) / window.screen.height < ratio + ); +}; + +export const EnterKeyInterceptorPlugin = ({ + commandPriority, + onEnter, +}: { + commandPriority: CommandListenerPriority; + onEnter: () => void; +}) => { + const [editor] = useLexicalComposerContext(); + useEffect(() => { + editor.registerCommand( + KEY_ENTER_COMMAND, + (payload) => { + const selection = $getSelection(); + if (!$isRangeSelection(selection)) { + return false; + } + if ($isVisualKeyboardOpen(window)) { + return false; + } + + const event = payload as KeyboardEvent; + event.preventDefault(); + + if (event.shiftKey) { + return editor.dispatchCommand(INSERT_PARAGRAPH_COMMAND, undefined); + } + + onEnter(); + return true; + }, + commandPriority, + ); + }, [editor, onEnter]); + return null; +}; diff --git a/src/v4/social/internal-components/Lexical/plugins/LinkPlugin.tsx b/src/v4/social/internal-components/Lexical/plugins/LinkPlugin.tsx new file mode 100644 index 000000000..bf3d7b5e4 --- /dev/null +++ b/src/v4/social/internal-components/Lexical/plugins/LinkPlugin.tsx @@ -0,0 +1,5 @@ +import React from 'react'; +import { LinkPlugin as LexicalLinkPlugin } from '@lexical/react/LexicalLinkPlugin'; +import { validateUrl } from '~/v4/helpers/utils'; + +export const LinkPlugin = () => <LexicalLinkPlugin validateUrl={validateUrl} />; diff --git a/src/v4/social/internal-components/Lexical/plugins/MentionPlugin.tsx b/src/v4/social/internal-components/Lexical/plugins/MentionPlugin.tsx new file mode 100644 index 000000000..8bc7937e9 --- /dev/null +++ b/src/v4/social/internal-components/Lexical/plugins/MentionPlugin.tsx @@ -0,0 +1,173 @@ +import React, { useCallback, useMemo } from 'react'; +import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; +import { + LexicalTypeaheadMenuPlugin, + MenuOption, + MenuRenderFn, + MenuTextMatch, +} from '@lexical/react/LexicalTypeaheadMenuPlugin'; +import { $createTextNode, $insertNodes, CommandListenerPriority, NodeKey, TextNode } from 'lexical'; + +const PUNCTUATION = '\\.,\\+\\*\\?\\$\\@\\|#{}\\(\\)\\^\\-\\[\\]\\\\/!%\'"~=<>_:;'; +const NAME = '\\b[A-Z][^\\s' + PUNCTUATION + ']'; + +const DocumentMentionsRegex = { + NAME, + PUNCTUATION, +}; + +const PUNC = DocumentMentionsRegex.PUNCTUATION; + +const TRIGGERS = ['@'].join(''); + +// Chars we expect to see in a mention (non-space, non-punctuation). +const VALID_CHARS = '[^' + TRIGGERS + PUNC + '\\s]'; + +// Non-standard series of chars. Each series must be preceded and followed by +// a valid char. +const VALID_JOINS = + '(?:' + + '\\.[ |$]|' + // E.g. "r. " in "Mr. Smith" + ' |' + // E.g. " " in "Josh Duck" + '[' + + PUNC + + ']|' + // E.g. "-' in "Salier-Hellendag" + ')'; + +const LENGTH_LIMIT = 75; + +const AtSignMentionsRegex = new RegExp( + '(^|\\s|\\()(' + + '[' + + TRIGGERS + + ']' + + '((?:' + + VALID_CHARS + + VALID_JOINS + + '){0,' + + LENGTH_LIMIT + + '})' + + ')$', +); + +// 50 is the longest alias length limit. +const ALIAS_LENGTH_LIMIT = 50; + +// Regex used to match alias. +const AtSignMentionsRegexAliasRegex = new RegExp( + '(^|\\s|\\()(' + + '[' + + TRIGGERS + + ']' + + '((?:' + + VALID_CHARS + + '){0,' + + ALIAS_LENGTH_LIMIT + + '})' + + ')$', +); + +function checkForAtSignMentions(text: string, minMatchLength: number): MenuTextMatch | null { + let match = AtSignMentionsRegex.exec(text); + + if (match === null) { + match = AtSignMentionsRegexAliasRegex.exec(text); + } + if (match !== null) { + // The strategy ignores leading whitespace but we need to know it's + // length to add it to the leadOffset + const maybeLeadingWhitespace = match[1]; + + const matchingString = match[3]; + if (matchingString.length >= minMatchLength) { + return { + leadOffset: match.index + maybeLeadingWhitespace.length, + matchingString, + replaceableString: match[2], + }; + } + } + return null; +} + +function getPossibleQueryMatch(text: string): MenuTextMatch | null { + return checkForAtSignMentions(text, 0); +} + +export class MentionTypeaheadOption<T> extends MenuOption { + data: T; + + constructor({ data, dataId }: { data: T; dataId: string }) { + super(dataId); + this.data = data; + } +} + +export function MentionPlugin<TData, TNode extends TextNode>({ + suggestions, + getSuggestionId, + onQueryChange, + $createNode, + menuRenderFn, + commandPriority, +}: { + suggestions: TData[]; + getSuggestionId: (suggestion: TData) => string; + onQueryChange: (queryString: string | null) => void; + $createNode: (data: TData, key?: NodeKey) => TNode; + menuRenderFn: MenuRenderFn<MentionTypeaheadOption<TData>>; + commandPriority: CommandListenerPriority; +}) { + const [editor] = useLexicalComposerContext(); + + const options: MentionTypeaheadOption<TData>[] = useMemo( + () => + suggestions.map( + (suggestion) => + new MentionTypeaheadOption<TData>({ + data: suggestion, + dataId: getSuggestionId(suggestion), + }), + ), + [suggestions], + ); + + const onSelectOption = useCallback( + ( + selectedOption: MentionTypeaheadOption<TData>, + nodeToReplace: TextNode | null, + closeMenu: () => void, + ) => { + editor.update(() => { + const node = $createNode(selectedOption.data, selectedOption.key); + const textNode = $createTextNode(' '); + if (nodeToReplace) { + $insertNodes([textNode]); + textNode.insertBefore(nodeToReplace); + nodeToReplace.replace(node); + } + textNode.select(); + closeMenu(); + }); + }, + [editor], + ); + + const checkForMentionMatch = useCallback( + (text: string) => { + return getPossibleQueryMatch(text); + }, + [editor], + ); + + return ( + <LexicalTypeaheadMenuPlugin + onQueryChange={onQueryChange} + onSelectOption={onSelectOption} + triggerFn={checkForMentionMatch} + options={options} + menuRenderFn={menuRenderFn} + commandPriority={commandPriority} + /> + ); +} diff --git a/src/v4/social/internal-components/Lexical/utils.ts b/src/v4/social/internal-components/Lexical/utils.ts new file mode 100644 index 000000000..257b9bf4e --- /dev/null +++ b/src/v4/social/internal-components/Lexical/utils.ts @@ -0,0 +1,252 @@ +import { SerializedAutoLinkNode } from '@lexical/link'; +import { InitialConfigType, InitialEditorStateType } from '@lexical/react/LexicalComposer'; +import { + EditorThemeClasses, + Klass, + LexicalEditor, + LexicalNode, + SerializedLexicalNode, + SerializedParagraphNode, + SerializedRootNode, + SerializedTextNode, +} from 'lexical'; +import { Mentioned, Mentionees } from '~/v4/helpers/utils'; +import { SerializedMentionNode } from './nodes/MentionNode'; + +export interface EditorStateJson extends SerializedLexicalNode { + children: []; +} + +export function $isSerializedTextNode(node: SerializedLexicalNode): node is SerializedTextNode { + return node.type === 'text'; +} + +export function $isSerializedMentionNode<T>( + node: SerializedLexicalNode, +): node is SerializedMentionNode<T> { + return node.type === 'mention'; +} + +export function $isSerializedAutoLinkNode( + node: SerializedLexicalNode, +): node is SerializedAutoLinkNode { + return node.type === 'autolink'; +} + +export type MentionData = { + userId: string; + displayName?: string; +}; + +function createRootNode(): SerializedRootNode<SerializedParagraphNode> { + return { + children: [], + direction: 'ltr', + format: '', + indent: 0, + type: 'root', + version: 1, + }; +} + +function createParagraphNode(): SerializedParagraphNode { + return { + children: [], + direction: 'ltr', + format: '', + indent: 0, + type: 'paragraph', + version: 1, + textFormat: 0, + }; +} + +function createSerializeTextNode(text: string): SerializedTextNode { + return { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text, + type: 'text', + version: 1, + }; +} + +function createSerializeMentionNode(mention: Mentioned): SerializedMentionNode<MentionData> { + return { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: ('@' + mention.userId) as string, + type: 'mention', + version: 1, + data: { + displayName: mention.userId as string, + userId: mention.userId as string, + }, + }; +} + +export function textToEditorState(value: { + data: { text: string }; + metadata?: { + mentioned?: Mentioned[]; + }; + mentionees?: Mentionees; +}) { + const rootNode = createRootNode(); + + const textArray = value.data.text.split('\n'); + + const mentions = value.metadata?.mentioned; + + let start = 0; + let stop = -1; + let mentionRunningIndex = 0; + + for (let i = 0; i < textArray.length; i++) { + start = stop + 1; + stop = start + textArray[i].length; + + const paragraph = createParagraphNode(); + + if (Array.isArray(mentions) && mentions?.length > 0) { + let runningIndex = 0; + + while (runningIndex < textArray[i].length) { + if (mentionRunningIndex >= mentions.length) { + paragraph.children.push(createSerializeTextNode(textArray[i].slice(runningIndex))); + runningIndex = textArray[i].length; + break; + } + + if (mentions[mentionRunningIndex].index >= stop) { + paragraph.children.push(createSerializeTextNode(textArray[i])); + runningIndex = textArray[i].length; + } else { + const text = textArray[i].slice( + runningIndex, + runningIndex + mentions[mentionRunningIndex]?.index - start, + ); + + if (text) { + paragraph.children.push(createSerializeTextNode(text)); + } + + paragraph.children.push(createSerializeMentionNode(mentions[mentionRunningIndex])); + + runningIndex += + mentions[mentionRunningIndex].index + mentions[mentionRunningIndex].length - start; + + mentionRunningIndex++; + } + } + } + + if (!mentions || mentions?.length === 0) { + const textNode = createSerializeTextNode(textArray[i]); + paragraph.children.push(textNode); + } + + rootNode.children.push(paragraph); + } + + return { root: rootNode }; +} + +export function editorStateToText(editor: LexicalEditor) { + const editorStateTextString: string[] = []; + const paragraphs = editor.getEditorState().toJSON().root.children as EditorStateJson[]; + + const mentioned: Mentioned[] = []; + let isChannelMentioned = false; + const mentioneeUserIds: string[] = []; + let runningIndex = 0; + + paragraphs.forEach((paragraph) => { + const children = paragraph.children; + const paragraphText: string[] = []; + + children.forEach( + (child: SerializedTextNode | SerializedMentionNode<MentionData> | SerializedAutoLinkNode) => { + if ($isSerializedTextNode(child)) { + paragraphText.push(child.text); + runningIndex += child.text.length; + } + if ($isSerializedAutoLinkNode(child)) { + child.children.forEach((c) => { + if (!$isSerializedTextNode(c)) return; + paragraphText.push(c.text); + runningIndex += c.text.length; + }); + } + + if ($isSerializedMentionNode<MentionData>(child)) { + if (child.data.userId === 'all') { + mentioned.push({ + index: runningIndex, + length: child.text.length, + type: 'channel', + }); + isChannelMentioned = true; + } else { + mentioned.push({ + index: runningIndex, + length: child.text.length, + type: 'user', + userId: child.data.userId, + }); + mentioneeUserIds.push(child.data.userId); + } + paragraphText.push(child.text); + runningIndex += child.text.length; + } + }, + ); + runningIndex += 1; + editorStateTextString.push(paragraphText.join('')); + }); + + const mentionees: Array<Amity.UserMention | Amity.ChannelMention> = []; + + if (mentioneeUserIds.length > 0) { + mentionees.push({ + type: 'user', + userIds: mentioneeUserIds, + }); + } + + if (isChannelMentioned) { + mentionees.push({ type: 'channel' }); + } + + return { mentioned, text: editorStateTextString.join('\n'), mentionees }; +} + +const defaultTheme = { + ltr: 'ltr', + rtl: 'rtl', +}; + +export const getEditorConfig = ({ + namespace, + theme, + nodes, + editorState, +}: { + namespace: string; + theme: EditorThemeClasses; + nodes: Array<Klass<LexicalNode>>; + editorState?: InitialEditorStateType; +}): InitialConfigType => ({ + namespace, + editable: true, + theme: { ...defaultTheme, ...theme }, + onError(error: Error) { + throw error; + }, + nodes, + editorState, +}); diff --git a/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx b/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx index 160438a47..db00f7be5 100644 --- a/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx +++ b/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx @@ -11,7 +11,7 @@ import ReactDOM from 'react-dom'; import { $createMentionNode } from './MentionNodes'; import { CommunityMember } from '../CommunityMember'; import { useMemberQueryByDisplayName } from '~/v4/social/hooks/useMemberQueryByDisplayName'; -import useCommunity from '~/v4/chat/hooks/useCommunity'; +import { useCommunity } from '~/v4/chat/hooks/useCommunity'; import { useUserQueryByDisplayName } from '~/v4/core/hooks/collections/useUsersCollection'; import useIntersectionObserver from '~/v4/core/hooks/useIntersectionObserver'; diff --git a/src/v4/social/internal-components/PostAd/PostAd.tsx b/src/v4/social/internal-components/PostAd/PostAd.tsx index 49289befa..85380fe39 100644 --- a/src/v4/social/internal-components/PostAd/PostAd.tsx +++ b/src/v4/social/internal-components/PostAd/PostAd.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { useAmityComponent } from '~/v4/core/hooks/uikit/index'; -import useImage from '~/v4/core/hooks/useImage'; import { UIPostAd } from './UIPostAd'; +import { useImage } from '~/v4/core/hooks/useImage'; interface PostAdProps { pageId?: string; diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index 2e23dfa8f..e2403ad0e 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -14,7 +14,7 @@ import { BottomSheet } from '~/v4/core/components/BottomSheet'; import { Typography } from '~/v4/core/components'; import useCommunityMembersCollection from '~/v4/social/hooks/collections/useCommunityMembersCollection'; import useSDK from '~/v4/core/hooks/useSDK'; -import useUser from '~/v4/core/hooks/objects/useUser'; +import { useUser } from '~/v4/core/hooks/objects/useUser'; import { LIKE_REACTION_KEY } from '~/v4/social/constants/reactions'; import { checkStoryPermission, formatTimeAgo, isAdmin, isModerator } from '~/v4/social/utils'; import { Button } from '~/v4/core/natives/Button'; diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx index 3e173b1b5..36ed36161 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx @@ -16,7 +16,7 @@ import Header from '~/v4/social/internal-components/StoryViewer/Renderers/Wrappe import Footer from '~/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer'; import useCommunityMembersCollection from '~/v4/social/hooks/collections/useCommunityMembersCollection'; import useSDK from '~/v4/core/hooks/useSDK'; -import useUser from '~/v4/core/hooks/objects/useUser'; +import { useUser } from '~/v4/core/hooks/objects/useUser'; import clsx from 'clsx'; import { LIKE_REACTION_KEY } from '~/v4/social/constants/reactions'; import { checkStoryPermission, formatTimeAgo, isAdmin, isModerator } from '~/v4/social/utils'; diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/storyAd.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/storyAd.tsx index 63caee44f..ea781dd1d 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/storyAd.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/storyAd.tsx @@ -2,8 +2,8 @@ import { Tester, CustomRenderer } from './types'; import React, { useState, useEffect, useCallback } from 'react'; -import useImage from '~/v4/core/hooks/useImage'; import { UIStoryAd } from '../../StoryAd/UIStoryAd'; +import { useImage } from '~/v4/core/hooks/useImage'; export const renderer: CustomRenderer = ({ story, action, config, onClose }) => { const [isPaused, setIsPaused] = useState(false); diff --git a/src/v4/social/internal-components/UserAvatar/UserAvatar.tsx b/src/v4/social/internal-components/UserAvatar/UserAvatar.tsx index bd94f799d..a487f819e 100644 --- a/src/v4/social/internal-components/UserAvatar/UserAvatar.tsx +++ b/src/v4/social/internal-components/UserAvatar/UserAvatar.tsx @@ -1,7 +1,7 @@ import clsx from 'clsx'; import React from 'react'; -import useUser from '~/v4/core/hooks/objects/useUser'; -import useImage from '~/v4/core/hooks/useImage'; +import { useUser } from '~/v4/core/hooks/objects/useUser'; +import { useImage } from '~/v4/core/hooks/useImage'; import styles from './UserAvatar.module.css'; const UserSvg = ({ className, ...props }: React.SVGProps<SVGSVGElement>) => ( diff --git a/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.tsx b/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.tsx index 17ecd0fae..a4b9ecb15 100644 --- a/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.tsx +++ b/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.tsx @@ -12,7 +12,7 @@ import { CommunityPrivateBadge } from '~/v4/social/elements/CommunityPrivateBadg import { CommunityDisplayName } from '~/v4/social/elements/CommunityDisplayName'; import { CommunityAvatar } from '~/v4/social/elements/CommunityAvatar'; import useIntersectionObserver from '~/v4/core/hooks/useIntersectionObserver'; -import useUser from '~/v4/core/hooks/objects/useUser'; +import { useUser } from '~/v4/core/hooks/objects/useUser'; import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; import useSDK from '~/v4/core/hooks/useSDK'; import { Mode } from '../PostComposerPage/PostComposerPage'; From 000742debc038b47fae9a65bf86c35dfb814fa8c Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Fri, 12 Jul 2024 16:37:41 +0700 Subject: [PATCH 240/300] fix: ASC-24263 - stories bug (#533) * fix: i18n error * fix: global feed story index * fix: CommunityFeedStory --- .../StoryViewer/Renderers/Image.tsx | 4 +- .../pages/StoryPage/CommunityFeedStory.tsx | 105 +++++++++++------- .../pages/StoryPage/GlobalFeedStory.tsx | 6 +- 3 files changed, 69 insertions(+), 46 deletions(-) diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index e2403ad0e..b583af669 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -277,9 +277,7 @@ export const renderer: CustomRenderer = ({ onPress={() => bottomSheetAction?.action()} > {bottomSheetAction?.icon && bottomSheetAction.icon} - <Typography.BodyBold> - {formatMessage({ id: bottomSheetAction.name })} - </Typography.BodyBold> + <Typography.BodyBold>{bottomSheetAction.name}</Typography.BodyBold> </Button> ))} </BottomSheet> diff --git a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx index c13558182..aa50d033e 100644 --- a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx @@ -51,6 +51,9 @@ interface CommunityFeedStoryProps { const DURATION = 5000; +const isStory = (story: Amity.Story | Amity.Ad): story is Amity.Story => + !!(story as Amity.Story)?.storyId; + export const CommunityFeedStory = ({ pageId = '*', communityId, @@ -103,8 +106,14 @@ export const CommunityFeedStory = ({ const [currentIndex, setCurrentIndex] = useState(0); const { file, setFile } = useStoryContext(); - const isStoryCreator = stories[currentIndex]?.creator?.userId === currentUserId; - const isModerator = checkStoryPermission(client, communityId); + const currentStory = stories[currentIndex]; + + const isStoryCreator = isStory(currentStory) + ? currentStory?.creator?.userId === currentUserId + : false; + const isModerator = isStory(currentStory) + ? checkStoryPermission(client, currentStory?.targetId) + : false; const nextStory = () => { if (currentIndex === stories.length - 1) { @@ -213,59 +222,73 @@ export const CommunityFeedStory = ({ }; const formattedStories = stories?.map((story) => { - const isImage = story?.dataType === 'image'; - const url = isImage ? story?.imageData?.fileUrl : story?.videoData?.videoUrl?.['720p']; - - return { - ...story, - url, - type: isImage ? 'image' : 'video', - actions: [ - isStoryCreator || isModerator - ? { - name: 'Delete', - action: () => deleteStory(story?.storyId as string), - icon: ( - <Trash2Icon - fill={getComputedStyle(document.documentElement).getPropertyValue( - '--asc-color-base-default', - )} - /> - ), - } - : null, - ].filter(isNonNullable), - onCreateStory, - discardStory, - addStoryButton, - fileInputRef, - currentIndex, - storiesCount: stories?.length, - increaseIndex, - pageId, - dragEventTarget: dragEventTarget.current, - }; + if (isStory(story)) { + const isImage = story?.dataType === 'image'; + const url = isImage ? story?.imageData?.fileUrl : story?.videoData?.videoUrl?.['720p']; + + return { + story, + url, + type: isImage ? 'image' : 'video', + actions: [ + isStoryCreator || isModerator + ? { + name: 'Delete', + action: () => deleteStory(story?.storyId as string), + icon: ( + <Trash2Icon + fill={getComputedStyle(document.documentElement).getPropertyValue( + '--asc-color-base-default', + )} + /> + ), + } + : null, + ].filter(isNonNullable), + onCreateStory, + discardStory, + addStoryButton, + fileInputRef, + currentIndex, + storiesCount: stories?.length, + increaseIndex, + pageId, + dragEventTarget: dragEventTarget.current, + }; + } else { + return { + ad: story, + actions: [], + pageId, + currentIndex, + storiesCount: stories?.length, + increaseIndex, + }; + } }); const targetRootId = 'asc-uikit-stories-viewer'; useEffect(() => { - if (stories[stories.length - 1]?.syncState === 'syncing') { + const lastStory = stories[stories.length - 1]; + if (isStory(lastStory) && lastStory?.syncState === 'syncing') { setCurrentIndex(stories.length - 1); } - if (stories[currentIndex]) { - stories[currentIndex]?.analytics.markAsSeen(); + if (currentStory && isStory(currentStory)) { + currentStory?.analytics.markAsSeen(); } }, [currentIndex, stories]); useEffect(() => { - if (stories.every((story) => story?.isSeen)) return; - const firstUnseenStoryIndex = stories.findIndex((story) => !story?.isSeen); + if (stories.filter(isStory).every((story) => story?.isSeen)) return; + const firstUnseenStoryIndex = stories.findIndex((story) => + isStory(story) ? !story?.isSeen : false, + ); if (firstUnseenStoryIndex !== -1) { setCurrentIndex(firstUnseenStoryIndex); } - }, [stories]); + }, []); useCommunityStoriesSubscription({ targetId: communityId, @@ -330,7 +353,7 @@ export const CommunityFeedStory = ({ stories={formattedStories} renderers={communityFeedRenderers as RendererObject[]} defaultInterval={DURATION} - onStoryStart={() => stories[currentIndex]?.analytics.markAsSeen()} + onStoryStart={() => isStory(currentStory) && currentStory?.analytics.markAsSeen()} onStoryEnd={nextStory} onNext={nextStory} onPrevious={previousStory} diff --git a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx index 4fb0def54..78ff67277 100644 --- a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx @@ -296,12 +296,14 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ useEffect(() => { if (stories.filter(isStory).every((story) => story?.isSeen)) return; - const firstUnseenStoryIndex = stories.filter(isStory).findIndex((story) => !story?.isSeen); + const firstUnseenStoryIndex = stories.findIndex((story) => + isStory(story) ? !story?.isSeen : false, + ); if (firstUnseenStoryIndex !== -1) { setCurrentIndex(firstUnseenStoryIndex); } - }, [stories]); + }, []); useEffect(() => { if (!file) return; From 49fdd43aba8a73b414e4d1e32bc0f960ba7e5da6 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Fri, 12 Jul 2024 16:37:55 +0700 Subject: [PATCH 241/300] fix: image ratio to 1 (#532) --- .../components/PostContent/ImageContent/ImageContent.module.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/v4/social/components/PostContent/ImageContent/ImageContent.module.css b/src/v4/social/components/PostContent/ImageContent/ImageContent.module.css index 107d4bb9a..fa430909e 100644 --- a/src/v4/social/components/PostContent/ImageContent/ImageContent.module.css +++ b/src/v4/social/components/PostContent/ImageContent/ImageContent.module.css @@ -7,6 +7,7 @@ } .imageContent[data-images-amount='1'] { + aspect-ratio: 1; grid-template: 'image1' / minmax(0, 1fr); } From aee2164ae4300e87d5ec40a7233005287a76c86e Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Fri, 12 Jul 2024 16:41:45 +0700 Subject: [PATCH 242/300] fix: ASC-00000 - minor bugs (#531) * fix: change unit to dvw and dvh * fix: change p to span because an error in a browser * fix: globalfeed key * fix: stop drag default and remove unnecessary preventDefault * fix: cannot click on mobille * fix: prevent drag event * fix: UIPostAd * chore: add TODO * fix: style on mobile * fix: Linkify --- .storybook/decorators/FluidControl.tsx | 5 +-- .../core/components/Typography/Typography.tsx | 16 +++---- src/v4/core/natives/Button/index.ts | 4 ++ .../components/GlobalFeed/GlobalFeed.tsx | 15 ++++++- .../social/components/Newsfeed/Newsfeed.tsx | 6 +-- .../PostContent/ImageContent/ImageContent.tsx | 7 +-- .../PostContent/VideoContent/VideoContent.tsx | 7 +-- .../elements/ExploreButton/ExploreButton.tsx | 2 +- .../social/elements/MenuButton/MenuButton.tsx | 5 ++- .../MyCommunitiesButton.tsx | 2 +- .../NewsfeedButton/NewsfeedButton.tsx | 2 +- .../AdInformation/AdInformation.tsx | 13 +++++- .../internal-components/Linkify/Linkify.tsx | 22 +++++----- .../PostAd/UIPostAd.module.css | 44 ++++++++++++++++--- .../internal-components/PostAd/UIPostAd.tsx | 12 +++-- .../PostMenu/PostMenu.module.css | 6 +++ .../internal-components/PostMenu/PostMenu.tsx | 14 +++--- .../TabButton/TabButton.tsx | 7 +-- .../TabsBar/TabsBar.module.css | 1 + 19 files changed, 129 insertions(+), 61 deletions(-) diff --git a/.storybook/decorators/FluidControl.tsx b/.storybook/decorators/FluidControl.tsx index 0dcdcf87c..263b0a973 100644 --- a/.storybook/decorators/FluidControl.tsx +++ b/.storybook/decorators/FluidControl.tsx @@ -23,9 +23,8 @@ const FullScreen = (props) => ( position: 'absolute', left: 0, top: 0, - width: '100vw', - height: '100vh', - overflow: 'auto', + width: '100dvw', + height: '100dvh', }} {...props} /> diff --git a/src/v4/core/components/Typography/Typography.tsx b/src/v4/core/components/Typography/Typography.tsx index 4c0d08c68..60e8e18f0 100644 --- a/src/v4/core/components/Typography/Typography.tsx +++ b/src/v4/core/components/Typography/Typography.tsx @@ -62,49 +62,49 @@ Typography.Subtitle = ({ children, className = '', style, ...rest }) => { Typography.Body = ({ children, className = '', style, ...rest }) => { return ( - <p + <span className={clsx(typography['typography'], typography['typography-body'], className)} style={style} {...rest} > {children} - </p> + </span> ); }; Typography.BodyBold = ({ children, className = '', style, ...rest }) => { return ( - <p + <span className={clsx(typography['typography'], typography['typography-body-bold'], className)} style={style} {...rest} > {children} - </p> + </span> ); }; Typography.Caption = ({ children, className = '', style, ...rest }) => { return ( - <p + <span className={clsx(typography['typography'], typography['typography-caption'], className)} style={style} {...rest} > {children} - </p> + </span> ); }; Typography.CaptionBold = ({ children, className = '', style, ...rest }) => { return ( - <p + <span className={clsx(typography['typography'], typography['typography-caption-bold'], className)} style={style} {...rest} > {children} - </p> + </span> ); }; diff --git a/src/v4/core/natives/Button/index.ts b/src/v4/core/natives/Button/index.ts index fe9c53c51..0672e4f64 100644 --- a/src/v4/core/natives/Button/index.ts +++ b/src/v4/core/natives/Button/index.ts @@ -1 +1,5 @@ export { Button } from './Button'; + +import type { ButtonProps } from './Button'; + +export type { ButtonProps }; diff --git a/src/v4/social/components/GlobalFeed/GlobalFeed.tsx b/src/v4/social/components/GlobalFeed/GlobalFeed.tsx index 8fc7c30a3..f3518f6fb 100644 --- a/src/v4/social/components/GlobalFeed/GlobalFeed.tsx +++ b/src/v4/social/components/GlobalFeed/GlobalFeed.tsx @@ -49,13 +49,24 @@ export const GlobalFeed = ({ return <EmptyNewsfeed pageId={pageId} />; } + const getItemKey = (item: Amity.Post | Amity.Ad, prevItem: Amity.Post | Amity.Ad | undefined) => { + if (isAmityAd(item)) { + if (prevItem && isAmityAd(prevItem)) { + return `${prevItem.adId}-${item.adId}`; + } else { + return `${prevItem.postId}-${item.adId}`; + } + } + return item.postId; + }; + return ( <div className={styles.global_feed} style={themeStyles} data-qa-anchor={accessibilityId}> {items.map((item, index) => ( - <div key={item.postId || item.adId}> + <div key={getItemKey(item, items[Math.max(0, index - 1)])}> {index !== 0 ? <div className={styles.global_feed__divider} /> : null} {isAmityAd(item) ? ( - <PostAd key={item.adId} ad={item} /> + <PostAd ad={item} /> ) : ( <div className={styles.global_feed__postContainer}> <PostContent diff --git a/src/v4/social/components/Newsfeed/Newsfeed.tsx b/src/v4/social/components/Newsfeed/Newsfeed.tsx index 6af5f7a47..eb179a4d0 100644 --- a/src/v4/social/components/Newsfeed/Newsfeed.tsx +++ b/src/v4/social/components/Newsfeed/Newsfeed.tsx @@ -74,6 +74,7 @@ export const Newsfeed = ({ pageId = '*' }: NewsfeedProps) => { <div className={styles.newsfeed} style={themeStyles} + onDrag={(event) => event.stopPropagation()} onTouchStart={(ev) => { touchStartY.current = ev.touches[0].clientY; }} @@ -85,11 +86,8 @@ export const Newsfeed = ({ pageId = '*' }: NewsfeedProps) => { } setTouchDiff(Math.min(touchY - touchStartY.current, 100)); - if (touchDiff > 0 && window.scrollY === 0) { - ev.preventDefault(); - } }} - onTouchEnd={() => { + onTouchEnd={(ev) => { touchStartY.current = 0; if (touchDiff >= 75) { refetch(); diff --git a/src/v4/social/components/PostContent/ImageContent/ImageContent.tsx b/src/v4/social/components/PostContent/ImageContent/ImageContent.tsx index c3dea18d7..50aeece54 100644 --- a/src/v4/social/components/PostContent/ImageContent/ImageContent.tsx +++ b/src/v4/social/components/PostContent/ImageContent/ImageContent.tsx @@ -5,6 +5,7 @@ import { Typography } from '~/v4/core/components'; import { useAmityElement } from '~/v4/core/hooks/uikit/index'; import styles from './ImageContent.module.css'; import usePost from '~/v4/core/hooks/objects/usePost'; +import { Button } from '~/v4/core/natives/Button'; interface ImageContentProps { pageId?: string; @@ -42,10 +43,10 @@ const Image = ({ } return ( - <div + <Button key={imagePost.postId} className={styles.imageContent__imgContainer} - onClick={() => onImageClick()} + onPress={() => onImageClick()} > <ImageThumbnail fileId={imagePost.data.fileId} /> {imageLeftCount > 0 && index === postAmount - 1 && ( @@ -53,7 +54,7 @@ const Image = ({ + {imageLeftCount} </Typography.Heading> )} - </div> + </Button> ); }; diff --git a/src/v4/social/components/PostContent/VideoContent/VideoContent.tsx b/src/v4/social/components/PostContent/VideoContent/VideoContent.tsx index 668a9bf40..5cb60fa5b 100644 --- a/src/v4/social/components/PostContent/VideoContent/VideoContent.tsx +++ b/src/v4/social/components/PostContent/VideoContent/VideoContent.tsx @@ -4,6 +4,7 @@ import { Typography } from '~/v4/core/components'; import { useAmityElement } from '~/v4/core/hooks/uikit'; import styles from './VideoContent.module.css'; import usePost from '~/v4/core/hooks/objects/usePost'; +import { Button } from '~/v4/core/natives/Button'; const PlayButtonSvg = (props: React.SVGProps<SVGSVGElement>) => ( <svg @@ -53,10 +54,10 @@ const Video = ({ } return ( - <div + <Button className={styles.videoContent__videoContainer} data-videos-amount={Math.min(postAmount, 4)} - onClick={() => onVideoClick()} + onPress={() => onVideoClick()} > <VideoThumbnail fileId={videoPost.data.thumbnailFileId} /> {videoLeftCount > 0 && index === postAmount - 1 && ( @@ -71,7 +72,7 @@ const Video = ({ </div> </div> ) : null} - </div> + </Button> ); }; diff --git a/src/v4/social/elements/ExploreButton/ExploreButton.tsx b/src/v4/social/elements/ExploreButton/ExploreButton.tsx index 913c44c8e..32b2a98d0 100644 --- a/src/v4/social/elements/ExploreButton/ExploreButton.tsx +++ b/src/v4/social/elements/ExploreButton/ExploreButton.tsx @@ -31,7 +31,7 @@ export function ExploreButton({ componentId={componentId} elementId={elementId} isActive={isActive} - onClick={() => onClick?.()} + onPress={() => onClick?.()} data-qa-anchor={accessibilityId} > {config.text} diff --git a/src/v4/social/elements/MenuButton/MenuButton.tsx b/src/v4/social/elements/MenuButton/MenuButton.tsx index 605a08409..e2e8aa71b 100644 --- a/src/v4/social/elements/MenuButton/MenuButton.tsx +++ b/src/v4/social/elements/MenuButton/MenuButton.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { useAmityElement } from '~/v4/core/hooks/uikit'; import { IconComponent } from '~/v4/core/IconComponent'; +import { Button } from '~/v4/core/natives/Button'; import styles from './MenuButton.module.css'; const EllipsisH = ({ ...props }: React.SVGProps<SVGSVGElement>) => ( @@ -34,13 +35,13 @@ export function MenuButton({ pageId = '*', componentId = '*', onClick }: MenuBut if (isExcluded) return null; return ( - <div onClick={onClick} data-qa-anchor={accessibilityId}> + <Button onPress={onClick} data-qa-anchor={accessibilityId}> <IconComponent defaultIcon={() => <EllipsisH className={styles.menuButton} style={themeStyles} />} imgIcon={() => <img src={config.icon} alt={uiReference} />} defaultIconName={defaultConfig.icon} configIconName={config.icon} /> - </div> + </Button> ); } diff --git a/src/v4/social/elements/MyCommunitiesButton/MyCommunitiesButton.tsx b/src/v4/social/elements/MyCommunitiesButton/MyCommunitiesButton.tsx index 80fd99320..a1c174078 100644 --- a/src/v4/social/elements/MyCommunitiesButton/MyCommunitiesButton.tsx +++ b/src/v4/social/elements/MyCommunitiesButton/MyCommunitiesButton.tsx @@ -33,7 +33,7 @@ export function MyCommunitiesButton({ componentId={componentId} elementId={elementId} isActive={isActive} - onClick={() => onClick?.()} + onPress={() => onClick?.()} > {config.text} </TabButton> diff --git a/src/v4/social/elements/NewsfeedButton/NewsfeedButton.tsx b/src/v4/social/elements/NewsfeedButton/NewsfeedButton.tsx index 9a017559f..b18aef0e5 100644 --- a/src/v4/social/elements/NewsfeedButton/NewsfeedButton.tsx +++ b/src/v4/social/elements/NewsfeedButton/NewsfeedButton.tsx @@ -33,7 +33,7 @@ export function NewsfeedButton({ componentId={componentId} elementId={elementId} isActive={isActive} - onClick={() => onClick?.()} + onPress={() => onClick?.()} > {config.text} </TabButton> diff --git a/src/v4/social/internal-components/AdInformation/AdInformation.tsx b/src/v4/social/internal-components/AdInformation/AdInformation.tsx index af0799be7..1d1e2552b 100644 --- a/src/v4/social/internal-components/AdInformation/AdInformation.tsx +++ b/src/v4/social/internal-components/AdInformation/AdInformation.tsx @@ -13,10 +13,19 @@ interface AdInformationProps { export const AdInformation = ({ isOpen, onOpenChange, ad }: AdInformationProps) => { return ( - <Drawer.Root open={isOpen} onOpenChange={onOpenChange}> + <Drawer.Root + open={isOpen} + onOpenChange={onOpenChange} + onDrag={(event) => event.stopPropagation()} + > <Drawer.Portal> <Drawer.Overlay className={styles.drawer__overlay} /> - <Drawer.Content className={styles.drawer__content}> + <Drawer.Content + className={styles.drawer__content} + onTouchStart={(event) => event.stopPropagation()} + onTouchMove={(event) => event.stopPropagation()} + onTouchEnd={(event) => event.stopPropagation()} + > <div className={styles.drawer__innerContent}> <div className={styles.drawer__placeholder} /> <Drawer.Title className={styles.drawer__title}> diff --git a/src/v4/social/internal-components/Linkify/Linkify.tsx b/src/v4/social/internal-components/Linkify/Linkify.tsx index 6b0e17dc2..45b304668 100644 --- a/src/v4/social/internal-components/Linkify/Linkify.tsx +++ b/src/v4/social/internal-components/Linkify/Linkify.tsx @@ -7,17 +7,15 @@ type UiKitLinkifyProps = Omit<React.ComponentProps<typeof Linkify>, 'componentDe export const UiKitLinkify = (props: UiKitLinkifyProps) => ( <Linkify - componentDecorator={(decoratedHref?: string, decoratedText?: string, key?: string) => ( - <a - className={styles.link} - key={key} - target="blank" - rel="noopener noreferrer" - href={decoratedHref} - > - {decoratedText} - </a> - )} - {...props} + options={{ + render: ({ attributes, content }) => { + const { href, ...props } = attributes; + return ( + <a className={styles.link} target="blank" rel="noopener noreferrer" href={href}> + {content} + </a> + ); + }, + }} /> ); diff --git a/src/v4/social/internal-components/PostAd/UIPostAd.module.css b/src/v4/social/internal-components/PostAd/UIPostAd.module.css index 3d643ae11..82a8dc63e 100644 --- a/src/v4/social/internal-components/PostAd/UIPostAd.module.css +++ b/src/v4/social/internal-components/PostAd/UIPostAd.module.css @@ -14,23 +14,31 @@ align-items: center; gap: var(--asc-spacing-s2); padding: var(--asc-spacing-s1) 0; + max-width: 100%; } .header__title { color: var(--asc-color-base-default); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .header__avatar { - width: 3rem; - height: 3rem; + width: 2rem; + height: 2rem; + flex-shrink: 0; } .header__detail { display: flex; + flex-flow: wrap; flex-direction: column; justify-content: center; align-items: flex-start; gap: var(--asc-spacing-xxs1); + flex: 1 1 auto; + overflow: hidden; } .content__text { @@ -53,11 +61,14 @@ .footer { background-color: var(--asc-color-background-shade1); padding: var(--asc-spacing-m1) var(--asc-spacing-s2); - display: flex; - flex-direction: row; - justify-content: space-between; + display: grid; + grid-template: + 'left right' auto + / minmax(0, 3fr) minmax(0, 1fr); align-items: center; + justify-content: start; margin-top: var(--asc-spacing-s1); + width: 100%; } .footer[data-has-url='true'] { @@ -72,11 +83,34 @@ color: var(--asc-color-base-default); } +.footer__left { + grid-area: left; + word-wrap: break-word; +} + +.footer__right { + grid-area: right; + display: flex; + justify-content: center; + align-items: center; + place-self: center; + max-width: 100%; +} + .footer__content__button { background-color: var(--asc-color-primary-default); color: var(--asc-color-white); padding: 0.375rem 0.75rem; border-radius: var(--asc-border-radius-md); + max-width: 100%; + overflow: hidden; +} + +.footer__content__button__text { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 100%; } .infoIcon__button { diff --git a/src/v4/social/internal-components/PostAd/UIPostAd.tsx b/src/v4/social/internal-components/PostAd/UIPostAd.tsx index 14ad06142..0df9765c7 100644 --- a/src/v4/social/internal-components/PostAd/UIPostAd.tsx +++ b/src/v4/social/internal-components/PostAd/UIPostAd.tsx @@ -59,7 +59,7 @@ export const UIPostAd = ({ data-has-url={!!ad.callToActionUrl} onClick={handleCallToActionClick} > - <div> + <div className={styles.footer__left}> <Typography.Body className={styles.footer__content__description}> {ad.description} </Typography.Body> @@ -68,9 +68,13 @@ export const UIPostAd = ({ </Typography.BodyBold> </div> {ad.callToActionUrl ? ( - <Button className={styles.footer__content__button} onPress={handleCallToActionClick}> - <Typography.CaptionBold>{ad.callToAction}</Typography.CaptionBold> - </Button> + <div className={styles.footer__right}> + <Button className={styles.footer__content__button} onPress={handleCallToActionClick}> + <Typography.CaptionBold className={styles.footer__content__button__text}> + {ad.callToAction} + </Typography.CaptionBold> + </Button> + </div> ) : null} </div> <AdInformation diff --git a/src/v4/social/internal-components/PostMenu/PostMenu.module.css b/src/v4/social/internal-components/PostMenu/PostMenu.module.css index 35f307011..f57a05f4c 100644 --- a/src/v4/social/internal-components/PostMenu/PostMenu.module.css +++ b/src/v4/social/internal-components/PostMenu/PostMenu.module.css @@ -22,6 +22,12 @@ border-radius: var(--asc-border-radius-md); } +@media (hover: none) { + .postMenu__item:hover { + background-color: inherit; + } +} + .postMenu__editPost__text { color: var(--asc-color-secondary-default); } diff --git a/src/v4/social/internal-components/PostMenu/PostMenu.tsx b/src/v4/social/internal-components/PostMenu/PostMenu.tsx index 19249b671..ec8f584c5 100644 --- a/src/v4/social/internal-components/PostMenu/PostMenu.tsx +++ b/src/v4/social/internal-components/PostMenu/PostMenu.tsx @@ -4,6 +4,7 @@ import { useMutation } from '@tanstack/react-query'; import { usePostPermissions } from '~/v4/core/hooks/usePostPermissions'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; import { useNotifications } from '~/v4/core/providers/NotificationProvider'; +import { Button } from '~/v4/core/natives/Button'; import styles from './PostMenu.module.css'; import { usePostFlaggedByMe } from '~/v4/core/hooks/usePostFlaggedByMe'; @@ -160,11 +161,10 @@ export const PostMenu = ({ <PenSvg className={styles.postMenu__editPost__icon} /> <span>Edit post</span> </button> */} - {showReportPostButton ? ( - <button + {showReportPostButton && !isLoading ? ( + <Button className={styles.postMenu__item} - disabled={isLoading} - onClick={() => { + onPress={() => { if (isFlaggedByMe) { mutateUnReportPost(); } else { @@ -176,13 +176,13 @@ export const PostMenu = ({ <span className={styles.postMenu__reportPost__text}> {isFlaggedByMe ? 'Unreport post' : 'Report post'} </span> - </button> + </Button> ) : null} {showDeletePostButton ? ( - <button className={styles.postMenu__item} onClick={() => onDeleteClick()}> + <Button className={styles.postMenu__item} onPress={() => onDeleteClick()}> <TrashSvg className={styles.postMenu__deletePost__icon} /> <span className={styles.postMenu__deletePost__text}>Delete post</span> - </button> + </Button> ) : null} </div> ); diff --git a/src/v4/social/internal-components/TabButton/TabButton.tsx b/src/v4/social/internal-components/TabButton/TabButton.tsx index 120d7ae0d..245401e14 100644 --- a/src/v4/social/internal-components/TabButton/TabButton.tsx +++ b/src/v4/social/internal-components/TabButton/TabButton.tsx @@ -2,10 +2,11 @@ import React, { ReactNode } from 'react'; import { Typography } from '~/v4/core/components'; import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { Button, ButtonProps } from '~/v4/core/natives/Button'; import styles from './TabButton.module.css'; -export interface TabButtonProps extends React.HTMLAttributes<HTMLButtonElement> { +export interface TabButtonProps extends ButtonProps { pageId?: string; componentId?: string; elementId?: string; @@ -29,7 +30,7 @@ export function TabButton({ }); return ( - <button + <Button style={themeStyles} className={styles.tabButton} data-active={isActive} @@ -45,6 +46,6 @@ export function TabButton({ {children} </Typography.Caption> )} - </button> + </Button> ); } diff --git a/src/v4/social/internal-components/TabsBar/TabsBar.module.css b/src/v4/social/internal-components/TabsBar/TabsBar.module.css index 495e7c761..76579fd01 100644 --- a/src/v4/social/internal-components/TabsBar/TabsBar.module.css +++ b/src/v4/social/internal-components/TabsBar/TabsBar.module.css @@ -1,3 +1,4 @@ +/* TODO: remove this block */ button, fieldset, input { From 5a9a424361f308110b495058017379f9db925a91 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Fri, 12 Jul 2024 16:48:58 +0700 Subject: [PATCH 243/300] fix: draft video story (#530) --- src/v4/social/pages/DraftsPage/DraftsPage.module.css | 1 + src/v4/social/pages/DraftsPage/DraftsPage.tsx | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.module.css b/src/v4/social/pages/DraftsPage/DraftsPage.module.css index e0f3fb9e5..8be9aed3b 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.module.css +++ b/src/v4/social/pages/DraftsPage/DraftsPage.module.css @@ -93,6 +93,7 @@ width: 100%; height: 100%; background-color: var(--asc-color-black); + object-fit: contain; } .hyperLinkContainer { diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index 8f778b5e2..d51defb69 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -250,13 +250,14 @@ export const PlainDraftStoryPage = ({ /> </div> ) : mediaType?.type === 'video' ? ( - <VideoPreview - mediaFit="contain" + <video className={styles.videoPreview} src={file ? URL.createObjectURL(file) : mediaType.url} autoPlay loop + muted controls={false} + playsInline /> ) : null} {hyperLink[0]?.data?.url && ( From 9e115b2c8dd63913730dc1fd5c2ad2e93ba438fc Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Fri, 12 Jul 2024 18:25:16 +0700 Subject: [PATCH 244/300] fix: ASC-00000 - story (#507) * fix: story uploading state * fix: remove unnessescary useMeme * fix: remove unused * fix: bottom sheet story * fix: remove unused * fix: done button type * fix: type --- .../HyperLinkConfig/HyperLinkConfig.tsx | 2 +- .../social/elements/DoneButton/DoneButton.tsx | 8 +-- .../CommentList/CommentList.tsx | 9 +++ .../StoryViewer/Renderers/Image.tsx | 70 ++++++++----------- .../StoryViewer/Renderers/Video.tsx | 14 ++-- .../StoryViewer/Renderers/types.ts | 2 + .../pages/StoryPage/CommunityFeedStory.tsx | 4 +- .../pages/StoryPage/GlobalFeedStory.tsx | 2 +- 8 files changed, 56 insertions(+), 55 deletions(-) diff --git a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx index c8c50f422..ee6beb090 100644 --- a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx +++ b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx @@ -170,7 +170,7 @@ export const HyperLinkConfig = ({ <Typography.Title> {formatMessage({ id: 'storyCreation.hyperlink.bottomSheet.title' })} </Typography.Title> - <DoneButton pageId={pageId} componentId={componentId} formId={formId} /> + <DoneButton type="submit" pageId={pageId} componentId={componentId} form={formId} /> </div> <div className={styles.divider} /> <div className={styles.hyperlinkFormContainer}> diff --git a/src/v4/social/elements/DoneButton/DoneButton.tsx b/src/v4/social/elements/DoneButton/DoneButton.tsx index 1d1070d5f..fe7b86e98 100644 --- a/src/v4/social/elements/DoneButton/DoneButton.tsx +++ b/src/v4/social/elements/DoneButton/DoneButton.tsx @@ -1,20 +1,20 @@ import React from 'react'; -import { Button, ButtonProps } from '~/v4/core/natives/Button'; +import { Button } from '~/v4/core/natives/Button'; import { Typography } from '~/v4/core/components'; import { useAmityElement } from '~/v4/core/hooks/uikit'; import styles from './DoneButton.module.css'; -export interface DoneButtonProps extends ButtonProps { +export interface DoneButtonProps extends React.ComponentProps<typeof Button> { pageId?: string; componentId?: string; - formId?: string; + className?: string; } export function DoneButton({ pageId = '*', componentId = '*', - formId, className, + ...buttonProps }: DoneButtonProps) { const elementId = 'done_button'; diff --git a/src/v4/social/internal-components/CommentList/CommentList.tsx b/src/v4/social/internal-components/CommentList/CommentList.tsx index 004453d20..7510ef267 100644 --- a/src/v4/social/internal-components/CommentList/CommentList.tsx +++ b/src/v4/social/internal-components/CommentList/CommentList.tsx @@ -6,6 +6,7 @@ import { ExpandIcon, MinusCircleIcon } from '~/v4/social/icons'; import { LoadMoreWrapper } from '~/v4/core/components/LoadMoreWrapper/LoadMoreWrapper'; import useCommentsCollection from '~/v4/social/hooks/collections/useCommentsCollection'; import { CommentBubbleDeleted } from '~/v4/social/elements/CommentBubbleDeleted'; +import { Typography } from '~/v4/core/components/index'; interface CommentListProps { parentId?: string; @@ -80,6 +81,14 @@ export const CommentList = ({ }); }; + if (commentCount === 0) { + return ( + <div className={styles.noCommentsContainer}> + <Typography.Body>No comments yet</Typography.Body> + </div> + ); + } + return ( <LoadMoreWrapper hasMore={hasMore} diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index b583af669..d7f0fb073 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import { useIntl } from 'react-intl'; import Truncate from 'react-truncate-markup'; import { @@ -44,7 +44,6 @@ export const renderer: CustomRenderer = ({ }) => { const { formatMessage } = useIntl(); const [loaded, setLoaded] = useState(false); - const [isOpenBottomSheet, setIsOpenBottomSheet] = useState(false); const [isOpenCommentSheet, setIsOpenCommentSheet] = useState(false); const [isPaused, setIsPaused] = useState(false); const { loader } = config; @@ -66,6 +65,8 @@ export const renderer: CustomRenderer = ({ myReactions, data, items, + isBottomSheetOpen, + setIsBottomSheetOpen, } = story as Amity.Story; const { members } = useCommunityMembersCollection({ @@ -86,45 +87,38 @@ export const renderer: CustomRenderer = ({ const haveStoryPermission = isGlobalAdmin || isModeratorUser || checkStoryPermission(client, community?.communityId); - const heading = useMemo( - () => <div data-qa-anchor="community_display_name">{community?.displayName}</div>, - [community?.displayName], - ); - const subheading = useMemo( - () => - createdAt && creator?.displayName ? ( - <span> - <span data-qa-anchor="created_at">{formatTimeAgo(createdAt as string)}</span> • By{' '} - <span data-qa-anchor="creator_display_name">{creator?.displayName}</span> - </span> - ) : ( - '' - ), - [createdAt, creator?.displayName], - ); + const heading = <div data-qa-anchor="community_display_name">{community?.displayName}</div>; + const subheading = + createdAt && creator?.displayName ? ( + <span> + <span data-qa-anchor="created_at">{formatTimeAgo(createdAt as string)}</span> • By{' '} + <span data-qa-anchor="creator_display_name">{creator?.displayName}</span> + </span> + ) : ( + '' + ); + const targetRootId = 'asc-uikit-stories-viewer'; - const extractColors = useCallback(() => { - if (imageRef.current && imageRef.current.complete) { - const colorThief = new ColorThief(); - const palette = colorThief.getPalette(imageRef.current, 2); - if (palette) { - const gradient = `linear-gradient(to top, rgb(${palette[0].join( - ',', - )}), rgb(${palette[1].join(',')})`; - setBackgroundGradient(gradient); - } + const extractColors = () => { + const colorThief = new ColorThief(); + const palette = colorThief.getPalette(imageRef.current, 2); + if (palette) { + const gradient = `linear-gradient(to top, rgb(${palette[0].join(',')}), rgb(${palette[1].join( + ',', + )})`; + setBackgroundGradient(gradient); } - }, []); + }; - const imageLoaded = useCallback(() => { + const imageLoaded = () => { setLoaded(true); if (isPaused) { setIsPaused(false); } action('play', true); extractColors(); - }, [action, isPaused, extractColors]); + }; const play = () => { action('play', true); @@ -137,11 +131,11 @@ export const renderer: CustomRenderer = ({ const openBottomSheet = () => { action('pause', true); - setIsOpenBottomSheet(true); + setIsBottomSheetOpen(true); }; const closeBottomSheet = () => { action('play', true); - setIsOpenBottomSheet(false); + setIsBottomSheetOpen(false); }; const openCommentSheet = () => { action('pause', true); @@ -160,12 +154,6 @@ export const renderer: CustomRenderer = ({ increaseIndex(); }; - useEffect(() => { - if (imageRef.current && imageRef.current.complete) { - extractColors(); - } - }, []); - useEffect(() => { if (fileInputRef.current) { const handleClick = () => { @@ -222,7 +210,7 @@ export const renderer: CustomRenderer = ({ duration={5000} currentIndex={currentIndex} storiesCount={storiesCount} - isPaused={isPaused || isOpenBottomSheet || isOpenCommentSheet} + isPaused={isPaused || isBottomSheetOpen || isOpenCommentSheet} onComplete={handleProgressComplete} /> <Header @@ -265,7 +253,7 @@ export const renderer: CustomRenderer = ({ <BottomSheet rootId={targetRootId} - isOpen={isOpenBottomSheet} + isOpen={isBottomSheetOpen} onClose={closeBottomSheet} mountPoint={document.getElementById(targetRootId) as HTMLElement} detent="content-height" diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx index 36ed36161..fc39dac32 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx @@ -44,7 +44,6 @@ export const renderer: CustomRenderer = ({ const [loaded, setLoaded] = useState(false); const [muted, setMuted] = useState(false); const [isPaused, setIsPaused] = useState(false); - const [isOpenBottomSheet, setIsOpenBottomSheet] = useState(false); const [isOpenCommentSheet, setIsOpenCommentSheet] = useState(false); const { loader } = config; const { client } = useSDK(); @@ -64,6 +63,8 @@ export const renderer: CustomRenderer = ({ myReactions, data, items, + isBottomSheetOpen, + setIsBottomSheetOpen, } = story as Amity.Story; const { members } = useCommunityMembersCollection({ @@ -131,11 +132,10 @@ export const renderer: CustomRenderer = ({ const openBottomSheet = () => { action('pause', true); - setIsOpenBottomSheet(true); }; const closeBottomSheet = () => { action('play', true); - setIsOpenBottomSheet(false); + setIsBottomSheetOpen(false); }; const openCommentSheet = () => { action('pause', true); @@ -162,7 +162,7 @@ export const renderer: CustomRenderer = ({ useEffect(() => { if (vid.current) { - if (isPaused || isOpenBottomSheet || isOpenCommentSheet) { + if (isPaused || isBottomSheetOpen || isOpenCommentSheet) { vid.current.pause(); action('pause', true); } else { @@ -170,7 +170,7 @@ export const renderer: CustomRenderer = ({ action('play', true); } } - }, [isPaused, isOpenBottomSheet, isOpenCommentSheet]); + }, [isPaused, isBottomSheetOpen, isOpenCommentSheet]); useEffect(() => { if (fileInputRef.current) { @@ -213,7 +213,7 @@ export const renderer: CustomRenderer = ({ duration={5000} currentIndex={currentIndex} storiesCount={storiesCount} - isPaused={isPaused || isOpenBottomSheet || isOpenCommentSheet} + isPaused={isPaused || isBottomSheetOpen || isOpenCommentSheet} onComplete={handleProgressComplete} /> <SpeakerButton @@ -258,7 +258,7 @@ export const renderer: CustomRenderer = ({ <BottomSheet rootId={targetRootId} - isOpen={isOpenBottomSheet} + isOpen={isBottomSheetOpen} onClose={closeBottomSheet} mountPoint={document.getElementById(targetRootId) as HTMLElement} detent="content-height" diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/types.ts b/src/v4/social/internal-components/StoryViewer/Renderers/types.ts index 440449600..34bd94b72 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/types.ts +++ b/src/v4/social/internal-components/StoryViewer/Renderers/types.ts @@ -33,6 +33,8 @@ export type CustomStory = Story & { story?: Amity.Story; ad?: Amity.Ad } & { increaseIndex: () => void; pageId?: string; dragEventTarget?: React.RefObject<HTMLElement>; + isBottomSheetOpen: boolean; + setIsBottomSheetOpen: (isOpen: React.SetStateAction<boolean>) => void; }; export type CustomRendererProps = RendererProps & { diff --git a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx index aa50d033e..1beefcf56 100644 --- a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx @@ -71,6 +71,7 @@ export const CommunityFeedStory = ({ const y = useMotionValue(0); const motionRef = useRef<HTMLDivElement>(null); const dragEventTarget = useRef(new EventTarget()); + const [isBottomSheetOpen, setIsBottomSheetOpen] = useState(false); const { stories } = useGetActiveStoriesByTarget({ targetId: communityId, @@ -136,6 +137,7 @@ export const CommunityFeedStory = ({ "This story will be permanently deleted. You'll no longer to see and find this story.", okText: 'Delete', onOk: async () => { + setIsBottomSheetOpen(false); await StoryRepository.softDeleteStory(storyId); if (currentIndex === 0) { onClose(communityId); @@ -149,7 +151,7 @@ export const CommunityFeedStory = ({ }); }; - const deleteStory = async (storyId: string) => { + const deleteStory = (storyId: string) => { confirmDeleteStory(storyId); }; diff --git a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx index 78ff67277..294dbdfdb 100644 --- a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx @@ -142,7 +142,7 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ onChangePage?.(); } else if (isLastStory) { // If it's the last story, move to the previous one - setCurrentIndex((prevIndex) => prevIndex - 1); + previousStory(); } else { // For any other case (including first story), stay on the same index // The next story will automatically take its place From 4e0eea1ee1766dedf0b6ebbe4bfedba4a497b80d Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Fri, 12 Jul 2024 18:50:23 +0700 Subject: [PATCH 245/300] fix: align with api (#534) --- .../CreatePostMenu/CreatePostMenu.tsx | 16 ++++- .../TopNavigation/TopNavigation.tsx | 27 ++++++-- .../CreatePostButton/CreatePostButton.tsx | 11 ++-- .../CreateStoryButton/CreateStoryButton.tsx | 11 ++-- .../PostCreationButton/PostCreationButton.tsx | 14 ++-- .../pages/SocialHomePage/SocialHomePage.tsx | 64 ++++++------------- 6 files changed, 69 insertions(+), 74 deletions(-) diff --git a/src/v4/social/components/CreatePostMenu/CreatePostMenu.tsx b/src/v4/social/components/CreatePostMenu/CreatePostMenu.tsx index e57b3f687..9abf9961c 100644 --- a/src/v4/social/components/CreatePostMenu/CreatePostMenu.tsx +++ b/src/v4/social/components/CreatePostMenu/CreatePostMenu.tsx @@ -2,6 +2,7 @@ import React from 'react'; import styles from './CreatePostMenu.module.css'; import { CreatePostButton } from '~/v4/social/elements/CreatePostButton'; import { CreateStoryButton } from '~/v4/social/elements/CreateStoryButton'; +import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; interface CreatePostMenuProps { pageId: string; @@ -9,10 +10,21 @@ interface CreatePostMenuProps { export function CreatePostMenu({ pageId }: CreatePostMenuProps) { const componentId = 'create_post_menu'; + + const { AmityCreatePostMenuComponentBehavior } = usePageBehavior(); + return ( <div className={styles.createPostMenu}> - <CreatePostButton pageId={pageId} componentId={componentId} /> - <CreateStoryButton pageId={pageId} componentId={componentId} /> + <CreatePostButton + pageId={pageId} + componentId={componentId} + onClick={() => AmityCreatePostMenuComponentBehavior.goToSelectPostTargetPage()} + /> + <CreateStoryButton + pageId={pageId} + componentId={componentId} + onClick={() => AmityCreatePostMenuComponentBehavior.goToStoryTargetSelectionPage()} + /> </div> ); } diff --git a/src/v4/social/components/TopNavigation/TopNavigation.tsx b/src/v4/social/components/TopNavigation/TopNavigation.tsx index 163178227..2840c9dca 100644 --- a/src/v4/social/components/TopNavigation/TopNavigation.tsx +++ b/src/v4/social/components/TopNavigation/TopNavigation.tsx @@ -4,26 +4,40 @@ import { GlobalSearchButton } from '~/v4/social/elements/GlobalSearchButton'; import { HeaderLabel } from '~/v4/social/elements/HeaderLabel'; import styles from './TopNavigation.module.css'; import { useAmityComponent } from '~/v4/core/hooks/uikit'; +import { useNavigation } from '~/v4/core/providers/NavigationProvider'; +import { HomePageTab } from '../../pages/SocialHomePage/SocialHomePage'; export interface TopNavigationProps { pageId?: string; - onGlobalSearchButtonClick: () => void; - onClickPostCreationButton: (event: React.MouseEvent) => void; - createPostButtonRef: React.RefObject<HTMLDivElement>; + selectedTab?: HomePageTab; + onClickPostCreationButton?: () => void; } export function TopNavigation({ pageId = '*', - onGlobalSearchButtonClick, + selectedTab, onClickPostCreationButton, - createPostButtonRef, }: TopNavigationProps) { const componentId = 'top_navigation'; + const { goToSocialGlobalSearchPage, goToMyCommunitiesSearchPage } = useNavigation(); const { isExcluded, themeStyles } = useAmityComponent({ pageId, componentId, }); + const handleGlobalSearchClick = () => { + switch (selectedTab) { + case HomePageTab.Newsfeed: + goToSocialGlobalSearchPage(); + break; + case HomePageTab.Explore: + break; + case HomePageTab.MyCommunities: + goToMyCommunitiesSearchPage(); + break; + } + }; + if (isExcluded) return null; return ( @@ -35,13 +49,12 @@ export function TopNavigation({ <GlobalSearchButton pageId={pageId} componentId={componentId} - onPress={onGlobalSearchButtonClick} + onPress={handleGlobalSearchClick} /> <PostCreationButton pageId={pageId} componentId={componentId} onClick={onClickPostCreationButton} - createPostButtonRef={createPostButtonRef} /> </div> </div> diff --git a/src/v4/social/elements/CreatePostButton/CreatePostButton.tsx b/src/v4/social/elements/CreatePostButton/CreatePostButton.tsx index ff7ac1aa2..a52149571 100644 --- a/src/v4/social/elements/CreatePostButton/CreatePostButton.tsx +++ b/src/v4/social/elements/CreatePostButton/CreatePostButton.tsx @@ -3,8 +3,8 @@ import { IconComponent } from '~/v4/core/IconComponent'; import { useAmityElement } from '~/v4/core/hooks/uikit'; import { Typography } from '~/v4/core/components'; import styles from './CreatePostButton.module.css'; -import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; import clsx from 'clsx'; +import { Button } from '~/v4/core/natives/Button/Button'; const CreatePostButtonSvg = (props: React.SVGProps<SVGSVGElement>) => ( <svg width="21" height="20" viewBox="0 0 21 20" xmlns="http://www.w3.org/2000/svg" {...props}> @@ -15,8 +15,8 @@ const CreatePostButtonSvg = (props: React.SVGProps<SVGSVGElement>) => ( interface CreatePostButtonProps { pageId?: string; componentId: string; - onClick?: (e: React.MouseEvent) => void; defaultClassName?: string; + onClick?: () => void; } export function CreatePostButton({ @@ -32,14 +32,13 @@ export function CreatePostButton({ componentId, elementId, }); - const { AmityCreatePostMenuComponentBehavior } = usePageBehavior(); if (isExcluded) return null; return ( - <button + <Button className={styles.createPostButton} - onClick={() => AmityCreatePostMenuComponentBehavior.goToSelectPostTargetPage()} + onPress={() => onClick?.()} data-qa-anchor={accessibilityId} style={themeStyles} > @@ -52,7 +51,7 @@ export function CreatePostButton({ defaultIconName={defaultConfig.image} /> <Typography.Body className={styles.createPostButton__text}>{config.text}</Typography.Body> - </button> + </Button> ); } diff --git a/src/v4/social/elements/CreateStoryButton/CreateStoryButton.tsx b/src/v4/social/elements/CreateStoryButton/CreateStoryButton.tsx index 1c8ac0c99..a8fcbea4d 100644 --- a/src/v4/social/elements/CreateStoryButton/CreateStoryButton.tsx +++ b/src/v4/social/elements/CreateStoryButton/CreateStoryButton.tsx @@ -4,7 +4,7 @@ import { useAmityElement } from '~/v4/core/hooks/uikit'; import { Typography } from '~/v4/core/components'; import styles from './CreateStoryButton.module.css'; import clsx from 'clsx'; -import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; +import { Button } from '~/v4/core/natives/Button'; const CreateStoryButtonSvg = (props: React.SVGProps<SVGSVGElement>) => ( <svg width="17" height="18" viewBox="0 0 17 18" xmlns="http://www.w3.org/2000/svg" {...props}> @@ -20,8 +20,8 @@ const CreateStoryButtonSvg = (props: React.SVGProps<SVGSVGElement>) => ( interface CreateStoryButtonProps { pageId?: string; componentId: string; - onClick?: (e: React.MouseEvent) => void; defaultClassName?: string; + onClick?: () => void; } export function CreateStoryButton({ @@ -37,15 +37,14 @@ export function CreateStoryButton({ componentId, elementId, }); - const { AmityCreatePostMenuComponentBehavior } = usePageBehavior(); if (isExcluded) return null; return ( - <div + <Button className={styles.createStoryButton} - onClick={() => AmityCreatePostMenuComponentBehavior.goToStoryTargetSelectionPage()} data-qa-anchor={accessibilityId} style={themeStyles} + onPress={() => onClick?.()} > <IconComponent defaultIcon={() => ( @@ -58,7 +57,7 @@ export function CreateStoryButton({ defaultIconName={defaultConfig.image} /> <Typography.Body className={styles.createStoryButton__text}>{config.text}</Typography.Body> - </div> + </Button> ); } diff --git a/src/v4/social/elements/PostCreationButton/PostCreationButton.tsx b/src/v4/social/elements/PostCreationButton/PostCreationButton.tsx index 03453cd79..5a18a5b23 100644 --- a/src/v4/social/elements/PostCreationButton/PostCreationButton.tsx +++ b/src/v4/social/elements/PostCreationButton/PostCreationButton.tsx @@ -2,8 +2,7 @@ import clsx from 'clsx'; import React from 'react'; import { useAmityElement } from '~/v4/core/hooks/uikit'; import { IconComponent } from '~/v4/core/IconComponent'; -import { getDefaultConfig, useCustomization } from '~/v4/core/providers/CustomizationProvider'; -import { useGenerateStylesShadeColors } from '~/v4/core/providers/ThemeProvider'; +import { Button } from '~/v4/core/natives/Button'; import styles from './PostCreationButton.module.css'; const PostCreationButtonSvg = (props: React.SVGProps<SVGSVGElement>) => ( @@ -25,8 +24,7 @@ export interface PostCreationButtonProps { componentId?: string; defaultClassName?: string; imgClassName?: string; - onClick?: (e: React.MouseEvent) => void; - createPostButtonRef: React.RefObject<HTMLDivElement>; + onClick?: () => void; } export function PostCreationButton({ @@ -35,7 +33,6 @@ export function PostCreationButton({ defaultClassName, imgClassName, onClick, - createPostButtonRef, }: PostCreationButtonProps) { const elementId = 'post_creation_button'; const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = @@ -50,17 +47,16 @@ export function PostCreationButton({ return ( <IconComponent defaultIcon={() => ( - <div + <Button style={themeStyles} className={styles.postCreationButton} - onClick={onClick} + onPress={onClick} data-qa-anchor={accessibilityId} - ref={createPostButtonRef} > <PostCreationButtonSvg className={clsx(styles.postCreationButton__icon, defaultClassName)} /> - </div> + </Button> )} imgIcon={() => ( <img diff --git a/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx b/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx index 6dc72e42d..8857d4840 100644 --- a/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx +++ b/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx @@ -9,11 +9,9 @@ import { MyCommunitiesButton } from '~/v4/social/elements/MyCommunitiesButton'; import { Newsfeed } from '~/v4/social/components/Newsfeed'; import { useAmityPage } from '~/v4/core/hooks/uikit'; import { CreatePostMenu } from '~/v4/social/components/CreatePostMenu'; -import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; -import { useNavigation } from '~/v4/core/providers/NavigationProvider'; import { useGlobalFeedContext } from '../../providers/GlobalFeedProvider'; -enum EnumTabNames { +export enum HomePageTab { Newsfeed = 'Newsfeed', Explore = 'Explore', MyCommunities = 'My communities', @@ -24,15 +22,12 @@ export function SocialHomePage() { const { themeStyles } = useAmityPage({ pageId, }); - const { goToSocialGlobalSearchPage, goToMyCommunitiesSearchPage } = useNavigation(); const { scrollPosition, onScroll } = useGlobalFeedContext(); - const [activeTab, setActiveTab] = useState(EnumTabNames.Newsfeed); + const [activeTab, setActiveTab] = useState(HomePageTab.Newsfeed); const [isShowCreatePostMenu, setIsShowCreatePostMenu] = useState(false); - const createPostMenuRef = useRef<HTMLDivElement | null>(null); - const createPostButtonRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null); const initialLoad = useRef(true); @@ -44,8 +39,7 @@ export function SocialHomePage() { }, 100); }, [containerRef.current]); - const handleClickButton = (event: React.MouseEvent) => { - event.stopPropagation(); + const handleClickButton = () => { setIsShowCreatePostMenu((prev) => !prev); }; @@ -54,36 +48,19 @@ export function SocialHomePage() { onScroll(event); }; - const handleGlobalSearchClick = () => { - switch (activeTab) { - case EnumTabNames.Newsfeed: - goToSocialGlobalSearchPage(); - break; - case EnumTabNames.Explore: - break; - case EnumTabNames.MyCommunities: - goToMyCommunitiesSearchPage(); - - break; - } - }; - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if ( - createPostMenuRef.current && - !createPostMenuRef.current.contains(event.target as Node) && - createPostButtonRef.current !== event.target - ) { + const handleClickOutside = () => { + if (isShowCreatePostMenu) { setIsShowCreatePostMenu(false); } }; - document.addEventListener('mousedown', handleClickOutside); - return () => { + if (isShowCreatePostMenu) { + document.addEventListener('mousedown', handleClickOutside); + } else { document.removeEventListener('mousedown', handleClickOutside); - }; - }, []); + } + }, [isShowCreatePostMenu]); return ( <div className={styles.socialHomePage} style={themeStyles}> @@ -91,32 +68,31 @@ export function SocialHomePage() { <div className={styles.socialHomePage__topNavigation}> <TopNavigation pageId={pageId} + selectedTab={activeTab} onClickPostCreationButton={handleClickButton} - onGlobalSearchButtonClick={handleGlobalSearchClick} - createPostButtonRef={createPostButtonRef} /> </div> <div className={styles.socialHomePage__tabs}> <NewsfeedButton pageId={pageId} - isActive={activeTab === EnumTabNames.Newsfeed} - onClick={() => setActiveTab(EnumTabNames.Newsfeed)} + isActive={activeTab === HomePageTab.Newsfeed} + onClick={() => setActiveTab(HomePageTab.Newsfeed)} /> - <ExploreButton pageId={pageId} isActive={activeTab === EnumTabNames.Explore} /> + <ExploreButton pageId={pageId} isActive={activeTab === HomePageTab.Explore} /> <MyCommunitiesButton pageId={pageId} - isActive={activeTab === EnumTabNames.MyCommunities} - onClick={() => setActiveTab(EnumTabNames.MyCommunities)} + isActive={activeTab === HomePageTab.MyCommunities} + onClick={() => setActiveTab(HomePageTab.MyCommunities)} /> </div> </div> <div className={styles.socialHomePage__contents} ref={containerRef} onScroll={handleScroll}> - {activeTab === EnumTabNames.Newsfeed && <Newsfeed pageId={pageId} />} - {activeTab === EnumTabNames.Explore && <div>Explore</div>} - {activeTab === EnumTabNames.MyCommunities && <MyCommunities pageId={pageId} />} + {activeTab === HomePageTab.Newsfeed && <Newsfeed pageId={pageId} />} + {activeTab === HomePageTab.Explore && <div>Explore</div>} + {activeTab === HomePageTab.MyCommunities && <MyCommunities pageId={pageId} />} </div> {isShowCreatePostMenu && ( - <div ref={createPostMenuRef} className={styles.socialHomePage__createPostMenu}> + <div className={styles.socialHomePage__createPostMenu}> <CreatePostMenu pageId={pageId} /> </div> )} From a4cff590dc67851eec64febbc6cb3a5e85520b67 Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Fri, 12 Jul 2024 19:14:12 +0700 Subject: [PATCH 246/300] feat: ASC-24094 - update code snippet (#529) * feat: create post button * feat: code snippet * feat: update MyCommunitiesSearchPage * fest: export components and page * fix: ci error * feat: export TopNavigation * fix: types * feat: export AmitySocialHomePageTab --------- Co-authored-by: Bonn <pittawat@amity.co> --- src/index.ts | 37 +++++++++++--- src/v4/core/hooks/usePostFlaggedByMe.ts | 17 +++++-- .../core/providers/PageBehaviorProvider.tsx | 50 +++++++++---------- .../CreatePostMenu/CreatePostMenu.tsx | 4 +- .../social/components/StoryTab/StoryTab.tsx | 10 ++-- src/v4/social/components/index.ts | 12 +++++ .../elements/ClearButton/ClearButton.tsx | 2 +- .../CreatePostButton/CreatePostButton.tsx | 2 +- src/v4/social/pages/DraftsPage/DraftsPage.tsx | 6 +-- .../PostComposerPage/PostComposerPage.tsx | 7 ++- .../social/pages/PostComposerPage/index.tsx | 2 +- .../SelectPostTargetPage.tsx | 6 +-- src/v4/social/pages/SocialHomePage/index.tsx | 2 +- .../social/pages/StoryPage/ViewStoryPage.tsx | 6 +-- .../StoryTargetSelectionPage.tsx | 2 +- src/v4/social/pages/index.ts | 6 +++ 16 files changed, 113 insertions(+), 58 deletions(-) diff --git a/src/index.ts b/src/index.ts index 4ef0265de..eaacd94d9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,11 +20,6 @@ export { useSDK as useAmitySDK } from '~/core/hooks/useSDK'; // v4 export { AmityUIKitManager } from '~/v4/core/AmityUIKitManager'; -export { - AmityDraftStoryPage, - ViewStoryPage as AmityViewStoryPage, - StoryTargetSelectionPage as AmityStoryTargetSelectionPage, -} from '~/v4/social/pages'; export { CommentTray as AmityCommentTrayComponent, StoryTab as AmityStoryTabComponent, @@ -39,7 +34,6 @@ export { MessageComposer as AmityLiveChatMessageComposeBar } from '~/v4/chat/com export { MessageReactionPreview as AmityLiveChatMessageReactionPreview } from '~/v4/chat/components/MessageReactionPreview'; export { MessageReactionPicker as AmityLiveChatMessageReactionPicker } from '~/v4/chat/components/MessageReactionPicker'; export { MessageQuickReaction as AmityLiveChatMessageQuickReaction } from '~/v4/chat/components/MessageQuickReaction'; -export { ReactionList as AmityReactionList } from '~/v4/social/components/ReactionList'; import type { MessageActionType } from '~/v4/chat/internal-components/LiveChatMessageContent/MessageAction'; import type { ReactionListProps } from '~/v4/social/components/ReactionList'; @@ -54,3 +48,34 @@ export { StoryPreview as AmityStoryPreview, StoryPreviewThumbnail as AmityStoryPreviewThumbnail, } from './v4/social/internal-components/StoryPreview'; + +// v4 Social +export { + SocialHomePage as AmitySocialHomePage, + AmityDraftStoryPage, + ViewStoryPage as AmityViewStoryPage, + StoryTargetSelectionPage as AmityStoryTargetSelectionPage, + PostDetailPage as AmityPostDetailPage, + SocialGlobalSearchPage as AmitySocialGlobalSearchPage, + MyCommunitiesSearchPage as AmityMyCommunitiesSearchPage, + SelectPostTargetPage as AmityPostTargetSelectionPage, + PostComposerPage as AmityPostComposerPage, +} from '~/v4/social/pages'; + +export { + MyCommunities as AmityMyCommunitiesComponent, + EmptyNewsfeed as AmityEmptyNewsFeedComponent, + GlobalFeed as AmityGlobalFeedComponent, + PostContent as AmityPostContentComponent, + TopSearchBar as AmityTopSearchBarComponent, + Newsfeed as AmityNewsFeedComponent, + CommunitySearchResult as AmityCommunitySearchResultComponent, + UserSearchResult as AmityUserSearchResultComponent, + DetailedMediaAttachment as AmityDetailedMediaAttachmentComponent, + MediaAttachment as AmityMediaAttachmentComponent, + CreatePostMenu as AmityCreatePostMenuComponent, + ReactionList as AmityReactionListComponent, + TopNavigation as AmitySocialHomeTopNavigationComponent, +} from '~/v4/social/components/'; + +export { HomePageTab as AmitySocialHomePageTab } from '~/v4/social/pages/SocialHomePage'; diff --git a/src/v4/core/hooks/usePostFlaggedByMe.ts b/src/v4/core/hooks/usePostFlaggedByMe.ts index bb808c15b..41d95763d 100644 --- a/src/v4/core/hooks/usePostFlaggedByMe.ts +++ b/src/v4/core/hooks/usePostFlaggedByMe.ts @@ -1,6 +1,10 @@ import { PostRepository } from '@amityco/ts-sdk'; -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { useMemo } from 'react'; +import { + UseMutateAsyncFunction, + useMutation, + useQuery, + useQueryClient, +} from '@tanstack/react-query'; export const usePostFlaggedByMe = ({ post, @@ -16,7 +20,12 @@ export const usePostFlaggedByMe = ({ onReportError?: (error: Error) => void; onUnreportSuccess?: () => void; onUnreportError?: (error: Error) => void; -}) => { +}): { + isLoading: boolean; + isFlaggedByMe: boolean; + mutateReportPost: UseMutateAsyncFunction<boolean, Error, void, void>; + mutateUnReportPost: UseMutateAsyncFunction<boolean, Error, void, void>; +} => { const queryClient = useQueryClient(); const postId = post?.postId || undefined; @@ -92,7 +101,7 @@ export const usePostFlaggedByMe = ({ return { isLoading, - isFlaggedByMe: data, + isFlaggedByMe: data || false, mutateReportPost, mutateUnReportPost, }; diff --git a/src/v4/core/providers/PageBehaviorProvider.tsx b/src/v4/core/providers/PageBehaviorProvider.tsx index aa310a690..f0fdf5184 100644 --- a/src/v4/core/providers/PageBehaviorProvider.tsx +++ b/src/v4/core/providers/PageBehaviorProvider.tsx @@ -3,39 +3,39 @@ import { PageTypes, useNavigation } from '~/v4/core/providers/NavigationProvider import { Mode } from '~/v4/social/pages/PostComposerPage/PostComposerPage'; export interface PageBehavior { - AmityStoryViewPageBehavior: { - onCloseAction(): void; + AmityStoryViewPageBehavior?: { + onCloseAction?(): void; hyperLinkAction?(context: Record<string, unknown>): void; }; - AmityDraftStoryPageBehavior: { - onCloseAction(): void; + AmityDraftStoryPageBehavior?: { + onCloseAction?(): void; }; - onClickHyperLink(): void; - AmitySocialHomePageBehavior: Record<string, unknown>; - AmityGlobalFeedComponentBehavior: { - goToPostDetailPage: (context: { postId: string }) => void; - goToViewStoryPage: (context: { + onClickHyperLink?(): void; + AmitySocialHomePageBehavior?: Record<string, unknown>; + AmityGlobalFeedComponentBehavior?: { + goToPostDetailPage?: (context: { postId: string }) => void; + goToViewStoryPage?: (context: { targetId: string; targetType: Amity.StoryTargetType; storyType: 'communityFeed' | 'globalFeed'; targetIds?: string[]; }) => void; }; - AmityPostDetailPageBehavior: Record<string, unknown>; - AmityPostContentComponentBehavior: { - goToCommunityProfilePage: (context: { communityId: string }) => void; - goToUserProfilePage: (context: { userId: string }) => void; + AmityPostDetailPageBehavior?: Record<string, unknown>; + AmityPostContentComponentBehavior?: { + goToCommunityProfilePage?: (context: { communityId: string }) => void; + goToUserProfilePage?: (context: { userId: string }) => void; }; - AmitySocialGlobalSearchPageBehavior: Record<string, unknown>; - AmityCommunitySearchResultComponentBehavior: { - goToCommunityProfilePage: (context: { communityId: string }) => void; + AmitySocialGlobalSearchPageBehavior?: Record<string, unknown>; + AmityCommunitySearchResultComponentBehavior?: { + goToCommunityProfilePage?: (context: { communityId: string }) => void; }; - AmityCreatePostMenuComponentBehavior: { - goToSelectPostTargetPage(): void; - goToStoryTargetSelectionPage(): void; + AmityCreatePostMenuComponentBehavior?: { + goToSelectPostTargetPage?(): void; + goToStoryTargetSelectionPage?(): void; }; - AmityPostTargetSelectionPage: { - goToPostComposerPage: (context: { + AmityPostTargetSelectionPage?: { + goToPostComposerPage?: (context: { mode: Mode.CREATE | Mode.EDIT; targetId: string | null; targetType: 'community' | 'user'; @@ -43,16 +43,16 @@ export interface PageBehavior { post?: Amity.Post; }) => void; }; - AmityStoryTargetSelectionPage: { - goToStoryCreationPage(context: { + AmityStoryTargetSelectionPage?: { + goToStoryCreationPage?(context: { targetId: string | null; targetType: Amity.StoryTargetType; mediaType: { type: 'image'; url: string } | { type: 'video'; url: string }; storyType: 'communityFeed' | 'globalFeed'; }): void; }; - AmityPostComposerPageBehavior: { - goToSocialHomePage(): void; + AmityPostComposerPageBehavior?: { + goToSocialHomePage?(): void; }; } diff --git a/src/v4/social/components/CreatePostMenu/CreatePostMenu.tsx b/src/v4/social/components/CreatePostMenu/CreatePostMenu.tsx index 9abf9961c..87230f30c 100644 --- a/src/v4/social/components/CreatePostMenu/CreatePostMenu.tsx +++ b/src/v4/social/components/CreatePostMenu/CreatePostMenu.tsx @@ -18,12 +18,12 @@ export function CreatePostMenu({ pageId }: CreatePostMenuProps) { <CreatePostButton pageId={pageId} componentId={componentId} - onClick={() => AmityCreatePostMenuComponentBehavior.goToSelectPostTargetPage()} + onClick={() => AmityCreatePostMenuComponentBehavior?.goToSelectPostTargetPage?.()} /> <CreateStoryButton pageId={pageId} componentId={componentId} - onClick={() => AmityCreatePostMenuComponentBehavior.goToStoryTargetSelectionPage()} + onClick={() => AmityCreatePostMenuComponentBehavior?.goToStoryTargetSelectionPage?.()} /> </div> ); diff --git a/src/v4/social/components/StoryTab/StoryTab.tsx b/src/v4/social/components/StoryTab/StoryTab.tsx index 418448989..a1d16a988 100644 --- a/src/v4/social/components/StoryTab/StoryTab.tsx +++ b/src/v4/social/components/StoryTab/StoryTab.tsx @@ -6,9 +6,11 @@ import { useStoryContext } from '~/v4/social/providers/StoryProvider'; import { StoryTabCommunityFeed } from './StoryTabCommunity'; import { StoryTabGlobalFeed } from './StoryTabGlobalFeed'; -type StoryTabProps = { type: 'communityFeed'; communityId: string } | { type: 'globalFeed' }; +type StoryTabProps = ({ type: 'communityFeed'; communityId: string } | { type: 'globalFeed' }) & { + pageId?: string; +}; -export const StoryTab: React.FC<StoryTabProps> = (props) => { +export const StoryTab: React.FC<StoryTabProps> = ({ pageId = '*', ...props }) => { const componentId = 'story_tab_component'; const { AmityGlobalFeedComponentBehavior } = usePageBehavior(); const { goToViewStoryPage, goToDraftStoryPage } = useNavigation(); @@ -19,6 +21,7 @@ export const StoryTab: React.FC<StoryTabProps> = (props) => { case 'communityFeed': return ( <StoryTabCommunityFeed + pageId={pageId} componentId={componentId} communityId={props.communityId || ''} onFileChange={(file) => { @@ -46,9 +49,10 @@ export const StoryTab: React.FC<StoryTabProps> = (props) => { case 'globalFeed': return ( <StoryTabGlobalFeed + pageId={pageId} componentId={componentId} goToViewStoryPage={({ storyTarget, storyTargets }) => { - AmityGlobalFeedComponentBehavior.goToViewStoryPage({ + AmityGlobalFeedComponentBehavior?.goToViewStoryPage?.({ targetId: storyTarget.targetId, targetType: storyTarget.targetType as Amity.StoryTargetType, storyType: 'globalFeed', diff --git a/src/v4/social/components/index.ts b/src/v4/social/components/index.ts index 6b93fd641..8cbcd8eb9 100644 --- a/src/v4/social/components/index.ts +++ b/src/v4/social/components/index.ts @@ -3,3 +3,15 @@ export * from './CommentTray'; export * from './HyperLinkConfig'; export * from './ReactionList'; export * from './StoryTab'; +export * from './CreatePostMenu'; +export * from './MediaAttachment'; +export * from './DetailedMediaAttachment'; +export * from './UserSearchResult'; +export * from './CommunitySearchResult'; +export * from './TopSearchBar'; +export * from './PostContent'; +export * from './MyCommunities'; +export * from './GlobalFeed'; +export * from './EmptyNewsFeed'; +export * from './Newsfeed'; +export * from './TopNavigation'; diff --git a/src/v4/social/elements/ClearButton/ClearButton.tsx b/src/v4/social/elements/ClearButton/ClearButton.tsx index e97d838fb..02b6a5310 100644 --- a/src/v4/social/elements/ClearButton/ClearButton.tsx +++ b/src/v4/social/elements/ClearButton/ClearButton.tsx @@ -3,7 +3,7 @@ import { IconComponent } from '~/v4/core/IconComponent'; import { useAmityElement } from '~/v4/core/hooks/uikit'; import styles from './ClearButton.module.css'; -import { Button } from '~/v4/core/natives/Button'; +import { Button, ButtonProps } from '~/v4/core/natives/Button'; function ClearButtonSvg(props: React.SVGProps<SVGSVGElement>) { return ( diff --git a/src/v4/social/elements/CreatePostButton/CreatePostButton.tsx b/src/v4/social/elements/CreatePostButton/CreatePostButton.tsx index a52149571..0f0f65f58 100644 --- a/src/v4/social/elements/CreatePostButton/CreatePostButton.tsx +++ b/src/v4/social/elements/CreatePostButton/CreatePostButton.tsx @@ -22,8 +22,8 @@ interface CreatePostButtonProps { export function CreatePostButton({ pageId = '*', componentId = '*', - onClick, defaultClassName, + onClick, }: CreatePostButtonProps) { const elementId = 'create_post_button'; const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index d51defb69..69a3d73e3 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -297,9 +297,9 @@ export const AmityDraftStoryPage = (props: AmityDraftStoryPageProps) => { return ( <PlainDraftStoryPage {...props} - onDiscardCreateStory={() => AmityDraftStoryPageBehavior.onCloseAction()} - goToCommunityPage={(communityId) => AmityDraftStoryPageBehavior.onCloseAction()} - goToGlobalFeedPage={() => AmityDraftStoryPageBehavior.onCloseAction()} + onDiscardCreateStory={() => AmityDraftStoryPageBehavior?.onCloseAction?.()} + goToCommunityPage={(communityId) => AmityDraftStoryPageBehavior?.onCloseAction?.()} + goToGlobalFeedPage={() => AmityDraftStoryPageBehavior?.onCloseAction?.()} /> ); }; diff --git a/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx b/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx index 14b000830..9f9975c2f 100644 --- a/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx +++ b/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useRef, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import styles from './PostComposerPage.module.css'; import { useAmityPage } from '~/v4/core/hooks/uikit'; import { CloseButton } from '~/v4/social/elements/CloseButton/CloseButton'; @@ -15,7 +15,6 @@ import ExclamationCircle from '~/v4/icons/ExclamationCircle'; import { useForm } from 'react-hook-form'; import { Mentioned } from '~/v4/helpers/utils'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; -import useConnectionState from '~/v4/social/hooks/useConnectionState'; import { Drawer } from 'vaul'; import ReactDOM from 'react-dom'; import { DetailedMediaAttachment } from '~/v4/social/components/DetailedMediaAttachment'; @@ -144,7 +143,7 @@ const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCr return PostRepository.createPost(params); }, onSuccess: (response) => { - AmityPostComposerPageBehavior.goToSocialHomePage(); + AmityPostComposerPageBehavior?.goToSocialHomePage?.(); prependItem(response.data); }, onError: (error) => { @@ -197,7 +196,7 @@ const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCr title: 'Discard this post?', content: 'The post will be permanently deleted. It cannot be undone.', onOk: () => { - AmityPostComposerPageBehavior.goToSocialHomePage(); + AmityPostComposerPageBehavior?.goToSocialHomePage?.(); }, okText: 'Discard', cancelText: 'Keep editing', diff --git a/src/v4/social/pages/PostComposerPage/index.tsx b/src/v4/social/pages/PostComposerPage/index.tsx index 662fb7624..79c9d9cd8 100644 --- a/src/v4/social/pages/PostComposerPage/index.tsx +++ b/src/v4/social/pages/PostComposerPage/index.tsx @@ -1 +1 @@ -export { PostComposerPage } from './PostComposerPage'; \ No newline at end of file +export { PostComposerPage, Mode } from './PostComposerPage'; \ No newline at end of file diff --git a/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.tsx b/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.tsx index a4b9ecb15..49f478a4c 100644 --- a/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.tsx +++ b/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.tsx @@ -15,7 +15,7 @@ import useIntersectionObserver from '~/v4/core/hooks/useIntersectionObserver'; import { useUser } from '~/v4/core/hooks/objects/useUser'; import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; import useSDK from '~/v4/core/hooks/useSDK'; -import { Mode } from '../PostComposerPage/PostComposerPage'; +import { Mode } from '~/v4/social/pages/PostComposerPage/'; export function SelectPostTargetPage() { const pageId = 'select_post_target_page'; @@ -43,7 +43,7 @@ export function SelectPostTargetPage() { return ( <div onClick={() => { - AmityPostTargetSelectionPage.goToPostComposerPage({ + AmityPostTargetSelectionPage?.goToPostComposerPage?.({ targetId: community.communityId, targetType: 'community', mode: Mode.CREATE, @@ -78,7 +78,7 @@ export function SelectPostTargetPage() { </div> <div onClick={() => { - AmityPostTargetSelectionPage.goToPostComposerPage({ + AmityPostTargetSelectionPage?.goToPostComposerPage?.({ mode: Mode.CREATE, targetId: null, targetType: 'user', diff --git a/src/v4/social/pages/SocialHomePage/index.tsx b/src/v4/social/pages/SocialHomePage/index.tsx index 71c95f184..7314d8968 100644 --- a/src/v4/social/pages/SocialHomePage/index.tsx +++ b/src/v4/social/pages/SocialHomePage/index.tsx @@ -1 +1 @@ -export { SocialHomePage } from './SocialHomePage'; +export { SocialHomePage, HomePageTab } from './SocialHomePage'; diff --git a/src/v4/social/pages/StoryPage/ViewStoryPage.tsx b/src/v4/social/pages/StoryPage/ViewStoryPage.tsx index 3333b5ac9..0d7928b4e 100644 --- a/src/v4/social/pages/StoryPage/ViewStoryPage.tsx +++ b/src/v4/social/pages/StoryPage/ViewStoryPage.tsx @@ -35,9 +35,9 @@ const ViewStoryPage: React.FC<AmityViewStoryPageProps> = ({ type, targetId }) => <ViewGlobalFeedStoryPage pageId={pageId} targetId={targetId} - onChangePage={() => AmityStoryViewPageBehavior.onCloseAction()} - onClose={() => AmityStoryViewPageBehavior.onCloseAction()} - onSwipeDown={() => AmityStoryViewPageBehavior.onCloseAction()} + onChangePage={() => AmityStoryViewPageBehavior?.onCloseAction?.()} + onClose={() => AmityStoryViewPageBehavior?.onCloseAction?.()} + onSwipeDown={() => AmityStoryViewPageBehavior?.onCloseAction?.()} onClickStory={(targetId) => goToViewStoryPage({ storyType: 'globalFeed', diff --git a/src/v4/social/pages/StoryTargetSelectionPage/StoryTargetSelectionPage.tsx b/src/v4/social/pages/StoryTargetSelectionPage/StoryTargetSelectionPage.tsx index 9d7318157..6cb517036 100644 --- a/src/v4/social/pages/StoryTargetSelectionPage/StoryTargetSelectionPage.tsx +++ b/src/v4/social/pages/StoryTargetSelectionPage/StoryTargetSelectionPage.tsx @@ -70,7 +70,7 @@ export function StoryTargetSelectionPage() { useEffect(() => { if (file) { - AmityStoryTargetSelectionPage.goToStoryCreationPage({ + AmityStoryTargetSelectionPage?.goToStoryCreationPage?.({ targetId: selectedCommunityId, targetType: 'community', mediaType: diff --git a/src/v4/social/pages/index.ts b/src/v4/social/pages/index.ts index 015555f59..3ff55cd60 100644 --- a/src/v4/social/pages/index.ts +++ b/src/v4/social/pages/index.ts @@ -1,3 +1,9 @@ export { AmityDraftStoryPage } from './DraftsPage'; export { ViewStoryPage } from './StoryPage'; export { StoryTargetSelectionPage } from './StoryTargetSelectionPage'; +export { SocialHomePage } from './SocialHomePage'; +export { PostComposerPage } from './PostComposerPage'; +export { SelectPostTargetPage } from './SelectPostTargetPage'; +export { MyCommunitiesSearchPage } from './MyCommunitiesSearchPage'; +export { SocialGlobalSearchPage } from './SocialGlobalSearchPage'; +export { PostDetailPage } from './PostDetailPage'; From ee61d27ad8547b6988f6eea99290d031166e0393 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Fri, 12 Jul 2024 19:14:23 +0700 Subject: [PATCH 247/300] fix: ASC-21809 - story video duration (#511) * fix: story uploading state * fix: remove unnessescary useMeme * fix: remove unused * fix: bottom sheet story * fix: video duration * fix: remove unused * fix: type * fix: done button type * fix: type * fix: ignore type * fix duration * fix: story image bottom sheet --- .../components/CommentTray/CommentTray.tsx | 2 +- .../StoryViewer/Renderers/Image.tsx | 70 +++-- .../StoryViewer/Renderers/Video.tsx | 261 ++++++++++-------- .../Renderers/Wrappers/Footer/index.tsx | 6 +- .../StoryViewer/Renderers/types.ts | 2 +- .../pages/StoryPage/CommunityFeedStory.tsx | 8 +- .../pages/StoryPage/GlobalFeedStory.tsx | 6 +- 7 files changed, 195 insertions(+), 160 deletions(-) diff --git a/src/v4/social/components/CommentTray/CommentTray.tsx b/src/v4/social/components/CommentTray/CommentTray.tsx index 3a2a1494e..e4d393220 100644 --- a/src/v4/social/components/CommentTray/CommentTray.tsx +++ b/src/v4/social/components/CommentTray/CommentTray.tsx @@ -8,7 +8,7 @@ const REPLIES_PER_PAGE = 5; interface CommentTrayProps { referenceType: Amity.CommentReferenceType; - referenceId: string; + referenceId?: string; community: Amity.Community; shouldAllowInteraction: boolean; shouldAllowCreation?: boolean; diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index d7f0fb073..b583af669 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useRef } from 'react'; +import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; import { useIntl } from 'react-intl'; import Truncate from 'react-truncate-markup'; import { @@ -44,6 +44,7 @@ export const renderer: CustomRenderer = ({ }) => { const { formatMessage } = useIntl(); const [loaded, setLoaded] = useState(false); + const [isOpenBottomSheet, setIsOpenBottomSheet] = useState(false); const [isOpenCommentSheet, setIsOpenCommentSheet] = useState(false); const [isPaused, setIsPaused] = useState(false); const { loader } = config; @@ -65,8 +66,6 @@ export const renderer: CustomRenderer = ({ myReactions, data, items, - isBottomSheetOpen, - setIsBottomSheetOpen, } = story as Amity.Story; const { members } = useCommunityMembersCollection({ @@ -87,38 +86,45 @@ export const renderer: CustomRenderer = ({ const haveStoryPermission = isGlobalAdmin || isModeratorUser || checkStoryPermission(client, community?.communityId); - const heading = <div data-qa-anchor="community_display_name">{community?.displayName}</div>; - const subheading = - createdAt && creator?.displayName ? ( - <span> - <span data-qa-anchor="created_at">{formatTimeAgo(createdAt as string)}</span> • By{' '} - <span data-qa-anchor="creator_display_name">{creator?.displayName}</span> - </span> - ) : ( - '' - ); - + const heading = useMemo( + () => <div data-qa-anchor="community_display_name">{community?.displayName}</div>, + [community?.displayName], + ); + const subheading = useMemo( + () => + createdAt && creator?.displayName ? ( + <span> + <span data-qa-anchor="created_at">{formatTimeAgo(createdAt as string)}</span> • By{' '} + <span data-qa-anchor="creator_display_name">{creator?.displayName}</span> + </span> + ) : ( + '' + ), + [createdAt, creator?.displayName], + ); const targetRootId = 'asc-uikit-stories-viewer'; - const extractColors = () => { - const colorThief = new ColorThief(); - const palette = colorThief.getPalette(imageRef.current, 2); - if (palette) { - const gradient = `linear-gradient(to top, rgb(${palette[0].join(',')}), rgb(${palette[1].join( - ',', - )})`; - setBackgroundGradient(gradient); + const extractColors = useCallback(() => { + if (imageRef.current && imageRef.current.complete) { + const colorThief = new ColorThief(); + const palette = colorThief.getPalette(imageRef.current, 2); + if (palette) { + const gradient = `linear-gradient(to top, rgb(${palette[0].join( + ',', + )}), rgb(${palette[1].join(',')})`; + setBackgroundGradient(gradient); + } } - }; + }, []); - const imageLoaded = () => { + const imageLoaded = useCallback(() => { setLoaded(true); if (isPaused) { setIsPaused(false); } action('play', true); extractColors(); - }; + }, [action, isPaused, extractColors]); const play = () => { action('play', true); @@ -131,11 +137,11 @@ export const renderer: CustomRenderer = ({ const openBottomSheet = () => { action('pause', true); - setIsBottomSheetOpen(true); + setIsOpenBottomSheet(true); }; const closeBottomSheet = () => { action('play', true); - setIsBottomSheetOpen(false); + setIsOpenBottomSheet(false); }; const openCommentSheet = () => { action('pause', true); @@ -154,6 +160,12 @@ export const renderer: CustomRenderer = ({ increaseIndex(); }; + useEffect(() => { + if (imageRef.current && imageRef.current.complete) { + extractColors(); + } + }, []); + useEffect(() => { if (fileInputRef.current) { const handleClick = () => { @@ -210,7 +222,7 @@ export const renderer: CustomRenderer = ({ duration={5000} currentIndex={currentIndex} storiesCount={storiesCount} - isPaused={isPaused || isBottomSheetOpen || isOpenCommentSheet} + isPaused={isPaused || isOpenBottomSheet || isOpenCommentSheet} onComplete={handleProgressComplete} /> <Header @@ -253,7 +265,7 @@ export const renderer: CustomRenderer = ({ <BottomSheet rootId={targetRootId} - isOpen={isBottomSheetOpen} + isOpen={isOpenBottomSheet} onClose={closeBottomSheet} mountPoint={document.getElementById(targetRootId) as HTMLElement} detent="content-height" diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx index fc39dac32..b4e2e867d 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Video.tsx @@ -1,27 +1,80 @@ -import React, { useEffect, useRef, useState } from 'react'; - +import React, { useEffect, useRef, useState, useCallback } from 'react'; import Truncate from 'react-truncate-markup'; import { CustomRenderer, Tester, } from '~/v4/social/internal-components/StoryViewer/Renderers/types'; -import { SpeakerButton } from '~/v4/social/elements'; - +import { SpeakerButton, HyperLink } from '~/v4/social/elements'; import { BottomSheet, Typography } from '~/v4/core/components'; import { Button } from '~/v4/core/natives/Button'; - import { CommentTray } from '~/v4/social/components'; -import { HyperLink } from '~/v4/social/elements/HyperLink'; import Header from '~/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Header'; import Footer from '~/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer'; import useCommunityMembersCollection from '~/v4/social/hooks/collections/useCommunityMembersCollection'; import useSDK from '~/v4/core/hooks/useSDK'; import { useUser } from '~/v4/core/hooks/objects/useUser'; -import clsx from 'clsx'; import { LIKE_REACTION_KEY } from '~/v4/social/constants/reactions'; import { checkStoryPermission, formatTimeAgo, isAdmin, isModerator } from '~/v4/social/utils'; -import rendererStyles from './Renderers.module.css'; import { StoryProgressBar } from '~/v4/social/elements/StoryProgressBar/StoryProgressBar'; +import clsx from 'clsx'; +import rendererStyles from './Renderers.module.css'; +import { Action } from 'react-insta-stories/dist/interfaces'; + +const useAudioControl = () => { + const [muted, setMuted] = useState(false); + const mute = useCallback(() => setMuted(true), []); + const unmute = useCallback(() => setMuted(false), []); + return { muted, mute, unmute }; +}; + +const usePauseControl = (action: Action) => { + const [isPaused, setIsPaused] = useState(false); + const play = useCallback(() => { + action('play', true); + setIsPaused(false); + }, [action]); + + const pause = useCallback(() => { + action('pause', true); + setIsPaused(true); + }, [action]); + + return { isPaused, play, pause }; +}; + +const useBottomSheetControl = (action: Action) => { + const [isBottomSheetOpen, setIsBottomSheetOpen] = useState(false); + const [isOpenCommentSheet, setIsOpenCommentSheet] = useState(false); + + const openBottomSheet = useCallback(() => { + action('pause', true); + setIsBottomSheetOpen(true); + }, [action]); + + const closeBottomSheet = useCallback(() => { + action('play', true); + setIsBottomSheetOpen(false); + }, [action]); + + const openCommentSheet = useCallback(() => { + action('pause', true); + setIsOpenCommentSheet(true); + }, [action]); + + const closeCommentSheet = useCallback(() => { + action('play', true); + setIsOpenCommentSheet(false); + }, [action]); + + return { + isBottomSheetOpen, + isOpenCommentSheet, + openBottomSheet, + closeBottomSheet, + openCommentSheet, + closeCommentSheet, + }; +}; export const renderer: CustomRenderer = ({ story: { @@ -42,15 +95,21 @@ export const renderer: CustomRenderer = ({ onClickCommunity, }) => { const [loaded, setLoaded] = useState(false); - const [muted, setMuted] = useState(false); - const [isPaused, setIsPaused] = useState(false); - const [isOpenCommentSheet, setIsOpenCommentSheet] = useState(false); const { loader } = config; const { client } = useSDK(); const { user } = useUser(client?.userId); + const vid = useRef<HTMLVideoElement>(null); - const isLiked = !!(story && story.myReactions && story.myReactions.includes(LIKE_REACTION_KEY)); - const totalLikes = story?.reactions[LIKE_REACTION_KEY] || 0; + const { muted, mute, unmute } = useAudioControl(); + const { isPaused, play, pause } = usePauseControl(action); + const { + isBottomSheetOpen, + isOpenCommentSheet, + openBottomSheet, + closeBottomSheet, + openCommentSheet, + closeCommentSheet, + } = useBottomSheetControl(action); const { storyId, @@ -63,29 +122,16 @@ export const renderer: CustomRenderer = ({ myReactions, data, items, - isBottomSheetOpen, - setIsBottomSheetOpen, } = story as Amity.Story; + const isLiked = story?.myReactions?.includes(LIKE_REACTION_KEY); + const totalLikes = story?.reactions[LIKE_REACTION_KEY] || 0; + const { members } = useCommunityMembersCollection({ - queryParams: { - communityId: community?.communityId as string, - }, + queryParams: { communityId: community?.communityId as string }, shouldCall: !!community?.communityId, }); - const member = members?.find((member) => member.userId === client?.userId); - const isMember = member != null; - - const heading = <div data-qa-anchor="community_display_name">{community?.displayName}</div>; - const subheading = - createdAt && creator?.displayName ? ( - <span> - <span data-qa-anchor="created_at">{formatTimeAgo(createdAt as string)}</span> • By{' '} - <span data-qa-anchor="creator_display_name">{creator?.displayName}</span> - </span> - ) : ( - '' - ); + const isMember = members?.some((member) => member.userId === client?.userId); const isOfficial = community?.isOfficial || false; const isCreator = creator?.userId === user?.userId; @@ -94,71 +140,25 @@ export const renderer: CustomRenderer = ({ const haveStoryPermission = isGlobalAdmin || isModeratorUser || checkStoryPermission(client, community?.communityId); - const vid = useRef<HTMLVideoElement>(null); - - const onWaiting = () => action('pause', true); - const onPlaying = () => action('play', true); - - const videoLoaded = () => { - messageHandler('UPDATE_VIDEO_DURATION', { duration: vid?.current?.duration }); + const videoLoaded = useCallback(() => { + messageHandler('UPDATE_VIDEO_DURATION', { + // TODO: need to fix video type from TS-SDK + // @ts-ignore + duration: story?.videoData?.attributes.metadata.video.duration, + }); setLoaded(true); - vid?.current - ?.play() - .then(() => { - if (isPaused) { - setIsPaused(false); - } - action('play'); - }) - .catch(() => { - setMuted(true); - vid?.current?.play().finally(() => { - action('play'); - }); - }); - }; - - const mute = () => setMuted(true); - const unmute = () => setMuted(false); - - const play = () => { action('play', true); - setIsPaused(false); - }; - const pause = () => { - action('pause', true); - setIsPaused(true); - }; + // TODO: need to fix video type from TS-SDK + // @ts-ignore + }, [messageHandler, story?.videoData?.attributes.metadata.video.duration, action]); - const openBottomSheet = () => { - action('pause', true); - }; - const closeBottomSheet = () => { - action('play', true); - setIsBottomSheetOpen(false); - }; - const openCommentSheet = () => { - action('pause', true); - setIsOpenCommentSheet(true); - }; - const closeCommentSheet = () => { - action('play', true); - setIsOpenCommentSheet(false); - }; - - const targetRootId = 'asc-uikit-stories-viewer'; - - const handleOnClose = () => { - onClose(); - }; - - const handleProgressComplete = () => { + const handleProgressComplete = useCallback(() => { if (currentIndex + 1 < storiesCount) { increaseIndex(); } else { onClose(); } - }; + }, [currentIndex, storiesCount, increaseIndex, onClose]); useEffect(() => { if (vid.current) { @@ -166,51 +166,63 @@ export const renderer: CustomRenderer = ({ vid.current.pause(); action('pause', true); } else { - vid.current.play().catch(() => {}); + vid.current.play(); action('play', true); } } - }, [isPaused, isBottomSheetOpen, isOpenCommentSheet]); + }, [isPaused, isBottomSheetOpen, isOpenCommentSheet, action]); useEffect(() => { if (fileInputRef.current) { - fileInputRef.current.addEventListener('click', () => { + const handleClick = () => { action('pause', true); - setIsPaused(true); - }); - fileInputRef.current.addEventListener('cancel', () => { + pause(); + }; + const handleCancel = () => { action('play', true); - setIsPaused(false); - }); + play(); + }; + + fileInputRef.current.addEventListener('click', handleClick); + fileInputRef.current.addEventListener('cancel', handleCancel); + + return () => { + fileInputRef.current?.removeEventListener('click', handleClick); + fileInputRef.current?.removeEventListener('cancel', handleCancel); + }; } - }, []); + }, [action, pause, play]); useEffect(() => { - if (dragEventTarget) { + if (dragEventTarget?.current) { const handleDragStart = () => { action('pause', true); - setIsPaused(true); + pause(); }; const handleDragEnd = () => { action('play', true); - setIsPaused(false); + play(); }; - dragEventTarget.current?.addEventListener('dragstart', handleDragStart); - dragEventTarget.current?.addEventListener('dragend', handleDragEnd); + dragEventTarget.current.addEventListener('dragstart', handleDragStart); + dragEventTarget.current.addEventListener('dragend', handleDragEnd); return () => { dragEventTarget.current?.removeEventListener('dragstart', handleDragStart); dragEventTarget.current?.removeEventListener('dragend', handleDragEnd); }; } - }, [dragEventTarget]); + }, [action, pause, play, dragEventTarget]); + + // TODO: need to fix video type from TS-SDK + // @ts-ignore + const videoDuration = Math.round(story?.videoData?.attributes.metadata.video.duration * 1000); return ( <div className={clsx(rendererStyles.rendererContainer)}> <StoryProgressBar pageId={pageId} - duration={5000} + duration={videoDuration} currentIndex={currentIndex} storiesCount={storiesCount} isPaused={isPaused || isBottomSheetOpen || isOpenCommentSheet} @@ -224,8 +236,17 @@ export const renderer: CustomRenderer = ({ /> <Header community={community} - heading={heading} - subheading={subheading} + heading={<div data-qa-anchor="community_display_name">{community?.displayName}</div>} + subheading={ + createdAt && creator?.displayName ? ( + <span> + <span data-qa-anchor="created_at">{formatTimeAgo(createdAt as string)}</span> • By{' '} + <span data-qa-anchor="creator_display_name">{creator?.displayName}</span> + </span> + ) : ( + '' + ) + } isHaveActions={actions?.length > 0} haveStoryPermission={haveStoryPermission} isOfficial={isOfficial} @@ -236,7 +257,7 @@ export const renderer: CustomRenderer = ({ onUnmute={unmute} onAction={openBottomSheet} onClickCommunity={() => onClickCommunity?.()} - onClose={handleOnClose} + onClose={onClose} addStoryButton={addStoryButton} /> <video @@ -247,42 +268,42 @@ export const renderer: CustomRenderer = ({ controls={false} onLoadedData={videoLoaded} playsInline - onWaiting={onWaiting} - onPlaying={onPlaying} + onWaiting={() => action('pause', true)} + onPlaying={() => action('play', true)} muted={muted} autoPlay /> {!loaded && ( <div className={clsx(rendererStyles.loadingOverlay)}>{loader || <div>loading...</div>}</div> )} - <BottomSheet - rootId={targetRootId} + rootId="asc-uikit-stories-viewer" isOpen={isBottomSheetOpen} onClose={closeBottomSheet} - mountPoint={document.getElementById(targetRootId) as HTMLElement} + mountPoint={document.getElementById('asc-uikit-stories-viewer') as HTMLElement} detent="content-height" > {actions?.map((bottomSheetAction) => ( <Button + key={bottomSheetAction.name} className={clsx(rendererStyles.actionButton)} - onPress={() => bottomSheetAction?.action()} + onPress={bottomSheetAction?.action} > - {bottomSheetAction?.icon && bottomSheetAction.icon} + {bottomSheetAction?.icon} <Typography.BodyBold>{bottomSheetAction.name}</Typography.BodyBold> </Button> ))} </BottomSheet> <BottomSheet - rootId={targetRootId} + rootId="asc-uikit-stories-viewer" isOpen={isOpenCommentSheet} onClose={closeCommentSheet} - mountPoint={document.getElementById(targetRootId) as HTMLElement} + mountPoint={document.getElementById('asc-uikit-stories-viewer') as HTMLElement} detent="full-height" > <CommentTray referenceId={storyId} - referenceType={'story'} + referenceType="story" community={community as Amity.Community} shouldAllowCreation={community?.allowCommentInStory} shouldAllowInteraction={isMember} @@ -318,7 +339,6 @@ export const renderer: CustomRenderer = ({ isLiked={isLiked} onClickComment={openCommentSheet} myReactions={myReactions} - // Only story-creator and moderator of the community should be able to see impression count. showImpression={isCreator || checkStoryPermission(client, community?.communityId)} isMember={isMember} /> @@ -333,7 +353,4 @@ export const tester: Tester = (story) => { }; }; -export default { - renderer, - tester, -}; +export default { renderer, tester }; diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx index 7c0541898..f740bfabc 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Wrappers/Footer/index.tsx @@ -15,12 +15,12 @@ import styles from './Footer.module.css'; const Footer: React.FC< React.PropsWithChildren<{ pageId?: string; - storyId: string; + storyId?: string; showImpression: boolean; - reach: number | null; + reach?: number | null; commentsCount: number; reactionsCount: number; - isLiked: boolean; + isLiked?: boolean; onClickComment: () => void; syncState?: Amity.SyncState; isMember?: boolean; diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/types.ts b/src/v4/social/internal-components/StoryViewer/Renderers/types.ts index 34bd94b72..a8ae8f62e 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/types.ts +++ b/src/v4/social/internal-components/StoryViewer/Renderers/types.ts @@ -33,8 +33,8 @@ export type CustomStory = Story & { story?: Amity.Story; ad?: Amity.Ad } & { increaseIndex: () => void; pageId?: string; dragEventTarget?: React.RefObject<HTMLElement>; + setIsBottomSheetOpen: React.Dispatch<React.SetStateAction<boolean>>; isBottomSheetOpen: boolean; - setIsBottomSheetOpen: (isOpen: React.SetStateAction<boolean>) => void; }; export type CustomRendererProps = RendererProps & { diff --git a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx index 1beefcf56..86db2327b 100644 --- a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx @@ -49,7 +49,9 @@ interface CommunityFeedStoryProps { }) => void; } -const DURATION = 5000; +const MIN_IMAGE_DURATION = 5000; // 5 seconds +const MAX_IMAGE_DURATION = 10000; // 10 seconds +const DEFAULT_IMAGE_DURATION = 7000; // 7 seconds const isStory = (story: Amity.Story | Amity.Ad): story is Amity.Story => !!(story as Amity.Story)?.storyId; @@ -256,6 +258,8 @@ export const CommunityFeedStory = ({ increaseIndex, pageId, dragEventTarget: dragEventTarget.current, + setIsBottomSheetOpen, + isBottomSheetOpen, }; } else { return { @@ -354,7 +358,7 @@ export const CommunityFeedStory = ({ currentIndex={currentIndex} stories={formattedStories} renderers={communityFeedRenderers as RendererObject[]} - defaultInterval={DURATION} + defaultInterval={DEFAULT_IMAGE_DURATION} onStoryStart={() => isStory(currentStory) && currentStory?.analytics.markAsSeen()} onStoryEnd={nextStory} onNext={nextStory} diff --git a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx index 294dbdfdb..f66899083 100644 --- a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx @@ -25,7 +25,9 @@ import useCommunityStoriesSubscription from '~/v4/social/hooks/useCommunityStori import styles from './StoryPage.module.css'; -const DURATION = 5000; +const MIN_IMAGE_DURATION = 5000; // 5 seconds +const MAX_IMAGE_DURATION = 10000; // 10 seconds +const DEFAULT_IMAGE_DURATION = 7000; // 7 seconds const isStory = (story: Amity.Story | Amity.Ad): story is Amity.Story => !!(story as Amity.Story)?.storyId; @@ -369,7 +371,7 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ currentIndex={currentIndex} stories={formattedStories} renderers={globalFeedRenderers as RendererObject[]} - defaultInterval={DURATION} + defaultInterval={DEFAULT_IMAGE_DURATION} onStoryStart={() => isStory(currentStory) && currentStory?.analytics.markAsSeen()} onStoryEnd={increaseIndex} onNext={nextStory} From 38003e8eefdbf9525a285486c125c7ebe2144dba Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Fri, 12 Jul 2024 19:18:17 +0700 Subject: [PATCH 248/300] fix: ASC-21809 - image local (#535) * fix: story uploading state * fix: remove unnessescary useMeme * fix: remove unused * fix: bottom sheet story * fix: video duration * fix: remove unused * fix: type * fix: done button type * fix: type * fix: ignore type * fix duration * fix: story image bottom sheet * fix: image local --- .../social/internal-components/StoryViewer/Renderers/Image.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx index b583af669..b18a340bf 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/Image.tsx @@ -254,7 +254,7 @@ export const renderer: CustomRenderer = ({ [styles.imageFill]: data.imageDisplayMode === 'fill', })} data-qa-anchor="image_view" - src={url} + src={url ?? (story?.data.fileData as string)} onLoad={imageLoaded} alt="Story Image" crossOrigin="anonymous" From 3547d7e315e4837a00841c649deb89a6dd1f1d33 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Mon, 15 Jul 2024 12:27:59 +0700 Subject: [PATCH 249/300] fix: video ratio (#536) --- .../VideoContent/VideoContent.module.css | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/v4/social/components/PostContent/VideoContent/VideoContent.module.css b/src/v4/social/components/PostContent/VideoContent/VideoContent.module.css index 1f5b8e9d5..acfb3e82e 100644 --- a/src/v4/social/components/PostContent/VideoContent/VideoContent.module.css +++ b/src/v4/social/components/PostContent/VideoContent/VideoContent.module.css @@ -4,6 +4,7 @@ gap: 0.25rem; width: 100%; overflow: hidden; + background-color: var(--asc-color-black); } .videoContent[data-videos-amount='1'] { @@ -34,7 +35,8 @@ .videoContent__video { width: 100%; position: absolute; - top: 0; + top: 50%; + transform: translateY(-50%); margin: auto; } @@ -42,16 +44,6 @@ cursor: pointer; position: relative; width: 100%; - padding-top: 56.25%; -} - -.videoContent__videoContainer[data-videos-amount='1'], -.videoContent__videoContainer[data-videos-amount='2'] { - padding-top: 56.25%; -} - -.videoContent__videoContainer[data-videos-amount='3'], -.videoContent__videoContainer[data-videos-amount='4'] { padding-top: 100%; } From 91bd7531a690f397c2e5fabbf7dfe45a18792746 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Mon, 15 Jul 2024 15:24:35 +0700 Subject: [PATCH 250/300] fix: fix Linkify (#539) --- src/v4/social/internal-components/Linkify/Linkify.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/v4/social/internal-components/Linkify/Linkify.tsx b/src/v4/social/internal-components/Linkify/Linkify.tsx index 45b304668..42963a7b7 100644 --- a/src/v4/social/internal-components/Linkify/Linkify.tsx +++ b/src/v4/social/internal-components/Linkify/Linkify.tsx @@ -7,11 +7,18 @@ type UiKitLinkifyProps = Omit<React.ComponentProps<typeof Linkify>, 'componentDe export const UiKitLinkify = (props: UiKitLinkifyProps) => ( <Linkify + {...props} options={{ render: ({ attributes, content }) => { - const { href, ...props } = attributes; + const { href, ...attrs } = attributes; return ( - <a className={styles.link} target="blank" rel="noopener noreferrer" href={href}> + <a + className={styles.link} + target="blank" + rel="noopener noreferrer" + href={href} + {...attrs} + > {content} </a> ); From 23b1b97547a92ec8564eb40a482936540a8144cf Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Mon, 15 Jul 2024 16:17:29 +0700 Subject: [PATCH 251/300] fix: StoryAd layout (#537) --- .../StoryAd/UIStoryAd.module.css | 7 + .../internal-components/StoryAd/UIStoryAd.tsx | 138 +++++++++--------- .../StoryAdInformation.module.css | 18 ++- .../StoryAdInformation/StoryAdInformation.tsx | 66 +++++---- 4 files changed, 131 insertions(+), 98 deletions(-) diff --git a/src/v4/social/internal-components/StoryAd/UIStoryAd.module.css b/src/v4/social/internal-components/StoryAd/UIStoryAd.module.css index dee56ad71..70d2165e8 100644 --- a/src/v4/social/internal-components/StoryAd/UIStoryAd.module.css +++ b/src/v4/social/internal-components/StoryAd/UIStoryAd.module.css @@ -327,6 +327,13 @@ fill: var(--asc-color-base-shade3); } +.storyAd__main { + inset: 0; + width: 100%; + height: 100%; + position: absolute; +} + .storyAd__topBar { display: grid; align-items: center; diff --git a/src/v4/social/internal-components/StoryAd/UIStoryAd.tsx b/src/v4/social/internal-components/StoryAd/UIStoryAd.tsx index 6e74a7a18..21be56df3 100644 --- a/src/v4/social/internal-components/StoryAd/UIStoryAd.tsx +++ b/src/v4/social/internal-components/StoryAd/UIStoryAd.tsx @@ -69,79 +69,87 @@ export const UIStoryAd = ({ }; return ( - <div className={styles.rendererContainer}> - <StoryProgressBar - pageId={pageId} - duration={5000} - currentIndex={currentIndex} - storiesCount={storiesCount} - isPaused={isPaused} - onComplete={onComplete} - /> + <div className={styles.rendererContainer} ref={targetRef}> + <div className={styles.storyAd__main}> + <StoryProgressBar + pageId={pageId} + duration={5000} + currentIndex={currentIndex} + storiesCount={storiesCount} + isPaused={isPaused} + onComplete={onComplete} + /> - <div className={styles.storyAd__topBar}> - <div className={styles.storyAd__topBar__left}> - <div className={styles.storyAd__topBar__avatar}> - <Avatar defaultImage={<Broadcast />} avatarUrl={avatarUrl} /> + <div className={styles.storyAd__topBar}> + <div className={styles.storyAd__topBar__left}> + <div className={styles.storyAd__topBar__avatar}> + <Avatar defaultImage={<Broadcast />} avatarUrl={avatarUrl} /> + </div> + <div className={styles.storyAd__topBar__text}> + <Typography.BodyBold className={styles.storyAd__topBar__advertiserName}> + {ad.advertiser?.name} + </Typography.BodyBold> + <AdsBadge /> + </div> </div> - <div className={styles.storyAd__topBar__text}> - <Typography.BodyBold className={styles.storyAd__topBar__advertiserName}> - {ad.advertiser?.name} - </Typography.BodyBold> - <AdsBadge /> + <div className={styles.storyAd__topBar__right}> + {isPaused ? ( + <PlayIcon + className={styles.playStoryButton} + data-qa-anchor="play_button" + onClick={() => onPlayClick?.()} + /> + ) : ( + <PauseIcon + className={styles.pauseStoryButton} + data-qa-anchor="pause_button" + onClick={() => onPauseClick?.()} + /> + )} + <CloseButton + defaultClassName={clsx(styles.storyAd__closeButton)} + pageId={pageId} + onPress={onClose} + /> </div> </div> - <div className={styles.storyAd__topBar__right}> - {isPaused ? ( - <PlayIcon - className={styles.playStoryButton} - data-qa-anchor="play_button" - onClick={() => onPlayClick?.()} - /> - ) : ( - <PauseIcon - className={styles.pauseStoryButton} - data-qa-anchor="pause_button" - onClick={() => onPauseClick?.()} - /> - )} - <CloseButton - defaultClassName={clsx(styles.storyAd__closeButton)} - pageId={pageId} - onPress={onClose} + + <div className={clsx(styles.storyImageContainer)}> + <img + ref={imageRef} + className={clsx(styles.storyImage)} + data-qa-anchor="image_view" + src={adImageUrl} + onLoad={handleImageLoaded} + alt="Story Image" + crossOrigin="anonymous" /> </div> - </div> - <div className={clsx(styles.storyImageContainer)}> - <img - ref={imageRef} - className={clsx(styles.storyImage)} - data-qa-anchor="image_view" - src={adImageUrl} - onLoad={handleImageLoaded} - alt="Story Image" - crossOrigin="anonymous" - /> - </div> - - {!isImageLoaded && <div className={styles.loadingOverlay}>{renderLoader()}</div>} + {!isImageLoaded && <div className={styles.loadingOverlay}>{renderLoader()}</div>} - {ad.callToActionUrl && ( - <div className={styles.hyperLinkContainer}> - <a - className={styles.hyperLink__text} - href={ad.callToActionUrl} - target="_blank" - rel="noreferrer" - > - {ad.callToAction} - </a> - </div> - )} - <Button className={styles.infoIcon__button} onPress={() => openAdvertisementInfo()}> - <InfoCircle className={styles.infoIcon} /> - </Button> + {ad.callToActionUrl && ( + <div className={styles.hyperLinkContainer}> + <a + className={styles.hyperLink__text} + href={ad.callToActionUrl} + target="_blank" + rel="noreferrer" + > + {ad.callToAction} + </a> + </div> + )} + <Button + className={styles.infoIcon__button} + onPress={() => { + console.log('openAdvertisementInfo'); + openAdvertisementInfo(); + }} + > + <InfoCircle className={styles.infoIcon} /> + </Button> + </div> <StoryAdInformation ad={ad} isOpen={isAdvertisementInfoOpen} diff --git a/src/v4/social/internal-components/StoryAdInformation/StoryAdInformation.module.css b/src/v4/social/internal-components/StoryAdInformation/StoryAdInformation.module.css index 74e1dcc95..f5f59aea8 100644 --- a/src/v4/social/internal-components/StoryAdInformation/StoryAdInformation.module.css +++ b/src/v4/social/internal-components/StoryAdInformation/StoryAdInformation.module.css @@ -1,7 +1,7 @@ .drawer__content { padding-top: 1rem; background-color: var(--asc-color-background-default); - max-height: 70%; + max-height: 50%; position: absolute; bottom: 0; left: 0; @@ -79,7 +79,21 @@ } .drawer__content__emptySpace { - min-height: 15rem; + min-height: 5rem; width: 100%; background-color: var(--asc-color-background-default); } + +.drawer__container { + position: absolute; + inset: 0; + height: 100%; + width: 100%; + overflow: hidden; +} + +.drawer__innerContainer { + position: relative; + height: 100%; + width: 100%; +} diff --git a/src/v4/social/internal-components/StoryAdInformation/StoryAdInformation.tsx b/src/v4/social/internal-components/StoryAdInformation/StoryAdInformation.tsx index 6cb95d249..3a7f4ddd8 100644 --- a/src/v4/social/internal-components/StoryAdInformation/StoryAdInformation.tsx +++ b/src/v4/social/internal-components/StoryAdInformation/StoryAdInformation.tsx @@ -28,39 +28,43 @@ export const StoryAdInformation = ({ }} > <Drawer.Portal container={targetRef?.current}> - <Drawer.Overlay className={styles.drawer__overlay} /> - <Drawer.Content className={styles.drawer__content}> - <div className={styles.drawer__innerContent}> - <div className={styles.drawer__placeholder} /> - <Drawer.Title className={styles.drawer__title}> - <Typography.Title>About this advertisement</Typography.Title> - </Drawer.Title> - <div className={styles.drawer__content__data}> - <Typography.BodyBold className={styles.drawer__content__data__title}> - Why this advertisement? - </Typography.BodyBold> - <div className={styles.drawer__content__data__text}> - <InfoCircle className={styles.drawer__content__data__infoIcon} /> - <Typography.Caption className={styles.drawer__content__data__caption}> - You're seeing this advertisement because it was displayed to all users in the - system. - </Typography.Caption> + <div className={styles.drawer__container}> + <div className={styles.drawer__innerContainer}> + <Drawer.Overlay className={styles.drawer__overlay} /> + <Drawer.Content className={styles.drawer__content}> + <div className={styles.drawer__innerContent}> + <div className={styles.drawer__placeholder} /> + <Drawer.Title className={styles.drawer__title}> + <Typography.Title>About this advertisement</Typography.Title> + </Drawer.Title> + <div className={styles.drawer__content__data}> + <Typography.BodyBold className={styles.drawer__content__data__title}> + Why this advertisement? + </Typography.BodyBold> + <div className={styles.drawer__content__data__text}> + <InfoCircle className={styles.drawer__content__data__infoIcon} /> + <Typography.Caption className={styles.drawer__content__data__caption}> + You're seeing this advertisement because it was displayed to all users in the + system. + </Typography.Caption> + </div> + </div> + <div className={styles.drawer__content__data}> + <Typography.BodyBold className={styles.drawer__content__data__title}> + About this advertiser + </Typography.BodyBold> + <div className={styles.drawer__content__data__text}> + <InfoCircle className={styles.drawer__content__data__infoIcon} /> + <Typography.Caption className={styles.drawer__content__data__caption}> + Advertiser name: {ad.advertiser?.companyName} + </Typography.Caption> + </div> + </div> </div> - </div> - <div className={styles.drawer__content__data}> - <Typography.BodyBold className={styles.drawer__content__data__title}> - About this advertiser - </Typography.BodyBold> - <div className={styles.drawer__content__data__text}> - <InfoCircle className={styles.drawer__content__data__infoIcon} /> - <Typography.Caption className={styles.drawer__content__data__caption}> - Advertiser name: {ad.advertiser?.companyName} - </Typography.Caption> - </div> - </div> + <div className={styles.drawer__content__emptySpace}></div> + </Drawer.Content> </div> - <div className={styles.drawer__content__emptySpace}></div> - </Drawer.Content> + </div> </Drawer.Portal> </Drawer.Root> ); From 21cf1819aaa615898e9d73ef6297c9c497022575 Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Mon, 15 Jul 2024 18:24:36 +0700 Subject: [PATCH 252/300] feat: combine v3 code (#541) --- src/v4/core/natives/Button/Button.module.css | 1 + src/v4/core/providers/NavigationProvider.tsx | 32 ++++++++++++++++--- .../CommunitySearchResult/CommunityItem.tsx | 13 ++++++-- .../components/PostContent/PostContent.tsx | 25 +++++++++++---- .../TopNavigation/TopNavigation.tsx | 12 ++++--- src/v4/social/pages/Application/index.tsx | 27 +++++++++++++++- .../pages/SocialHomePage/SocialHomePage.tsx | 11 +++++-- 7 files changed, 99 insertions(+), 22 deletions(-) diff --git a/src/v4/core/natives/Button/Button.module.css b/src/v4/core/natives/Button/Button.module.css index 257690dee..b2f924073 100644 --- a/src/v4/core/natives/Button/Button.module.css +++ b/src/v4/core/natives/Button/Button.module.css @@ -1,4 +1,5 @@ .button { box-sizing: border-box; all: unset; + cursor: pointer; } diff --git a/src/v4/core/providers/NavigationProvider.tsx b/src/v4/core/providers/NavigationProvider.tsx index 633d5cdc5..95730fe3e 100644 --- a/src/v4/core/providers/NavigationProvider.tsx +++ b/src/v4/core/providers/NavigationProvider.tsx @@ -253,7 +253,16 @@ interface NavigationProviderProps { onCancel: () => void; }) => void; children: React.ReactNode; - onChangePage?: (data: { type: string; [x: string]: string | boolean }) => void; + onChangePage?: ( + data: + | { type: string; [x: string]: string | boolean } + | { + type: string; + context: { + [x: string]: string | boolean; + }; + }, + ) => void; onClickCategory?: (categoryId: string) => void; onClickCommunity?: (communityId: string) => void; onClickUser?: (userId: string) => void; @@ -313,7 +322,16 @@ export default function NavigationProvider({ }; const onChangePage = onChangePageProp - ? async (data: { type: string; [x: string]: string | boolean }) => { + ? async ( + data: + | { type: string; [x: string]: string | boolean } + | { + type: string; + context: { + [x: string]: string | boolean; + }; + }, + ) => { onChangePageProp(data); } : null; @@ -329,7 +347,9 @@ export default function NavigationProvider({ (communityId) => { const next = { type: PageTypes.CommunityFeed, - communityId, + context: { + communityId, + }, }; if (onChangePage) return onChangePage(next); @@ -337,7 +357,7 @@ export default function NavigationProvider({ pushPage(next); }, - [onChangePage, onClickCommunity, pushPage], + [onClickCommunity, pushPage], ); const handleCommunityCreated = useCallback( @@ -375,7 +395,9 @@ export default function NavigationProvider({ (userId, pageType) => { const next = { type: pageType ?? PageTypes.UserFeed, - userId, + context: { + userId, + }, }; if (onChangePage) return onChangePage(next); diff --git a/src/v4/social/components/CommunitySearchResult/CommunityItem.tsx b/src/v4/social/components/CommunitySearchResult/CommunityItem.tsx index 420ccf68a..c54212adb 100644 --- a/src/v4/social/components/CommunitySearchResult/CommunityItem.tsx +++ b/src/v4/social/components/CommunitySearchResult/CommunityItem.tsx @@ -7,6 +7,8 @@ import { CommunityPrivateBadge } from '~/v4/social/elements/CommunityPrivateBadg import { CommunityCategoryName } from '~/v4/social/elements/CommunityCategoryName'; import { CommunityMembersCount } from '~/v4/social/elements/CommunityMembersCount'; import styles from './CommunityItem.module.css'; +import { Button } from '~/v4/core/natives/Button'; +import { useNavigation } from '~/v4/core/providers/NavigationProvider'; const CommunityCategories = ({ community, @@ -52,8 +54,15 @@ export const CommunityItem = ({ pageId: string; componentId: string; }) => { + const { onClickCommunity } = useNavigation(); return ( - <div key={community.communityId} className={styles.communityItem}> + <Button + key={community.communityId} + className={styles.communityItem} + onPress={() => { + onClickCommunity(community.communityId); + }} + > <div className={styles.communityItem__leftPane}> <CommunityAvatar pageId={pageId} componentId={componentId} community={community} /> </div> @@ -80,6 +89,6 @@ export const CommunityItem = ({ /> </div> </div> - </div> + </Button> ); }; diff --git a/src/v4/social/components/PostContent/PostContent.tsx b/src/v4/social/components/PostContent/PostContent.tsx index c2d7795c3..5f5950a8c 100644 --- a/src/v4/social/components/PostContent/PostContent.tsx +++ b/src/v4/social/components/PostContent/PostContent.tsx @@ -34,6 +34,8 @@ import usePostSubscription from '~/v4/core/hooks/subscriptions/usePostSubscripti import { ReactionList } from '../index'; import { usePostedUserInformation } from '~/v4/core/hooks/usePostedUserInformation'; import millify from 'millify'; +import { Button } from '~/v4/core/natives/Button'; +import { useNavigation } from '~/v4/core/providers/NavigationProvider'; interface PostTitleProps { post: Amity.Post; @@ -49,19 +51,30 @@ const PostTitle = ({ pageId, post }: PostTitleProps) => { }); const { user: postedUser } = useUser(post.postedUserId); + const { onClickCommunity, onClickUser } = useNavigation(); if (targetCommunity) { return ( <div className={styles.postTitle}> - <Typography.BodyBold className={styles.postTitle__text}> - {postedUser?.displayName} - </Typography.BodyBold> + {postedUser && ( + <Button onPress={() => onClickUser(postedUser?.userId)}> + <Typography.BodyBold className={styles.postTitle__text}> + {postedUser?.displayName} + </Typography.BodyBold> + </Button> + )} {targetCommunity && ( <> <AngleRight className={styles.postTitle__icon} /> - <Typography.BodyBold className={styles.postTitle__text}> - {targetCommunity.displayName} - </Typography.BodyBold>{' '} + <Button + onPress={() => { + onClickCommunity(targetCommunity.communityId); + }} + > + <Typography.BodyBold className={styles.postTitle__text}> + {targetCommunity.displayName} + </Typography.BodyBold> + </Button> </> )} </div> diff --git a/src/v4/social/components/TopNavigation/TopNavigation.tsx b/src/v4/social/components/TopNavigation/TopNavigation.tsx index 2840c9dca..a2f4cd95d 100644 --- a/src/v4/social/components/TopNavigation/TopNavigation.tsx +++ b/src/v4/social/components/TopNavigation/TopNavigation.tsx @@ -51,11 +51,13 @@ export function TopNavigation({ componentId={componentId} onPress={handleGlobalSearchClick} /> - <PostCreationButton - pageId={pageId} - componentId={componentId} - onClick={onClickPostCreationButton} - /> + {selectedTab !== HomePageTab.Explore && ( + <PostCreationButton + pageId={pageId} + componentId={componentId} + onClick={onClickPostCreationButton} + /> + )} </div> </div> ); diff --git a/src/v4/social/pages/Application/index.tsx b/src/v4/social/pages/Application/index.tsx index 6afaecd33..71cbb9516 100644 --- a/src/v4/social/pages/Application/index.tsx +++ b/src/v4/social/pages/Application/index.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import { SocialHomePage } from '~/v4/social/pages/SocialHomePage'; import { PostComposerPage } from '~/v4/social/pages/PostComposerPage'; @@ -13,10 +13,21 @@ import { MyCommunitiesSearchPage } from '../MyCommunitiesSearchPage/MyCommunitie import styles from './Application.module.css'; import { AmityDraftStoryPage } from '..'; import { StoryTargetSelectionPage } from '~/v4/social/pages/StoryTargetSelectionPage'; +import CommunityFeed from '~/social/pages/CommunityFeed'; +import UserFeedPage from '~/social/pages/UserFeed'; + const Application = () => { const { page } = useNavigation(); + const [open, setOpen] = useState(false); + const [socialSettings, setSocialSettings] = useState<Amity.SocialSettings | null>(null); + + + const toggleOpen = () => { + setOpen(!open); + }; + return ( <StoryProvider> <div className={styles.applicationContainer}> @@ -45,6 +56,20 @@ const Application = () => { )} {page.type === PageTypes.SelectPostTargetPage && <SelectPostTargetPage />} {page.type === PageTypes.MyCommunitiesSearchPage && <MyCommunitiesSearchPage />} + {/* V3 */} + {page.type === PageTypes.Explore} + {page.type === PageTypes.CommunityFeed && ( + <CommunityFeed + communityId={page.context.communityId} + isNewCommunity={page.context.isNewCommunity} + isOpen={open} + toggleOpen={toggleOpen} + /> + )} + {page.type === PageTypes.UserFeed && ( + <UserFeedPage userId={page.context.userId} socialSettings={socialSettings} /> + )} + {/*End of V3 */} </div> </StoryProvider> ); diff --git a/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx b/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx index 8857d4840..de3bfa5aa 100644 --- a/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx +++ b/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx @@ -10,6 +10,7 @@ import { Newsfeed } from '~/v4/social/components/Newsfeed'; import { useAmityPage } from '~/v4/core/hooks/uikit'; import { CreatePostMenu } from '~/v4/social/components/CreatePostMenu'; import { useGlobalFeedContext } from '../../providers/GlobalFeedProvider'; +import ExplorePage from '~/social/pages/Explore'; export enum HomePageTab { Newsfeed = 'Newsfeed', @@ -78,7 +79,11 @@ export function SocialHomePage() { isActive={activeTab === HomePageTab.Newsfeed} onClick={() => setActiveTab(HomePageTab.Newsfeed)} /> - <ExploreButton pageId={pageId} isActive={activeTab === HomePageTab.Explore} /> + <ExploreButton + pageId={pageId} + isActive={activeTab === HomePageTab.Explore} + onClick={() => setActiveTab(HomePageTab.Explore)} + /> <MyCommunitiesButton pageId={pageId} isActive={activeTab === HomePageTab.MyCommunities} @@ -87,8 +92,8 @@ export function SocialHomePage() { </div> </div> <div className={styles.socialHomePage__contents} ref={containerRef} onScroll={handleScroll}> - {activeTab === HomePageTab.Newsfeed && <Newsfeed pageId={pageId} />} - {activeTab === HomePageTab.Explore && <div>Explore</div>} + {activeTab === HomePageTab.Newsfeed && <Newsfeed pageId={pageId} />} + {activeTab === HomePageTab.Explore && <ExplorePage />} {activeTab === HomePageTab.MyCommunities && <MyCommunities pageId={pageId} />} </div> {isShowCreatePostMenu && ( From 40c6654c322c7dd8d96347bc3a6aaac0f03e0056 Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Mon, 15 Jul 2024 18:27:52 +0700 Subject: [PATCH 253/300] refactor: ASC-00000 - upload media (#540) * refactor: upload media * fix: spelling * refactor: props name * fix: props name * refactor: props name --- .../DetailedMediaAttachment.tsx | 33 ++--- .../MediaAttachment/MediaAttachment.tsx | 29 ++-- .../elements/CameraButton/CameraButton.tsx | 139 ++++-------------- .../elements/ImageButton/ImageButton.tsx | 40 +---- .../elements/VideoButton/VideoButton.tsx | 72 +-------- src/v4/social/hooks/useFileUpload.ts | 4 +- .../ImageThumbnail/ImageThumbnail.tsx | 3 - .../MentionTextInput/MentionTextInput.tsx | 4 +- .../PostComposerPage/PostComposerPage.tsx | 80 +++++++--- src/v4/social/utils/generateThumbnailVideo.ts | 27 ++++ 10 files changed, 158 insertions(+), 273 deletions(-) create mode 100644 src/v4/social/utils/generateThumbnailVideo.ts diff --git a/src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.tsx b/src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.tsx index 9c0b66042..4db09e0f9 100644 --- a/src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.tsx +++ b/src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.tsx @@ -4,32 +4,23 @@ import styles from './DetailedMediaAttachment.module.css'; import { CameraButton } from '~/v4/social/elements/CameraButton'; import { ImageButton } from '~/v4/social/elements/ImageButton/ImageButton'; import { VideoButton } from '~/v4/social/elements/VideoButton/VideoButton'; -import { FileButton } from '~/v4/social/elements/FileButton'; interface DetailedMediaAttachmentProps { pageId: string; - uploadLoading?: boolean; - onChangeImages?: (files: File[]) => void; - onChangeVideos?: (files: File[]) => void; - onChangeThumbnail?: ( - thumbnail: { file: File; videoUrl: string; thumbnail: string | undefined }[], - ) => void; isVisibleCamera: boolean; isVisibleImage: boolean; isVisibleVideo: boolean; - videoThumbnail?: { file: File; videoUrl: string; thumbnail: string | undefined }[]; + onVideoFileChange?: (files: File[]) => void; + onImageFileChange?: (files: File[]) => void; } export function DetailedMediaAttachment({ pageId, - uploadLoading, - onChangeImages, - onChangeVideos, - onChangeThumbnail, isVisibleCamera, isVisibleImage, isVisibleVideo, - videoThumbnail, + onVideoFileChange, + onImageFileChange, }: DetailedMediaAttachmentProps) { const componentId = 'detailed_media_attachment'; const { themeStyles, accessibilityId, isExcluded } = useAmityComponent({ pageId, componentId }); @@ -47,25 +38,25 @@ export function DetailedMediaAttachment({ <CameraButton pageId={pageId} componentId={componentId} - onChange={onChangeImages} isVisibleImage={isVisibleImage} isVisibleVideo={isVisibleVideo} - onChangeVideos={onChangeVideos} - videoThumbnail={videoThumbnail} - onChangeThumbnail={onChangeThumbnail} + onVideoFileChange={onVideoFileChange} + onImageFileChange={onImageFileChange} /> )} {isVisibleImage && ( - <ImageButton pageId={pageId} componentId={componentId} onChange={onChangeImages} /> + <ImageButton + pageId={pageId} + componentId={componentId} + onImageFileChange={onImageFileChange} + /> )} {isVisibleVideo && ( <VideoButton pageId={pageId} componentId={componentId} - onChangeVideos={onChangeVideos} - onChangeThumbnail={onChangeThumbnail} - videoThumbnail={videoThumbnail} + onVideoFileChange={onVideoFileChange} /> )} </div> diff --git a/src/v4/social/components/MediaAttachment/MediaAttachment.tsx b/src/v4/social/components/MediaAttachment/MediaAttachment.tsx index 402fc47e2..396306d1b 100644 --- a/src/v4/social/components/MediaAttachment/MediaAttachment.tsx +++ b/src/v4/social/components/MediaAttachment/MediaAttachment.tsx @@ -9,25 +9,20 @@ import clsx from 'clsx'; interface MediaAttachmentProps { pageId: string; uploadLoading?: boolean; - onChangeImages?: (files: File[]) => void; - onChangeVideos?: (files: File[]) => void; - onChangeThumbnail?: (thumbnail: { file: File; videoUrl: string; thumbnail: string | undefined }[]) => void; isVisibleCamera: boolean; isVisibleImage: boolean; isVisibleVideo: boolean; - videoThumbnail?: { file: File; videoUrl: string; thumbnail: string | undefined }[]; + onVideoFileChange?: (files: File[]) => void; + onImageFileChange?: (files: File[]) => void; } export function MediaAttachment({ pageId, - uploadLoading, - onChangeImages, - onChangeVideos, - onChangeThumbnail, isVisibleCamera, isVisibleImage, isVisibleVideo, - videoThumbnail, + onVideoFileChange, + onImageFileChange, }: MediaAttachmentProps) { const componentId = 'media_attachment'; const { themeStyles, accessibilityId, isExcluded } = useAmityComponent({ pageId, componentId }); @@ -48,25 +43,25 @@ export function MediaAttachment({ <CameraButton pageId={pageId} componentId={componentId} - onChange={onChangeImages} isVisibleImage={isVisibleImage} isVisibleVideo={isVisibleVideo} - onChangeVideos={onChangeVideos} - videoThumbnail={videoThumbnail} - onChangeThumbnail={onChangeThumbnail} + onVideoFileChange={onVideoFileChange} + onImageFileChange={onImageFileChange} /> )} {isVisibleImage && ( - <ImageButton pageId={pageId} componentId={componentId} onChange={onChangeImages} /> + <ImageButton + pageId={pageId} + componentId={componentId} + onImageFileChange={onImageFileChange} + /> )} {isVisibleVideo && ( <VideoButton pageId={pageId} componentId={componentId} - onChangeVideos={onChangeVideos} - onChangeThumbnail={onChangeThumbnail} - videoThumbnail={videoThumbnail} + onVideoFileChange={onVideoFileChange} /> )} </div> diff --git a/src/v4/social/elements/CameraButton/CameraButton.tsx b/src/v4/social/elements/CameraButton/CameraButton.tsx index 9023cddfe..1dca24b79 100644 --- a/src/v4/social/elements/CameraButton/CameraButton.tsx +++ b/src/v4/social/elements/CameraButton/CameraButton.tsx @@ -1,10 +1,9 @@ import React, { useCallback, useState } from 'react'; import { IconComponent } from '~/v4/core/IconComponent'; -import { Typography } from '~/v4/core/components'; +import { Typography } from '~/v4/core/components'; import { useAmityElement } from '~/v4/core/hooks/uikit'; import styles from './CameraButton.module.css'; import clsx from 'clsx'; -import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; import { Button } from '~/v4/core/natives/Button'; interface CameraButtonProps { @@ -12,14 +11,10 @@ interface CameraButtonProps { componentId?: string; imgIconClassName?: string; defaultIconClassName?: string; - onChange?: (files: File[]) => void; isVisibleImage?: boolean; isVisibleVideo?: boolean; - onChangeVideos?: (files: File[]) => void; - onChangeThumbnail?: ( - thumbnail: { file: File; videoUrl: string; thumbnail: string | undefined }[], - ) => void; - videoThumbnail?: { file: File; videoUrl: string; thumbnail: string | undefined }[]; + onVideoFileChange?: (files: File[]) => void; + onImageFileChange?: (files: File[]) => void; } const CameraSvg = (props: React.SVGProps<SVGSVGElement>) => { @@ -55,128 +50,44 @@ export function CameraButton({ componentId = '*', imgIconClassName, defaultIconClassName, - onChange, isVisibleImage, isVisibleVideo, - onChangeVideos, - videoThumbnail, - onChangeThumbnail, + onVideoFileChange, + onImageFileChange, }: CameraButtonProps) { const elementId = 'camera_button'; const { themeStyles, isExcluded, config, accessibilityId, uiReference, defaultConfig } = useAmityElement({ pageId, componentId, elementId }); if (isExcluded) return null; - const [uploadedImages, setUploadedImages] = useState<File[]>([]); - - const { confirm } = useConfirmContext(); const triggerFileInput = () => { const fileInput = document.getElementById('upload') as HTMLInputElement; fileInput.click(); }; - const onLoadVideo: React.ChangeEventHandler<HTMLInputElement> = useCallback( - (e) => { - e.preventDefault(); - e.stopPropagation(); - const targetFiles = e.target.files ? [...e.target.files] : []; - - const existingVideosCount = videoThumbnail ? videoThumbnail.length : 0; - - if (targetFiles.length + existingVideosCount > 10) { - confirm({ - pageId: pageId, - type: 'info', - title: 'Maximum upload limit reached', - content: - 'You’ve reached the upload limit of 10 videos. Any additional videos will not be saved. ', - okText: 'Close', - }); - return; - } - - if (targetFiles.length) { - onChangeVideos?.(targetFiles); - const updatedVideos = targetFiles.map((file) => ({ - file, - videoUrl: URL.createObjectURL(file), - thumbnail: null, - })); - onChangeThumbnail?.((prevVideos) => [...prevVideos, ...updatedVideos]); - videoThumbnail && - updatedVideos.forEach((video, index) => - generateThumbnail(video.file, index + videoThumbnail.length), - ); - } - }, - [onChangeVideos, videoThumbnail?.length, onChangeThumbnail], - ); - - const onLoadImage: React.ChangeEventHandler<HTMLInputElement> = useCallback( - (e) => { - e.preventDefault(); - e.stopPropagation(); - const targetFiles = e.target.files ? [...e.target.files] : []; + const onLoadMedia: React.ChangeEventHandler<HTMLInputElement> = useCallback((e) => { + e.preventDefault(); + e.stopPropagation(); + const targetFiles = e.target.files ? [...e.target.files] : []; + const isImage = targetFiles.some((file) => file.type.startsWith('image/')); + const isVideo = targetFiles.some((file) => file.type.startsWith('video/')); - if (targetFiles.length + uploadedImages.length > 10) { - confirm({ - pageId: pageId, - type: 'info', - title: 'Maximum upload limit reached', - content: - 'You’ve reached the upload limit of 10 images. Any additional images will not be saved. ', - okText: 'Close', - }); - return; - } + if (isImage) { + onImageFileChange?.(e.target.files ? [...e.target.files] : []); + } else if (isVideo) { + onVideoFileChange?.(e.target.files ? [...e.target.files] : []); + } + }, []); - if (targetFiles.length) { - setUploadedImages((prevImages) => [...prevImages, ...targetFiles]); - onChange?.(targetFiles); - } - }, - [onChange], - ); - - const onLoadMedia: React.ChangeEventHandler<HTMLInputElement> = useCallback( - (e) => { - e.preventDefault(); - e.stopPropagation(); - const targetFiles = e.target.files ? [...e.target.files] : []; - const isImage = targetFiles.some((file) => file.type.startsWith('image/')); - const isVideo = targetFiles.some((file) => file.type.startsWith('video/')); - - if (isImage) { - onLoadImage(e); - } else if (isVideo) { - onLoadVideo(e); - } - }, - [onLoadImage, onLoadVideo], - ); - - const generateThumbnail = (file: File, index: number) => { - const videoElement = document.createElement('video'); - const canvasElement = document.createElement('canvas'); - const context = canvasElement.getContext('2d'); + const onImageChange: React.ChangeEventHandler<HTMLInputElement> = useCallback((e) => { + onImageFileChange?.(e.target.files ? [...e.target.files] : []); + }, []); - videoElement.src = URL.createObjectURL(file); - videoElement.currentTime = 10; // Seek to 10 seconds (you can adjust this) + const onVideoChange: React.ChangeEventHandler<HTMLInputElement> = useCallback((e) => { + onImageFileChange?.(e.target.files ? [...e.target.files] : []); + }, []); - videoElement.addEventListener('loadeddata', () => { - videoElement.pause(); - canvasElement.width = videoElement.videoWidth; - canvasElement.height = videoElement.videoHeight; - context?.drawImage(videoElement, 0, 0, canvasElement.width, canvasElement.height); - const thumbnail = canvasElement.toDataURL('image/png'); - onChangeThumbnail?.((prevVideos) => { - const newVideos = [...prevVideos]; - newVideos[index].thumbnail = thumbnail; - return newVideos; - }); - }); - }; return ( <Button style={themeStyles} @@ -197,7 +108,7 @@ export function CameraButton({ {isVisibleImage && !isVisibleVideo && ( <input type="file" - onChange={onLoadImage} + onChange={onImageChange} id="upload" accept="image/*" className={styles.cameraButton_input} @@ -207,7 +118,7 @@ export function CameraButton({ {!isVisibleImage && isVisibleVideo && ( <input type="file" - onChange={onLoadVideo} + onChange={onVideoChange} id="upload" accept="video/*" className={styles.cameraButton_input} diff --git a/src/v4/social/elements/ImageButton/ImageButton.tsx b/src/v4/social/elements/ImageButton/ImageButton.tsx index 7079ec33e..8ffffb2db 100644 --- a/src/v4/social/elements/ImageButton/ImageButton.tsx +++ b/src/v4/social/elements/ImageButton/ImageButton.tsx @@ -12,7 +12,7 @@ interface ImageButtonProps { componentId?: string; imgIconClassName?: string; defaultIconClassName?: string; - onChange?: (files: File[]) => void; + onImageFileChange?: (files: File[]) => void; } const ImageButtonSvg = (props: React.SVGProps<SVGSVGElement>) => { @@ -49,16 +49,12 @@ export function ImageButton({ componentId = '*', imgIconClassName, defaultIconClassName, - onChange, + onImageFileChange, }: ImageButtonProps) { const elementId = 'image_button'; const { themeStyles, isExcluded, config, accessibilityId, uiReference, defaultConfig } = useAmityElement({ pageId, componentId, elementId }); - const [uploadedImages, setUploadedImages] = useState<File[]>([]); - - const { confirm } = useConfirmContext(); - if (isExcluded) return null; const triggerFileInput = () => { @@ -66,31 +62,11 @@ export function ImageButton({ fileInput.click(); }; - const onLoad: React.ChangeEventHandler<HTMLInputElement> = useCallback( - (e) => { - e.preventDefault(); - e.stopPropagation(); - const targetFiles = e.target.files ? [...e.target.files] : []; - - if (targetFiles.length + uploadedImages.length > 10) { - confirm({ - pageId: pageId, - type: 'info', - title: 'Maximum upload limit reached', - content: - 'You’ve reached the upload limit of 10 images. Any additional images will not be saved. ', - okText: 'Close', - }); - return; - } - - if (targetFiles.length) { - setUploadedImages((prevImages) => [...prevImages, ...targetFiles]); - onChange?.(targetFiles); - } - }, - [onChange], - ); + const onChangeImage = (e: React.ChangeEvent<HTMLInputElement>) => { + e.preventDefault(); + e.stopPropagation(); + onImageFileChange?.(e.target.files ? [...e.target.files] : []); + }; return ( <Button @@ -111,7 +87,7 @@ export function ImageButton({ {config.text && <Typography.BodyBold>{config.text}</Typography.BodyBold>} <input type="file" - onChange={onLoad} + onChange={onChangeImage} multiple id="image-upload" accept="image/*" diff --git a/src/v4/social/elements/VideoButton/VideoButton.tsx b/src/v4/social/elements/VideoButton/VideoButton.tsx index 85c7ef6e6..c52da7cc8 100644 --- a/src/v4/social/elements/VideoButton/VideoButton.tsx +++ b/src/v4/social/elements/VideoButton/VideoButton.tsx @@ -12,11 +12,7 @@ interface VideoButtonProps { componentId?: string; imgIconClassName?: string; defaultIconClassName?: string; - onChangeVideos?: (files: File[]) => void; - onChangeThumbnail?: ( - thumbnail: { file: File; videoUrl: string; thumbnail: string | undefined }[], - ) => void; - videoThumbnail?: { file: File; videoUrl: string; thumbnail: string | undefined }[]; + onVideoFileChange?: (files: File[]) => void; } const VideoButtonSvg = (props: React.SVGProps<SVGSVGElement>) => { @@ -51,9 +47,7 @@ export function VideoButton({ componentId = '*', imgIconClassName, defaultIconClassName, - onChangeVideos, - onChangeThumbnail, - videoThumbnail, + onVideoFileChange, }: VideoButtonProps) { const elementId = 'video_button'; const { themeStyles, isExcluded, config, accessibilityId, uiReference, defaultConfig } = @@ -68,62 +62,10 @@ export function VideoButton({ fileInput.click(); }; - const onLoad: React.ChangeEventHandler<HTMLInputElement> = useCallback( - (e) => { - e.preventDefault(); - e.stopPropagation(); - const targetFiles = e.target.files ? [...e.target.files] : []; - const existingVideosCount = videoThumbnail ? videoThumbnail.length : 0; - - if (targetFiles.length + existingVideosCount > 10) { - confirm({ - pageId: pageId, - type: 'info', - title: 'Maximum upload limit reached', - content: - 'You’ve reached the upload limit of 10 videos. Any additional videos will not be saved. ', - okText: 'Close', - }); - return; - } - - if (targetFiles.length) { - onChangeVideos?.(targetFiles); - const updatedVideos = targetFiles.map((file) => ({ - file, - videoUrl: URL.createObjectURL(file), - thumbnail: null, - })); - onChangeThumbnail?.((prevVideo) => [...prevVideo, ...updatedVideos]); - videoThumbnail && - updatedVideos.forEach((video, index) => - generateThumbnail(video.file, index + videoThumbnail.length), - ); - } - }, - [onChangeVideos, videoThumbnail?.length, onChangeThumbnail], - ); - - const generateThumbnail = (file: File, index: number) => { - const videoElement = document.createElement('video'); - const canvasElement = document.createElement('canvas'); - const context = canvasElement.getContext('2d'); - - videoElement.src = URL.createObjectURL(file); - videoElement.currentTime = 10; // Seek to 10 seconds (you can adjust this) - - videoElement.addEventListener('loadeddata', () => { - videoElement.pause(); - canvasElement.width = videoElement.videoWidth; - canvasElement.height = videoElement.videoHeight; - context?.drawImage(videoElement, 0, 0, canvasElement.width, canvasElement.height); - const thumbnail = canvasElement.toDataURL('image/png'); - onChangeThumbnail?.((prevVideos) => { - const newVideos = [...prevVideos]; - newVideos[index].thumbnail = thumbnail; - return newVideos; - }); - }); + const onVideoChange = (e: React.ChangeEvent<HTMLInputElement>) => { + e.preventDefault(); + e.stopPropagation(); + onVideoFileChange?.(e.target.files ? [...e.target.files] : []); }; return ( @@ -146,7 +88,7 @@ export function VideoButton({ <input type="file" accept="video/*" - onChange={onLoad} + onChange={onVideoChange} multiple id="video-upload" className={styles.videoButton__input} diff --git a/src/v4/social/hooks/useFileUpload.ts b/src/v4/social/hooks/useFileUpload.ts index 7adee79de..eef294845 100644 --- a/src/v4/social/hooks/useFileUpload.ts +++ b/src/v4/social/hooks/useFileUpload.ts @@ -47,7 +47,7 @@ export default function useFileUpload({ uploaded: remaining, uploading: fileList, }); - } else if (index) { + } else if (index !== undefined) { const remaining = uploadedFiles.filter((_, i) => i !== index); onChange({ @@ -62,7 +62,7 @@ export default function useFileUpload({ }); } }, - [onChange], + [onChange, uploadedFiles, fileList], ); // file upload function diff --git a/src/v4/social/internal-components/ImageThumbnail/ImageThumbnail.tsx b/src/v4/social/internal-components/ImageThumbnail/ImageThumbnail.tsx index 773a60f71..790e9e10d 100644 --- a/src/v4/social/internal-components/ImageThumbnail/ImageThumbnail.tsx +++ b/src/v4/social/internal-components/ImageThumbnail/ImageThumbnail.tsx @@ -2,7 +2,6 @@ import React from 'react'; import styles from './ImageThumbnail.module.css'; import { CloseIcon, ExclamationCircle } from '~/icons'; import { Spinner } from '~/v4/social/internal-components/Spinner'; -import clsx from 'clsx'; import useFileUpload from '~/v4/social/hooks/useFileUpload'; import { FileRepository } from '@amityco/ts-sdk'; @@ -25,8 +24,6 @@ export function ImageThumbnail({ onError, isErrorUpload, }: ImageThumbnailProps) { - // Images/files incoming from uploads. - const useFileUploadProps = useFileUpload({ files, uploadedFiles, diff --git a/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx b/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx index db00f7be5..372a08da1 100644 --- a/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx +++ b/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx @@ -133,7 +133,7 @@ function Mention({ let options: MentionTypeaheadOption[] = []; const intersectionRef = useRef<HTMLDivElement>(null); const { - users: members, + members, hasMore: hasMoreMember, isLoading: isLoadingMember, loadMore: loadMoreMember, @@ -162,7 +162,7 @@ function Mention({ ref: intersectionRef, }); - const community = useCommunity(communityId || ''); + const community = useCommunity({ communityId }).community; const isPublic = community?.isPublic; if (communityId && !isPublic) { diff --git a/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx b/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx index 9f9975c2f..33803132f 100644 --- a/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx +++ b/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx @@ -22,7 +22,8 @@ import { ImageThumbnail } from '~/v4/social/internal-components/ImageThumbnail'; import { MediaAttachment } from '~/v4/social/components/MediaAttachment'; import { VideoThumbnail } from '~/v4/social/internal-components/VideoThumbnail'; import { isMobile } from '~/v4/social/utils/isMobile'; -import { useGlobalFeedContext } from '../../providers/GlobalFeedProvider'; +import { useGlobalFeedContext } from '~/v4/social/providers/GlobalFeedProvider'; +import { generateThumbnailVideo } from '~/v4/social/utils/generateThumbnailVideo'; export enum Mode { CREATE = 'create', @@ -127,6 +128,8 @@ const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCr const [incomingVideos, setIncomingVideos] = useState<File[]>([]); const [uploadLoading, setUploadLoading] = useState(false); + const [uploadedImagesCount, setUploadedImagesCount] = useState<File[]>([]); + // Visible menu attachment const [isVisibleCamera, setIsVisibleCamera] = useState(false); const [isVisibleImage, setIsVisibleImage] = useState(true); @@ -225,16 +228,65 @@ const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCr } }, []); - const handleThumbnailChange = ( - updatedThumbnails: { file: File; videoUrl: string; thumbnail: string | undefined }[], - ) => { - setVideoThumbnail(updatedThumbnails); - }; - const handleRemoveThumbnail = (index: number) => { setVideoThumbnail((prev) => prev.filter((_, i) => i !== index)); }; + const handleImageFileChange = (file: File[]) => { + if (file.length + uploadedImagesCount.length > 10) { + confirm({ + pageId: pageId, + type: 'info', + title: 'Maximum upload limit reached', + content: + 'You’ve reached the upload limit of 10 images. Any additional images will not be saved. ', + okText: 'Close', + }); + return; + } + + if (file.length > 0) { + setUploadedImagesCount((prevImages) => [...prevImages, ...file]); + setIncomingImages(file); + } + }; + + const handleVideoFileChange = async (file: File[]) => { + const existingVideosCount = videoThumbnail ? videoThumbnail.length : 0; + + if (file.length + existingVideosCount > 10) { + confirm({ + pageId: pageId, + type: 'info', + title: 'Maximum upload limit reached', + content: + 'You’ve reached the upload limit of 10 videos. Any additional videos will not be saved. ', + okText: 'Close', + }); + return; + } + + if (file.length > 0) { + setIncomingVideos?.(file); + const updatedVideos = file.map((file) => ({ + file, + videoUrl: URL.createObjectURL(file), + thumbnail: undefined, + })); + + const thumbnailVideo = await Promise.all( + updatedVideos.map(async (video) => { + const thumbnail = await generateThumbnailVideo(video.file); + return { + ...video, + thumbnail: thumbnail, + }; + }), + ); + setVideoThumbnail((prev) => [...prev, ...thumbnailVideo]); + } + }; + return ( <div className={styles.postComposerPage} style={themeStyles}> <form onSubmit={handleSubmit(onSubmit)}> @@ -312,26 +364,20 @@ const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCr snap == HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU ? ( <DetailedMediaAttachment pageId={pageId} - uploadLoading={uploadLoading} - onChangeImages={(newImageFiles) => setIncomingImages(newImageFiles)} - onChangeThumbnail={handleThumbnailChange} - videoThumbnail={videoThumbnail} - onChangeVideos={setIncomingVideos} isVisibleCamera={isVisibleCamera} isVisibleImage={isVisibleImage} isVisibleVideo={isVisibleVideo} + onVideoFileChange={handleVideoFileChange} + onImageFileChange={handleImageFileChange} /> ) : ( <MediaAttachment pageId={pageId} - uploadLoading={uploadLoading} - onChangeImages={(newImageFiles) => setIncomingImages(newImageFiles)} - onChangeThumbnail={(updatedVideo) => handleThumbnailChange(updatedVideo)} - videoThumbnail={videoThumbnail} - onChangeVideos={setIncomingVideos} isVisibleCamera={isVisibleCamera} isVisibleImage={isVisibleImage} isVisibleVideo={isVisibleVideo} + onVideoFileChange={handleVideoFileChange} + onImageFileChange={handleImageFileChange} /> )} </Drawer.Content> diff --git a/src/v4/social/utils/generateThumbnailVideo.ts b/src/v4/social/utils/generateThumbnailVideo.ts new file mode 100644 index 000000000..68638054d --- /dev/null +++ b/src/v4/social/utils/generateThumbnailVideo.ts @@ -0,0 +1,27 @@ +export const generateThumbnailVideo = (file: File): Promise<string> => { + return new Promise((resolve, reject) => { + const videoElement = document.createElement('video'); + const canvasElement = document.createElement('canvas'); + const context = canvasElement.getContext('2d'); + + videoElement.src = URL.createObjectURL(file); + videoElement.currentTime = 10; + + videoElement.addEventListener('loadeddata', () => { + try { + videoElement.pause(); + canvasElement.width = videoElement.videoWidth; + canvasElement.height = videoElement.videoHeight; + context?.drawImage(videoElement, 0, 0, canvasElement.width, canvasElement.height); + const thumbnail = canvasElement.toDataURL('image/png'); + resolve(thumbnail); + } catch (error) { + reject(error); + } + }); + + videoElement.addEventListener('error', (event) => { + reject(new Error(`Error loading video: ${event.message}`)); + }); + }); +}; From 281921e2a68636b746c20cdef7533b985fa784b8 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Mon, 15 Jul 2024 18:35:58 +0700 Subject: [PATCH 254/300] fix: split into RoundedBackButton and BackButton (#543) --- .../social/elements/BackButton/BackButton.tsx | 25 ++++---- .../RoundedBackButton.module.css | 5 ++ .../RoundedBackButton.stories.tsx | 15 +++++ .../RoundedBackButton/RoundedBackButton.tsx | 61 +++++++++++++++++++ .../elements/RoundedBackButton/index.ts | 1 + src/v4/social/pages/DraftsPage/DraftsPage.tsx | 7 +-- 6 files changed, 96 insertions(+), 18 deletions(-) create mode 100644 src/v4/social/elements/RoundedBackButton/RoundedBackButton.module.css create mode 100644 src/v4/social/elements/RoundedBackButton/RoundedBackButton.stories.tsx create mode 100644 src/v4/social/elements/RoundedBackButton/RoundedBackButton.tsx create mode 100644 src/v4/social/elements/RoundedBackButton/index.ts diff --git a/src/v4/social/elements/BackButton/BackButton.tsx b/src/v4/social/elements/BackButton/BackButton.tsx index a1245b030..977aca3a9 100644 --- a/src/v4/social/elements/BackButton/BackButton.tsx +++ b/src/v4/social/elements/BackButton/BackButton.tsx @@ -1,24 +1,19 @@ import React from 'react'; import { IconComponent } from '~/v4/core/IconComponent'; import { useAmityElement } from '~/v4/core/hooks/uikit'; -import { Button, ButtonProps } from '~/v4/core/natives/Button'; - +import { Button, ButtonProps } from '~/v4/core/natives/Button/Button'; import styles from './BackButton.module.css'; const BackButtonSvg = (props: React.SVGProps<SVGSVGElement>) => ( <svg - xmlns="http://www.w3.org/2000/svg" - width="32" - height="32" + width="10" + height="17" + viewBox="0 0 10 17" fill="none" - viewBox="0 0 32 32" + xmlns="http://www.w3.org/2000/svg" {...props} > - <circle cx="16" cy="16" r="16" fill="#000" fillOpacity="0.5"></circle> - <path - fill="currentColor" - d="M16.176 23.914c-.14.176-.422.176-.598 0L8.23 16.566a.405.405 0 010-.597l7.348-7.348c.176-.176.457-.176.598 0l.703.668c.176.176.176.457 0 .598l-5.45 5.449h12.024c.211 0 .422.21.422.422v.984c0 .246-.21.422-.422.422H11.43l5.449 5.484c.176.141.176.422 0 .598l-.703.668z" - ></path> + <path d="M8.62109 15.9141C8.44531 16.0898 8.19922 16.0898 8.02344 15.9141L0.640625 8.56641C0.5 8.39062 0.5 8.14453 0.640625 7.96875L8.02344 0.621094C8.19922 0.445312 8.44531 0.445312 8.62109 0.621094L9.32422 1.28906C9.46484 1.46484 9.46484 1.74609 9.32422 1.88672L2.96094 8.25L9.32422 14.6484C9.46484 14.7891 9.46484 15.0703 9.32422 15.2461L8.62109 15.9141Z" /> </svg> ); @@ -48,9 +43,13 @@ export const BackButton = ({ if (isExcluded) return null; return ( - <Button style={themeStyles} className={styles.backButton} onPress={onPress}> + <Button + data-qa-anchor={accessibilityId} + className={styles.backButton} + onPress={onPress} + style={themeStyles} + > <IconComponent - data-qa-anchor={accessibilityId} defaultIcon={() => <BackButtonSvg className={defaultClassName} />} imgIcon={() => <img src={config.icon} alt={uiReference} className={imgClassName} />} defaultIconName={defaultConfig.icon} diff --git a/src/v4/social/elements/RoundedBackButton/RoundedBackButton.module.css b/src/v4/social/elements/RoundedBackButton/RoundedBackButton.module.css new file mode 100644 index 000000000..6c06eda72 --- /dev/null +++ b/src/v4/social/elements/RoundedBackButton/RoundedBackButton.module.css @@ -0,0 +1,5 @@ +.roundedBackButton { + all: unset; + fill: var(--asc-color-white); + cursor: pointer; +} diff --git a/src/v4/social/elements/RoundedBackButton/RoundedBackButton.stories.tsx b/src/v4/social/elements/RoundedBackButton/RoundedBackButton.stories.tsx new file mode 100644 index 000000000..54d38767c --- /dev/null +++ b/src/v4/social/elements/RoundedBackButton/RoundedBackButton.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { RoundedBackButton } from './RoundedBackButton'; + +export default { + title: 'v4-social/elements/RoundedBackButton', +}; + +export const RoundedBackButtonStory = { + render: () => { + return <RoundedBackButton />; + }, + + name: 'RoundedBackButton', +}; diff --git a/src/v4/social/elements/RoundedBackButton/RoundedBackButton.tsx b/src/v4/social/elements/RoundedBackButton/RoundedBackButton.tsx new file mode 100644 index 000000000..88d346ba8 --- /dev/null +++ b/src/v4/social/elements/RoundedBackButton/RoundedBackButton.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { IconComponent } from '~/v4/core/IconComponent'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { Button, ButtonProps } from '~/v4/core/natives/Button'; + +import styles from './RoundedBackButton.module.css'; + +const RoundedBackButtonSvg = (props: React.SVGProps<SVGSVGElement>) => ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="32" + height="32" + fill="none" + viewBox="0 0 32 32" + {...props} + > + <circle cx="16" cy="16" r="16" fill="#000" fillOpacity="0.5"></circle> + <path + fill="currentColor" + d="M16.176 23.914c-.14.176-.422.176-.598 0L8.23 16.566a.405.405 0 010-.597l7.348-7.348c.176-.176.457-.176.598 0l.703.668c.176.176.176.457 0 .598l-5.45 5.449h12.024c.211 0 .422.21.422.422v.984c0 .246-.21.422-.422.422H11.43l5.449 5.484c.176.141.176.422 0 .598l-.703.668z" + ></path> + </svg> +); + +interface RoundedBackButtonProps { + pageId?: string; + componentId?: string; + defaultClassName?: string; + imgClassName?: string; + onPress?: ButtonProps['onPress']; +} + +export const RoundedBackButton = ({ + pageId = '*', + componentId = '*', + defaultClassName, + imgClassName, + onPress, +}: RoundedBackButtonProps) => { + const elementId = 'back_button'; + const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = + useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + return ( + <Button style={themeStyles} className={styles.roundedBackButton} onPress={onPress}> + <IconComponent + data-qa-anchor={accessibilityId} + defaultIcon={() => <RoundedBackButtonSvg className={defaultClassName} />} + imgIcon={() => <img src={config.icon} alt={uiReference} className={imgClassName} />} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} + /> + </Button> + ); +}; diff --git a/src/v4/social/elements/RoundedBackButton/index.ts b/src/v4/social/elements/RoundedBackButton/index.ts new file mode 100644 index 000000000..b3df8a048 --- /dev/null +++ b/src/v4/social/elements/RoundedBackButton/index.ts @@ -0,0 +1 @@ +export { RoundedBackButton } from './RoundedBackButton'; diff --git a/src/v4/social/pages/DraftsPage/DraftsPage.tsx b/src/v4/social/pages/DraftsPage/DraftsPage.tsx index 69a3d73e3..ddf3de08b 100644 --- a/src/v4/social/pages/DraftsPage/DraftsPage.tsx +++ b/src/v4/social/pages/DraftsPage/DraftsPage.tsx @@ -2,7 +2,6 @@ import React, { useCallback, useEffect, useState } from 'react'; import { SubmitHandler } from 'react-hook-form'; import { AspectRatioButton, - BackButton, HyperLinkButton, ShareStoryButton, HyperLink, @@ -17,6 +16,7 @@ import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; import { PageTypes, useNavigation } from '~/v4/core/providers/NavigationProvider'; import { VideoPreview } from '~/v4/social/internal-components/VideoPreview'; import { useAmityPage } from '~/v4/core/hooks/uikit'; +import { RoundedBackButton } from '~/v4/social/elements/RoundedBackButton'; import ColorThief from 'colorthief'; import styles from './DraftsPage.module.css'; @@ -212,7 +212,7 @@ export const PlainDraftStoryPage = ({ <div id="asc-uikit-create-story" className={styles.draftPageContainer}> <div className={styles.headerContainer}> <div className={styles.header}> - <BackButton pageId={pageId} onPress={discardCreateStory} /> + <RoundedBackButton pageId={pageId} onPress={discardCreateStory} /> <div className={styles.topRightButtons}> {mediaType?.type === 'image' && ( <AspectRatioButton pageId={pageId} onPress={onClickImageMode} /> @@ -221,7 +221,6 @@ export const PlainDraftStoryPage = ({ </div> </div> </div> - {mediaType?.type === 'image' ? ( <div className={styles.mainContainer} @@ -267,7 +266,6 @@ export const PlainDraftStoryPage = ({ </HyperLink> </div> )} - <HyperLinkConfig pageId={pageId} isOpen={isHyperLinkBottomSheetOpen} @@ -276,7 +274,6 @@ export const PlainDraftStoryPage = ({ onRemove={onRemoveHyperLink} isHaveHyperLink={hyperLink?.[0]?.data?.url !== ''} /> - <div className={styles.footer}> <ShareStoryButton community={community} From 3200a08af0f570de45ff208b00ae2f2ae7289bf8 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Tue, 16 Jul 2024 12:17:17 +0700 Subject: [PATCH 255/300] fix: useFeed (#542) --- src/social/hooks/useFeed.ts | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/social/hooks/useFeed.ts b/src/social/hooks/useFeed.ts index 3a22d9e92..fa6e0f95c 100644 --- a/src/social/hooks/useFeed.ts +++ b/src/social/hooks/useFeed.ts @@ -8,13 +8,23 @@ const useFeed = () => { const [loadMoreHasBeenCalled, setLoadMoreHasBeenCalled] = useState(false); async function fetchMore() { + if (isLoading) { + return; + } try { + setIsLoading(true); const newPosts = await FeedRepository.queryGlobalFeed({ limit: 10, queryToken: queryToken || undefined, }); setQueryToken(newPosts.paging.next || null); - setItems((prevItems) => [...prevItems, ...newPosts.data]); + setItems((prevItems: Amity.Post[]) => { + const currentItemIds = new Set([...prevItems.map((item) => item.postId)]); + return [ + ...prevItems, + ...newPosts.data.filter((item: Amity.Post) => !currentItemIds.has(item.postId)), + ]; + }); } finally { setIsLoading(false); } @@ -30,7 +40,13 @@ const useFeed = () => { }, []); const prependItem = (post: Amity.Post) => { - setItems((prevItems) => [post, ...prevItems]); + setItems((prevItems) => { + const currentItemIds = new Set([...prevItems.map((item) => item.postId)]); + if (!currentItemIds.has(post.postId)) { + return [post, ...prevItems]; + } + return prevItems; + }); }; const removeItem = (postId: string) => { @@ -42,7 +58,7 @@ const useFeed = () => { const loadMore = () => { setLoadMoreHasBeenCalled(true); - if (hasMore) { + if (hasMore && !isLoading) { fetchMore(); } }; From 223ef147270832f97d093363a62d702e7d5a5b01 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Tue, 16 Jul 2024 12:18:13 +0700 Subject: [PATCH 256/300] chore: ASC-23077 - eslint 9 (#387) * chore: import rule lint * chore: eslint 9 * chore: add packageManager field * fix: remove pnpm version from ci * chore: removed unused file * chore: update packageManager field in package.json * chore: update pnpm-lock.yaml * chore: bring back the old code * fix: lint * chore: upgrade pnpm to 9.5.0 * chore: bring back .npmrc * fix: css path --- .eslintignore | 6 - .eslintrc.cjs | 44 - .github/workflows/dev.yaml | 1 - .github/workflows/production.yaml | 1 - .github/workflows/staging.yaml | 1 - .github/workflows/testing.yaml | 1 - eslint.config.mjs | 74 ++ package.json | 16 +- pnpm-lock.yaml | 1172 +++++------------ src/chat/components/UserAvatar/index.tsx | 2 +- .../components/InputText/InsideInputText.tsx | 4 +- .../Uploaders/File/File.stories.tsx | 2 +- .../Uploaders/Image/Image.stories.tsx | 2 +- .../components/Uploaders/Image/styles.tsx | 5 +- .../Uploaders/Video/Video.stories.tsx | 2 +- .../components/Uploaders/Video/styles.tsx | 2 +- src/core/components/UserSelector/index.tsx | 4 +- src/core/hooks/useUser.ts | 2 - .../providers/UiKitProvider/theme/palette.ts | 15 +- .../UiKitProvider/theme/typography.ts | 34 +- src/social/components/ExploreHeader/index.tsx | 4 +- src/social/components/Images/styles.tsx | 6 +- .../components/community/Card/styles.tsx | 4 +- .../Creator/components/ImagesUploaded.tsx | 11 +- src/social/components/post/Creator/index.tsx | 3 +- .../components/post/Editor/Content/index.tsx | 6 +- src/social/pages/NewsFeed/styles.tsx | 4 +- src/utils.ts | 2 +- .../ChatHeader/ChatHeader.stories.tsx | 2 +- .../chat/components/ChatHeader/ChatHeader.tsx | 2 +- .../MessageComposer/MessageComposer.tsx | 6 +- .../MessageBubble/index.tsx | 2 +- .../MessageTextWithMention/index.tsx | 2 +- .../LiveChatMessageContent/index.tsx | 6 +- .../LiveChatNotification.tsx | 2 +- src/v4/chat/pages/LiveChat/LiveChat.tsx | 4 +- src/v4/core/components/Drawer/Drawer.tsx | 2 +- src/v4/core/components/Modal/index.tsx | 2 +- .../components/SocialMentionItem/index.tsx | 2 +- src/v4/core/hooks/usePaginator.ts | 4 +- src/v4/core/providers/AdEngineProvider.tsx | 6 +- src/v4/core/providers/AmityUIKitProvider.tsx | 8 +- src/v4/helpers/utils.ts | 2 +- .../CommentEdition/CommentEdition.tsx | 4 +- .../CommentOptions/CommentOptions.tsx | 4 +- .../components/CreatePostMenu/index.tsx | 2 +- .../DetailedMediaAttachment/index.tsx | 2 +- .../components/GlobalFeed/GlobalFeed.tsx | 2 +- .../HyperLinkConfig/HyperLinkConfig.tsx | 13 +- .../social/components/Newsfeed/Newsfeed.tsx | 2 +- .../components/PostComment/PostComment.tsx | 18 +- .../PostCommentComposer.tsx | 2 +- .../PostCommentComposer/PostCommentInput.tsx | 6 +- .../PostCommentList/PostCommentList.tsx | 8 +- .../PostCommentMentionInput.tsx | 4 +- .../PostContent/ImageContent/ImageContent.tsx | 2 +- .../components/PostContent/PostContent.tsx | 2 +- .../PostContent/TextContent/TextContent.tsx | 2 +- .../PostReplyComment/PostReplyComment.tsx | 28 +- .../PostReplyCommentList.tsx | 6 +- .../TopNavigation/TopNavigation.tsx | 2 +- .../UserSearchResult/UserSearchItem.tsx | 2 +- .../CreateNewPostButton.tsx | 2 +- .../elements/CreatePostButton/index.tsx | 2 +- .../MyTimelineAvatar/MyTimelineAvatar.tsx | 6 +- .../elements/MyTimelineAvatar/index.tsx | 2 +- .../social/elements/PostTextField/index.tsx | 2 +- .../ShareStoryButton/ShareStoryButton.tsx | 2 +- .../StoryCommentButton/StoryCommentButton.tsx | 5 +- .../StoryProgressBar/StoryProgressBar.tsx | 2 +- .../StoryReactionButton.tsx | 2 +- src/v4/social/hooks/useLiveCollection.ts | 1 - .../internal-components/Comment/UIComment.tsx | 2 +- .../CommentAd/CommentAd.tsx | 2 +- .../CommentAd/UICommentAd.tsx | 4 +- .../CommentList/CommentList.tsx | 3 +- .../CommunityMember/CommunityMember.tsx | 4 +- .../ImageViewer/ImageViewer.tsx | 2 +- .../MentionTextInput/MentionNodes.ts | 1 - .../MentionTextInput/MentionTextInput.tsx | 2 +- .../internal-components/PostAd/PostAd.tsx | 2 +- .../internal-components/PostAd/UIPostAd.tsx | 4 +- .../internal-components/StoryAd/UIStoryAd.tsx | 6 +- .../StoryCommentComposeBar.tsx | 2 +- .../StoryViewer/Renderers/storyAd.tsx | 2 +- .../TextWithMention/TextWithMention.tsx | 4 +- .../VideoThumbnail/index.tsx | 2 +- src/v4/social/pages/Application/index.tsx | 6 +- .../social/pages/PostComposerPage/index.tsx | 2 +- .../pages/PostDetailPage/PostDetailPage.tsx | 4 +- .../SocialGlobalSearchPage.tsx | 2 +- .../pages/SocialHomePage/SocialHomePage.tsx | 4 +- 92 files changed, 641 insertions(+), 1038 deletions(-) delete mode 100644 .eslintignore delete mode 100644 .eslintrc.cjs create mode 100644 eslint.config.mjs diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 14ebc1271..000000000 --- a/.eslintignore +++ /dev/null @@ -1,6 +0,0 @@ -node_modules/ -storybook-build/ -build/ - -dist/ -jest.config.js \ No newline at end of file diff --git a/.eslintrc.cjs b/.eslintrc.cjs deleted file mode 100644 index bbff5e4c4..000000000 --- a/.eslintrc.cjs +++ /dev/null @@ -1,44 +0,0 @@ -module.exports = { - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:import/recommended', - 'plugin:import/typescript', - 'prettier', - ], - parser: '@typescript-eslint/parser', - plugins: ['@typescript-eslint', 'import', 'jest'], - root: true, - rules: { - '@typescript-eslint/no-empty-function': 'off', - 'import/no-named-as-default': 'off', - 'import/no-named-as-default-member': 'off', - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-unused-vars': 'off', - '@typescript-eslint/ban-ts-comment': 'off', - }, - ignorePatterns: [ - // Ignore build artifacts - '**/build', - '**/coverage', - '**/.webpackCache', - '**/node_modules', - '**/images', - - // Ignore generated files - '**/__generated__', - '**/generated', - '*.graphql', - ], - settings: { - 'import/resolver': { - typescript: true, - node: true, - }, - }, - env: { - node: true, - browser: true, - jest: true, - }, -}; diff --git a/.github/workflows/dev.yaml b/.github/workflows/dev.yaml index a8dcad97a..3716f2309 100644 --- a/.github/workflows/dev.yaml +++ b/.github/workflows/dev.yaml @@ -31,7 +31,6 @@ jobs: - uses: pnpm/action-setup@v4 with: - version: 9 run_install: false - name: Get pnpm store directory diff --git a/.github/workflows/production.yaml b/.github/workflows/production.yaml index f54fafa7a..9a592d33e 100644 --- a/.github/workflows/production.yaml +++ b/.github/workflows/production.yaml @@ -42,7 +42,6 @@ jobs: - uses: pnpm/action-setup@v4 with: - version: 9 run_install: false - name: Get pnpm store directory diff --git a/.github/workflows/staging.yaml b/.github/workflows/staging.yaml index 3d15d4011..d522f1e14 100644 --- a/.github/workflows/staging.yaml +++ b/.github/workflows/staging.yaml @@ -30,7 +30,6 @@ jobs: - uses: pnpm/action-setup@v4 with: - version: 9 run_install: false - name: Get pnpm store directory diff --git a/.github/workflows/testing.yaml b/.github/workflows/testing.yaml index 8511c3ba7..6d20fa089 100644 --- a/.github/workflows/testing.yaml +++ b/.github/workflows/testing.yaml @@ -22,7 +22,6 @@ jobs: - uses: pnpm/action-setup@v4 with: - version: 9 run_install: false - name: Get pnpm store directory diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 000000000..df7b5ce8a --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,74 @@ +// @ts-check + +import jest from 'eslint-plugin-jest'; + +import globals from 'globals'; +import js from '@eslint/js'; +import tseslint from 'typescript-eslint'; +import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; + +const ignores = [ + '**/node_modules', + '**/storybook-build', + '**/build', + '**/dist', + '**/jest.config.js', + // Ignore build artifacts + '**/build', + '**/coverage', + '**/node_modules', +]; + +export default [ + js.configs.recommended, + ...tseslint.configs.recommended, + { + languageOptions: { + globals: { + ...globals.browser, + ...globals.node, + ...globals.jest, + }, + }, + rules: { + '@typescript-eslint/no-empty-function': 'off', + 'import/no-named-as-default': 'off', + 'import/no-named-as-default-member': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/ban-ts-comment': 'off', + }, + }, + { + files: ['**/*.{test,spec}.{ts,tsx}'], + ignores, + ...jest.configs['flat/recommended'], + rules: { + ...jest.configs['flat/recommended'].rules, + 'jest/prefer-expect-assertions': 'off', + }, + }, + { + files: ['src/v4/**/*.{ts,tsx}'], + ignores, + rules: { + 'no-restricted-imports': [ + 'error', + { + patterns: [ + { + group: ['../'], + message: 'Relative imports are not allowed.', + }, + { + group: ['**/*/index'], + message: 'index imports are not allowed.', + }, + ], + }, + ], + }, + }, + eslintPluginPrettierRecommended, + { ignores }, +]; diff --git a/package.json b/package.json index cdf9d11f0..1a1840545 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ }, "devDependencies": { "@amityco/ts-sdk": "^6.27.0", + "@eslint/js": "^9.4.0", "@storybook/addon-a11y": "^7.6.7", "@storybook/addon-actions": "^7.6.7", "@storybook/addon-backgrounds": "^7.6.7", @@ -57,6 +58,7 @@ "@storybook/react": "^7.6.7", "@storybook/react-vite": "^7.6.7", "@storybook/theming": "^7.6.7", + "@types/eslint__js": "^8.42.3", "@types/jest": "^29.5.11", "@types/lodash": "^4.14.202", "@types/prop-types": "^15.7.11", @@ -74,16 +76,16 @@ "autoprefixer": "^10.4.19", "browserslist": "^4.23.0", "esbuild-plugin-replace": "^1.4.0", - "eslint": "^8.56.0", + "eslint": "^9.4.0", "eslint-config-prettier": "^9.1.0", - "eslint-import-resolver-typescript": "^3.6.1", - "eslint-plugin-import": "^2.29.1", "eslint-plugin-jest": "^27.6.1", + "eslint-plugin-prettier": "^5.1.3", + "globals": "^15.4.0", "husky": "^8.0.3", "jest": "^29.7.0", "lint-staged": "^13.3.0", "postcss": "^8.4.38", - "prettier": "2.4.0", + "prettier": "3.3.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.21.1", @@ -97,6 +99,7 @@ "ts-jest": "^29.1.1", "tsup": "^7.3.0", "typescript": "^4.9.5", + "typescript-eslint": "^7.12.0", "typescript-plugin-css-modules": "^5.1.0", "vite": "^4.5.1", "vite-tsconfig-paths": "^4.2.3" @@ -146,7 +149,7 @@ }, "lint-staged": { "*.{js,jsx,ts,tsx}": "eslint --cache --fix", - "*.{json,md,css}": "prettier --write", + "*.{json,css}": "prettier --write", "*.{css}": "stylelint --fix" }, "postcss": { @@ -154,5 +157,6 @@ "autoprefixer": {} } }, - "license": "LGPL-2.1-only" + "license": "LGPL-2.1-only", + "packageManager": "pnpm@9.5.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7ac64fee4..596f9b382 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -135,6 +135,9 @@ importers: '@amityco/ts-sdk': specifier: ^6.27.0 version: 6.27.0 + '@eslint/js': + specifier: ^9.4.0 + version: 9.7.0 '@storybook/addon-a11y': specifier: ^7.6.7 version: 7.6.19 @@ -171,6 +174,9 @@ importers: '@storybook/theming': specifier: ^7.6.7 version: 7.6.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@types/eslint__js': + specifier: ^8.42.3 + version: 8.42.3 '@types/jest': specifier: ^29.5.11 version: 29.5.12 @@ -206,10 +212,10 @@ importers: version: 9.0.8 '@typescript-eslint/eslint-plugin': specifier: ^6.18.0 - version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(typescript@4.9.5) + version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@9.7.0)(typescript@4.9.5))(eslint@9.7.0)(typescript@4.9.5) '@typescript-eslint/parser': specifier: ^6.18.0 - version: 6.21.0(eslint@8.57.0)(typescript@4.9.5) + version: 6.21.0(eslint@9.7.0)(typescript@4.9.5) '@vitejs/plugin-react': specifier: ^4.2.1 version: 4.3.1(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2)) @@ -223,20 +229,20 @@ importers: specifier: ^1.4.0 version: 1.4.0 eslint: - specifier: ^8.56.0 - version: 8.57.0 + specifier: ^9.4.0 + version: 9.7.0 eslint-config-prettier: specifier: ^9.1.0 - version: 9.1.0(eslint@8.57.0) - eslint-import-resolver-typescript: - specifier: ^3.6.1 - version: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint-plugin-import@2.29.1)(eslint@8.57.0) - eslint-plugin-import: - specifier: ^2.29.1 - version: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + version: 9.1.0(eslint@9.7.0) eslint-plugin-jest: specifier: ^27.6.1 - version: 27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(jest@29.7.0(@types/node@20.14.4))(typescript@4.9.5) + version: 27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@9.7.0)(typescript@4.9.5))(eslint@9.7.0)(typescript@4.9.5))(eslint@9.7.0)(jest@29.7.0(@types/node@20.14.4))(typescript@4.9.5) + eslint-plugin-prettier: + specifier: ^5.1.3 + version: 5.1.3(@types/eslint@8.56.10)(eslint-config-prettier@9.1.0(eslint@9.7.0))(eslint@9.7.0)(prettier@3.3.1) + globals: + specifier: ^15.4.0 + version: 15.8.0 husky: specifier: ^8.0.3 version: 8.0.3 @@ -250,8 +256,8 @@ importers: specifier: ^8.4.38 version: 8.4.38 prettier: - specifier: 2.4.0 - version: 2.4.0 + specifier: 3.3.1 + version: 3.3.1 react: specifier: ^18.2.0 version: 18.3.1 @@ -291,6 +297,9 @@ importers: typescript: specifier: ^4.9.5 version: 4.9.5 + typescript-eslint: + specifier: ^7.12.0 + version: 7.16.1(eslint@9.7.0)(typescript@4.9.5) typescript-plugin-css-modules: specifier: ^5.1.0 version: 5.1.0(typescript@4.9.5) @@ -1296,13 +1305,25 @@ packages: resolution: {integrity: sha512-Zm2NGpWELsQAD1xsJzGQpYfvICSsFkEpU0jxBjfdC6uNEWXcHnfs9hScFWtXVDVl+rBQJGrl4g1vcKIejpH9dA==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/eslintrc@2.1.4': - resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@eslint-community/regexpp@4.11.0': + resolution: {integrity: sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/js@8.57.0': - resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@eslint/config-array@0.17.0': + resolution: {integrity: sha512-A68TBu6/1mHHuc5YJL0U0VVeGNiklLAL6rRmhTCP2B5XjWLMnrX+HkO+IAXyHvks5cyyY1jjK5ITPQ1HGS2EVA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.1.0': + resolution: {integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.7.0': + resolution: {integrity: sha512-ChuWDQenef8OSFnvuxv0TCVxEwmu3+hPNKvM9B34qpM0rDRbjL8t5QkQeHHeAfsKQjuH9wS82WeCi1J/owatng==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.4': + resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@fal-works/esbuild-plugin-global-externals@2.1.2': resolution: {integrity: sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ==} @@ -1387,18 +1408,13 @@ packages: peerDependencies: react-hook-form: ^7.0.0 - '@humanwhocodes/config-array@0.11.14': - resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} - engines: {node: '>=10.10.0'} - deprecated: Use @eslint/config-array instead - '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} - '@humanwhocodes/object-schema@2.0.3': - resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} - deprecated: Use @eslint/object-schema instead + '@humanwhocodes/retry@0.3.0': + resolution: {integrity: sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==} + engines: {node: '>=18.18'} '@hutson/parse-repository-url@3.0.2': resolution: {integrity: sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==} @@ -1625,6 +1641,10 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@pkgr/core@0.1.1': + resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@radix-ui/number@1.0.1': resolution: {integrity: sha512-T5gIdVO2mmPW3NNhjNgEP3cqMXjXL9UbO0BzWcXfvdBs+BohbQxvd/K5hSVKmn9/lbTdsQVKbUcP5WLCwvUbBg==} @@ -2872,6 +2892,9 @@ packages: '@types/eslint@8.56.10': resolution: {integrity: sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==} + '@types/eslint__js@8.42.3': + resolution: {integrity: sha512-alfG737uhmPdnvkrLdZLcEKJ/B8s9Y4hrZ+YAdzUeoArBlSUERA2E87ROfOaS4jd/C45fzOoZzidLc1IPwLqOw==} + '@types/estree@0.0.51': resolution: {integrity: sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==} @@ -2917,9 +2940,6 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/json5@0.0.29': - resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - '@types/lodash@4.17.5': resolution: {integrity: sha512-MBIOHVZqVqgfro1euRDWX7OO0fBVUUMrN6Pwm8LQsz8cWhEpihlvR70ENj3f40j58TNxZaWv2ndSkInykNBBJw==} @@ -3039,6 +3059,17 @@ packages: typescript: optional: true + '@typescript-eslint/eslint-plugin@7.16.1': + resolution: {integrity: sha512-SxdPak/5bO0EnGktV05+Hq8oatjAYVY3Zh2bye9pGZy6+jwyR3LG3YKkV4YatlsgqXP28BTeVm9pqwJM96vf2A==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + '@typescript-eslint/parser': ^7.0.0 + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + '@typescript-eslint/parser@6.21.0': resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} engines: {node: ^16.0.0 || >=18.0.0} @@ -3049,6 +3080,16 @@ packages: typescript: optional: true + '@typescript-eslint/parser@7.16.1': + resolution: {integrity: sha512-u+1Qx86jfGQ5i4JjK33/FnawZRpsLxRnKzGE6EABZ40KxVT/vWsiZFEBBHjFOljmmV3MBYOHEKi0Jm9hbAOClA==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + '@typescript-eslint/scope-manager@5.62.0': resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -3057,6 +3098,10 @@ packages: resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/scope-manager@7.16.1': + resolution: {integrity: sha512-nYpyv6ALte18gbMz323RM+vpFpTjfNdyakbf3nsLvF43uF9KeNC289SUEW3QLZ1xPtyINJ1dIsZOuWuSRIWygw==} + engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/type-utils@6.21.0': resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==} engines: {node: ^16.0.0 || >=18.0.0} @@ -3067,6 +3112,16 @@ packages: typescript: optional: true + '@typescript-eslint/type-utils@7.16.1': + resolution: {integrity: sha512-rbu/H2MWXN4SkjIIyWcmYBjlp55VT+1G3duFOIukTNFxr9PI35pLc2ydwAfejCEitCv4uztA07q0QWanOHC7dA==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + '@typescript-eslint/types@5.62.0': resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -3075,6 +3130,10 @@ packages: resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/types@7.16.1': + resolution: {integrity: sha512-AQn9XqCzUXd4bAVEsAXM/Izk11Wx2u4H3BAfQVhSfzfDOm/wAON9nP7J5rpkCxts7E5TELmN845xTUCQrD1xIQ==} + engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/typescript-estree@5.62.0': resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -3093,6 +3152,15 @@ packages: typescript: optional: true + '@typescript-eslint/typescript-estree@7.16.1': + resolution: {integrity: sha512-0vFPk8tMjj6apaAZ1HlwM8w7jbghC8jc1aRNJG5vN8Ym5miyhTQGMqU++kuBFDNKe9NcPeZ6x0zfSzV8xC1UlQ==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + '@typescript-eslint/utils@5.62.0': resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -3105,6 +3173,12 @@ packages: peerDependencies: eslint: ^7.0.0 || ^8.0.0 + '@typescript-eslint/utils@7.16.1': + resolution: {integrity: sha512-WrFM8nzCowV0he0RlkotGDujx78xudsxnGMBHI88l5J8wEhED6yBwaSLP99ygfrzAjsQvcYQ94quDwI0d7E1fA==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + '@typescript-eslint/visitor-keys@5.62.0': resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -3113,8 +3187,9 @@ packages: resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} engines: {node: ^16.0.0 || >=18.0.0} - '@ungap/structured-clone@1.2.0': - resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + '@typescript-eslint/visitor-keys@7.16.1': + resolution: {integrity: sha512-Qlzzx4sE4u3FsHTPQAAQFJFNOuqtuY0LFrZHwQ8IHK705XxBiWOFkfKRWu6niB7hwfgnwIpO4jTC75ozW1PHWg==} + engines: {node: ^18.18.0 || >=20.0.0} '@vitejs/plugin-react@3.1.0': resolution: {integrity: sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==} @@ -3223,11 +3298,6 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - acorn@8.12.0: - resolution: {integrity: sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==} - engines: {node: '>=0.4.0'} - hasBin: true - acorn@8.12.1: resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} engines: {node: '>=0.4.0'} @@ -3318,10 +3388,6 @@ packages: resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==} engines: {node: '>=10'} - array-buffer-byte-length@1.0.1: - resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} - engines: {node: '>= 0.4'} - array-differ@3.0.0: resolution: {integrity: sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==} engines: {node: '>=8'} @@ -3332,30 +3398,10 @@ packages: array-ify@1.0.0: resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} - array-includes@3.1.8: - resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} - engines: {node: '>= 0.4'} - array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} - array.prototype.findlastindex@1.2.5: - resolution: {integrity: sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==} - engines: {node: '>= 0.4'} - - array.prototype.flat@1.3.2: - resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} - engines: {node: '>= 0.4'} - - array.prototype.flatmap@1.3.2: - resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} - engines: {node: '>= 0.4'} - - arraybuffer.prototype.slice@1.0.3: - resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} - engines: {node: '>= 0.4'} - arraybuffer.slice@0.0.7: resolution: {integrity: sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==} @@ -3970,18 +4016,6 @@ packages: data-uri-to-buffer@0.0.3: resolution: {integrity: sha512-Cp+jOa8QJef5nXS5hU7M1DWzXPEIoVR3kbV0dQuVGwROZg8bGf1DcCnkmajBTnvghTtSNMUdRrPjgaT6ZQucbw==} - data-view-buffer@1.0.1: - resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} - engines: {node: '>= 0.4'} - - data-view-byte-length@1.0.1: - resolution: {integrity: sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==} - engines: {node: '>= 0.4'} - - data-view-byte-offset@1.0.0: - resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} - engines: {node: '>= 0.4'} - dateformat@3.0.3: resolution: {integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==} @@ -4004,14 +4038,6 @@ packages: supports-color: optional: true - debug@3.2.7: - resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - debug@4.3.4: resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'} @@ -4123,10 +4149,6 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} - doctrine@2.1.0: - resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} - engines: {node: '>=0.10.0'} - doctrine@3.0.0: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} @@ -4231,10 +4253,6 @@ packages: error-stack-parser@2.1.4: resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==} - es-abstract@1.23.3: - resolution: {integrity: sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==} - engines: {node: '>= 0.4'} - es-define-property@1.0.0: resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} engines: {node: '>= 0.4'} @@ -4249,21 +4267,6 @@ packages: es-module-lexer@1.5.4: resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} - es-object-atoms@1.0.0: - resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} - engines: {node: '>= 0.4'} - - es-set-tostringtag@2.0.3: - resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} - engines: {node: '>= 0.4'} - - es-shim-unscopables@1.0.2: - resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} - - es-to-primitive@1.2.1: - resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} - engines: {node: '>= 0.4'} - esbuild-plugin-alias@0.2.1: resolution: {integrity: sha512-jyfL/pwPqaFXyKnj8lP8iLk6Z0m099uXR45aSN8Av1XD4vhvQutxxPzgA2bTcAwQpa1zCXDcWOlhFgyP3GKqhQ==} @@ -4315,47 +4318,6 @@ packages: peerDependencies: eslint: '>=7.0.0' - eslint-import-resolver-node@0.3.9: - resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} - - eslint-import-resolver-typescript@3.6.1: - resolution: {integrity: sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - eslint: '*' - eslint-plugin-import: '*' - - eslint-module-utils@2.8.1: - resolution: {integrity: sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: '*' - eslint-import-resolver-node: '*' - eslint-import-resolver-typescript: '*' - eslint-import-resolver-webpack: '*' - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - eslint: - optional: true - eslint-import-resolver-node: - optional: true - eslint-import-resolver-typescript: - optional: true - eslint-import-resolver-webpack: - optional: true - - eslint-plugin-import@2.29.1: - resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - eslint-plugin-jest@27.9.0: resolution: {integrity: sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -4369,26 +4331,44 @@ packages: jest: optional: true + eslint-plugin-prettier@5.1.3: + resolution: {integrity: sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '*' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + eslint-scope@5.1.1: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} engines: {node: '>=8.0.0'} - eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-scope@8.0.2: + resolution: {integrity: sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint@8.57.0: - resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-visitor-keys@4.0.0: + resolution: {integrity: sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.7.0: + resolution: {integrity: sha512-FzJ9D/0nGiCGBf8UXO/IGLTgLVzIxze1zpfA8Ton2mjLovXdAPlYDv+MQDcqj3TmrhAGYfOpz9RfR+ent0AgAw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true - espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + espree@10.1.0: + resolution: {integrity: sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} @@ -4467,6 +4447,9 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + fast-glob@3.3.2: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} @@ -4506,9 +4489,9 @@ packages: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} - file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} file-entry-cache@9.0.0: resolution: {integrity: sha512-6MgEugi8p2tiUhqO7GnPsmbCCzj0YRCwwaTbpGRyKZesjRSzkqkAE9fPp7V2yMs5hwfgbQLgdvSSkGNg1s5Uvw==} @@ -4562,9 +4545,9 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} - flat-cache@3.2.0: - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} - engines: {node: ^10.12.0 || >=12.0.0} + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} flat-cache@5.0.0: resolution: {integrity: sha512-JrqFmyUl2PnPi1OvLyTVHnQvwQ0S+e6lGSwu8OkAZlSaNIZciTY2H/cOOROxsBA1m/LZNHDsqAgDZt6akWcjsQ==} @@ -4655,13 +4638,6 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - function.prototype.name@1.1.6: - resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} - engines: {node: '>= 0.4'} - - functions-have-names@1.2.3: - resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -4706,13 +4682,6 @@ packages: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} - get-symbol-description@1.0.2: - resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} - engines: {node: '>= 0.4'} - - get-tsconfig@4.7.5: - resolution: {integrity: sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==} - getpass@0.1.7: resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} @@ -4778,13 +4747,13 @@ packages: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} - globals@13.24.0: - resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} - engines: {node: '>=8'} + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} - globalthis@1.0.4: - resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} - engines: {node: '>= 0.4'} + globals@15.8.0: + resolution: {integrity: sha512-VZAJ4cewHTExBWDHR6yptdIBlx9YSSZuwojj9Nt5mBRXQzrKakDsVKQ1J63sklLvzAJm0X5+RpO4i3Y2hcOnFw==} + engines: {node: '>=18'} globby@11.1.0: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} @@ -4827,9 +4796,6 @@ packages: resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} engines: {node: '>=6'} - has-bigints@1.0.2: - resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} - has-binary2@1.0.3: resolution: {integrity: sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==} @@ -4983,10 +4949,6 @@ packages: inline-style-prefixer@7.0.0: resolution: {integrity: sha512-I7GEdScunP1dQ6IM2mQWh6v0mOYdYmH3Bp31UecKdrcUgcURTcctSe1IECdUznSHKSmsHtjrT3CwCPI1pyxfUQ==} - internal-slot@1.0.7: - resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} - engines: {node: '>= 0.4'} - intl-messageformat@10.5.14: resolution: {integrity: sha512-IjC6sI0X7YRjjyVH9aUgdftcmZK7WXdHeil4KwbjDnRWjnVitKpAx3rr6t6di1joFp5188VqKcobOPA6mCLG/w==} @@ -5011,24 +4973,13 @@ packages: resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} engines: {node: '>= 0.4'} - is-array-buffer@3.0.4: - resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} - engines: {node: '>= 0.4'} - is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - is-bigint@1.0.4: - resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} - is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} - is-boolean-object@1.1.2: - resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} - engines: {node: '>= 0.4'} - is-buffer@1.1.6: resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} @@ -5039,14 +4990,6 @@ packages: is-core-module@2.13.1: resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} - is-data-view@1.0.1: - resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==} - engines: {node: '>= 0.4'} - - is-date-object@1.0.5: - resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} - engines: {node: '>= 0.4'} - is-deflate@1.0.0: resolution: {integrity: sha512-YDoFpuZWu1VRXlsnlYMzKyVRITXj7Ej/V9gXQ2/pAe7X1J7M/RNOqaIYi6qUn+B7nGyB9pDXrv02dsB58d2ZAQ==} @@ -5091,14 +5034,6 @@ packages: resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==} engines: {node: '>= 0.4'} - is-negative-zero@2.0.3: - resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} - engines: {node: '>= 0.4'} - - is-number-object@1.0.7: - resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} - engines: {node: '>= 0.4'} - is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -5127,14 +5062,6 @@ packages: resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} engines: {node: '>=0.10.0'} - is-regex@1.1.4: - resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} - engines: {node: '>= 0.4'} - - is-shared-array-buffer@1.0.3: - resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} - engines: {node: '>= 0.4'} - is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} @@ -5143,14 +5070,6 @@ packages: resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - is-string@1.0.7: - resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} - engines: {node: '>= 0.4'} - - is-symbol@1.0.4: - resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} - engines: {node: '>= 0.4'} - is-text-path@1.0.1: resolution: {integrity: sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==} engines: {node: '>=0.10.0'} @@ -5166,9 +5085,6 @@ packages: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} - is-weakref@1.0.2: - resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} - is-what@3.14.1: resolution: {integrity: sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==} @@ -5185,9 +5101,6 @@ packages: isarray@2.0.1: resolution: {integrity: sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ==} - isarray@2.0.5: - resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -5439,10 +5352,6 @@ packages: json-stringify-safe@5.0.1: resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} - json5@1.0.2: - resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} - hasBin: true - json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -5959,18 +5868,6 @@ packages: resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} engines: {node: '>= 0.4'} - object.fromentries@2.0.8: - resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} - engines: {node: '>= 0.4'} - - object.groupby@1.0.3: - resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} - engines: {node: '>= 0.4'} - - object.values@1.2.0: - resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} - engines: {node: '>= 0.4'} - ohash@1.1.3: resolution: {integrity: sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw==} @@ -6248,16 +6145,20 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prettier@2.4.0: - resolution: {integrity: sha512-DsEPLY1dE5HF3BxCRBmD4uYZ+5DCbvatnolqTqcxEgKVZnL2kUfyu7b8pPQ5+hTBkdhU9SLUmK0/pHb07RE4WQ==} - engines: {node: '>=10.13.0'} - hasBin: true + prettier-linter-helpers@1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} prettier@2.8.8: resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} engines: {node: '>=10.13.0'} hasBin: true + prettier@3.3.1: + resolution: {integrity: sha512-7CAwy5dRsxs8PHXT3twixW9/OEll8MLE0VRPCJyl7CkS6VHGPSlsVaWTiASPTyGyYRyApxlaWTzwUxVNrhcwDg==} + engines: {node: '>=14'} + hasBin: true + pretty-format@29.7.0: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -6612,10 +6513,6 @@ packages: regenerator-transform@0.15.2: resolution: {integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==} - regexp.prototype.flags@1.5.2: - resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} - engines: {node: '>= 0.4'} - regexpu-core@5.3.2: resolution: {integrity: sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==} engines: {node: '>=4'} @@ -6664,9 +6561,6 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} - resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - resolve.exports@2.0.2: resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} engines: {node: '>=10'} @@ -6721,20 +6615,12 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - safe-array-concat@1.1.2: - resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} - engines: {node: '>=0.4'} - safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - safe-regex-test@1.0.3: - resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} - engines: {node: '>= 0.4'} - safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -6788,10 +6674,6 @@ packages: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} - set-function-name@2.0.2: - resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} - engines: {node: '>= 0.4'} - set-harmonic-interval@1.0.1: resolution: {integrity: sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g==} engines: {node: '>=6.9'} @@ -6959,17 +6841,6 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} - string.prototype.trim@1.2.9: - resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==} - engines: {node: '>= 0.4'} - - string.prototype.trimend@1.0.8: - resolution: {integrity: sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==} - - string.prototype.trimstart@1.0.8: - resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} - engines: {node: '>= 0.4'} - string_decoder@0.10.31: resolution: {integrity: sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==} @@ -7098,6 +6969,10 @@ packages: synchronous-promise@2.0.17: resolution: {integrity: sha512-AsS729u2RHUfEra9xJrE39peJcc2stq2+poBXX8bcM08Y6g9j/i/PUzwNQqkaJde7Ntg1TO7bSREbR5sdosQ+g==} + synckit@0.8.8: + resolution: {integrity: sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==} + engines: {node: ^14.18.0 || >=16.0.0} + table@6.8.2: resolution: {integrity: sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==} engines: {node: '>=10.0.0'} @@ -7286,9 +7161,6 @@ packages: typescript: optional: true - tsconfig-paths@3.15.0: - resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} - tsconfig-paths@4.2.0: resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} engines: {node: '>=6'} @@ -7347,10 +7219,6 @@ packages: resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} engines: {node: '>=10'} - type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - type-fest@0.21.3: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} @@ -7375,25 +7243,19 @@ packages: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} - typed-array-buffer@1.0.2: - resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} - engines: {node: '>= 0.4'} - - typed-array-byte-length@1.0.1: - resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==} - engines: {node: '>= 0.4'} - - typed-array-byte-offset@1.0.2: - resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==} - engines: {node: '>= 0.4'} - - typed-array-length@1.0.6: - resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} - engines: {node: '>= 0.4'} - typedarray@0.0.6: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + typescript-eslint@7.16.1: + resolution: {integrity: sha512-889oE5qELj65q/tGeOSvlreNKhimitFwZqQ0o7PcWC7/lgRkAMknznsCsV8J8mZGTP/Z+cIbX8accf2DE33hrA==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + typescript-plugin-css-modules@5.1.0: resolution: {integrity: sha512-6h+sLBa4l+XYSTn/31vZHd/1c3SvAbLpobY6FxDiUOHJQG1eD9Gh3eCs12+Eqc+TCOAdxcO+zAPvUq0jBfdciw==} peerDependencies: @@ -7412,9 +7274,6 @@ packages: engines: {node: '>=0.8.0'} hasBin: true - unbox-primitive@1.0.2: - resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} - undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} @@ -7654,9 +7513,6 @@ packages: whatwg-url@7.1.0: resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} - which-boxed-primitive@1.0.2: - resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} - which-typed-array@1.1.15: resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} engines: {node: '>= 0.4'} @@ -8857,19 +8713,29 @@ snapshots: '@esbuild/win32-x64@0.19.12': optional: true - '@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)': + '@eslint-community/eslint-utils@4.4.0(eslint@9.7.0)': dependencies: - eslint: 8.57.0 + eslint: 9.7.0 eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.10.1': {} - '@eslint/eslintrc@2.1.4': + '@eslint-community/regexpp@4.11.0': {} + + '@eslint/config-array@0.17.0': + dependencies: + '@eslint/object-schema': 2.1.4 + debug: 4.3.5 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/eslintrc@3.1.0': dependencies: ajv: 6.12.6 debug: 4.3.5 - espree: 9.6.1 - globals: 13.24.0 + espree: 10.1.0 + globals: 14.0.0 ignore: 5.3.1 import-fresh: 3.3.0 js-yaml: 4.1.0 @@ -8878,7 +8744,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@8.57.0': {} + '@eslint/js@9.7.0': {} + + '@eslint/object-schema@2.1.4': {} '@fal-works/esbuild-plugin-global-externals@2.1.2': {} @@ -8975,17 +8843,9 @@ snapshots: dependencies: react-hook-form: 7.52.0(react@18.3.1) - '@humanwhocodes/config-array@0.11.14': - dependencies: - '@humanwhocodes/object-schema': 2.0.3 - debug: 4.3.5 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - '@humanwhocodes/module-importer@1.0.1': {} - '@humanwhocodes/object-schema@2.0.3': {} + '@humanwhocodes/retry@0.3.0': {} '@hutson/parse-repository-url@3.0.2': {} @@ -9395,6 +9255,8 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@pkgr/core@0.1.1': {} + '@radix-ui/number@1.0.1': dependencies: '@babel/runtime': 7.24.7 @@ -11450,6 +11312,10 @@ snapshots: '@types/estree': 1.0.5 '@types/json-schema': 7.0.15 + '@types/eslint__js@8.42.3': + dependencies: + '@types/eslint': 8.56.10 + '@types/estree@0.0.51': {} '@types/estree@1.0.5': {} @@ -11505,8 +11371,6 @@ snapshots: '@types/json-schema@7.0.15': {} - '@types/json5@0.0.29': {} - '@types/lodash@4.17.5': {} '@types/mdx@2.0.13': {} @@ -11619,16 +11483,16 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(typescript@4.9.5)': + '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@9.7.0)(typescript@4.9.5))(eslint@9.7.0)(typescript@4.9.5)': dependencies: '@eslint-community/regexpp': 4.10.1 - '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@4.9.5) + '@typescript-eslint/parser': 6.21.0(eslint@9.7.0)(typescript@4.9.5) '@typescript-eslint/scope-manager': 6.21.0 - '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.0)(typescript@4.9.5) - '@typescript-eslint/utils': 6.21.0(eslint@8.57.0)(typescript@4.9.5) + '@typescript-eslint/type-utils': 6.21.0(eslint@9.7.0)(typescript@4.9.5) + '@typescript-eslint/utils': 6.21.0(eslint@9.7.0)(typescript@4.9.5) '@typescript-eslint/visitor-keys': 6.21.0 debug: 4.3.5 - eslint: 8.57.0 + eslint: 9.7.0 graphemer: 1.4.0 ignore: 5.3.1 natural-compare: 1.4.0 @@ -11639,14 +11503,45 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5)': + '@typescript-eslint/eslint-plugin@7.16.1(@typescript-eslint/parser@7.16.1(eslint@9.7.0)(typescript@4.9.5))(eslint@9.7.0)(typescript@4.9.5)': + dependencies: + '@eslint-community/regexpp': 4.10.1 + '@typescript-eslint/parser': 7.16.1(eslint@9.7.0)(typescript@4.9.5) + '@typescript-eslint/scope-manager': 7.16.1 + '@typescript-eslint/type-utils': 7.16.1(eslint@9.7.0)(typescript@4.9.5) + '@typescript-eslint/utils': 7.16.1(eslint@9.7.0)(typescript@4.9.5) + '@typescript-eslint/visitor-keys': 7.16.1 + eslint: 9.7.0 + graphemer: 1.4.0 + ignore: 5.3.1 + natural-compare: 1.4.0 + ts-api-utils: 1.3.0(typescript@4.9.5) + optionalDependencies: + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@6.21.0(eslint@9.7.0)(typescript@4.9.5)': dependencies: '@typescript-eslint/scope-manager': 6.21.0 '@typescript-eslint/types': 6.21.0 '@typescript-eslint/typescript-estree': 6.21.0(typescript@4.9.5) '@typescript-eslint/visitor-keys': 6.21.0 debug: 4.3.5 - eslint: 8.57.0 + eslint: 9.7.0 + optionalDependencies: + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@7.16.1(eslint@9.7.0)(typescript@4.9.5)': + dependencies: + '@typescript-eslint/scope-manager': 7.16.1 + '@typescript-eslint/types': 7.16.1 + '@typescript-eslint/typescript-estree': 7.16.1(typescript@4.9.5) + '@typescript-eslint/visitor-keys': 7.16.1 + debug: 4.3.5 + eslint: 9.7.0 optionalDependencies: typescript: 4.9.5 transitivePeerDependencies: @@ -11662,12 +11557,29 @@ snapshots: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/visitor-keys': 6.21.0 - '@typescript-eslint/type-utils@6.21.0(eslint@8.57.0)(typescript@4.9.5)': + '@typescript-eslint/scope-manager@7.16.1': + dependencies: + '@typescript-eslint/types': 7.16.1 + '@typescript-eslint/visitor-keys': 7.16.1 + + '@typescript-eslint/type-utils@6.21.0(eslint@9.7.0)(typescript@4.9.5)': dependencies: '@typescript-eslint/typescript-estree': 6.21.0(typescript@4.9.5) - '@typescript-eslint/utils': 6.21.0(eslint@8.57.0)(typescript@4.9.5) + '@typescript-eslint/utils': 6.21.0(eslint@9.7.0)(typescript@4.9.5) + debug: 4.3.5 + eslint: 9.7.0 + ts-api-utils: 1.3.0(typescript@4.9.5) + optionalDependencies: + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/type-utils@7.16.1(eslint@9.7.0)(typescript@4.9.5)': + dependencies: + '@typescript-eslint/typescript-estree': 7.16.1(typescript@4.9.5) + '@typescript-eslint/utils': 7.16.1(eslint@9.7.0)(typescript@4.9.5) debug: 4.3.5 - eslint: 8.57.0 + eslint: 9.7.0 ts-api-utils: 1.3.0(typescript@4.9.5) optionalDependencies: typescript: 4.9.5 @@ -11678,6 +11590,8 @@ snapshots: '@typescript-eslint/types@6.21.0': {} + '@typescript-eslint/types@7.16.1': {} + '@typescript-eslint/typescript-estree@5.62.0(typescript@4.9.5)': dependencies: '@typescript-eslint/types': 5.62.0 @@ -11707,35 +11621,61 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@5.62.0(eslint@8.57.0)(typescript@4.9.5)': + '@typescript-eslint/typescript-estree@7.16.1(typescript@4.9.5)': dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) - '@types/json-schema': 7.0.15 - '@types/semver': 7.5.8 - '@typescript-eslint/scope-manager': 5.62.0 - '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0(typescript@4.9.5) - eslint: 8.57.0 - eslint-scope: 5.1.1 + '@typescript-eslint/types': 7.16.1 + '@typescript-eslint/visitor-keys': 7.16.1 + debug: 4.3.5 + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.4 semver: 7.6.2 - transitivePeerDependencies: + ts-api-utils: 1.3.0(typescript@4.9.5) + optionalDependencies: + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@5.62.0(eslint@9.7.0)(typescript@4.9.5)': + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@9.7.0) + '@types/json-schema': 7.0.15 + '@types/semver': 7.5.8 + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/typescript-estree': 5.62.0(typescript@4.9.5) + eslint: 9.7.0 + eslint-scope: 5.1.1 + semver: 7.6.2 + transitivePeerDependencies: - supports-color - typescript - '@typescript-eslint/utils@6.21.0(eslint@8.57.0)(typescript@4.9.5)': + '@typescript-eslint/utils@6.21.0(eslint@9.7.0)(typescript@4.9.5)': dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@9.7.0) '@types/json-schema': 7.0.15 '@types/semver': 7.5.8 '@typescript-eslint/scope-manager': 6.21.0 '@typescript-eslint/types': 6.21.0 '@typescript-eslint/typescript-estree': 6.21.0(typescript@4.9.5) - eslint: 8.57.0 + eslint: 9.7.0 semver: 7.6.2 transitivePeerDependencies: - supports-color - typescript + '@typescript-eslint/utils@7.16.1(eslint@9.7.0)(typescript@4.9.5)': + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@9.7.0) + '@typescript-eslint/scope-manager': 7.16.1 + '@typescript-eslint/types': 7.16.1 + '@typescript-eslint/typescript-estree': 7.16.1(typescript@4.9.5) + eslint: 9.7.0 + transitivePeerDependencies: + - supports-color + - typescript + '@typescript-eslint/visitor-keys@5.62.0': dependencies: '@typescript-eslint/types': 5.62.0 @@ -11746,7 +11686,10 @@ snapshots: '@typescript-eslint/types': 6.21.0 eslint-visitor-keys: 3.4.3 - '@ungap/structured-clone@1.2.0': {} + '@typescript-eslint/visitor-keys@7.16.1': + dependencies: + '@typescript-eslint/types': 7.16.1 + eslint-visitor-keys: 3.4.3 '@vitejs/plugin-react@3.1.0(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2))': dependencies: @@ -11885,16 +11828,14 @@ snapshots: dependencies: acorn: 7.4.1 - acorn-jsx@5.3.2(acorn@8.12.0): + acorn-jsx@5.3.2(acorn@8.12.1): dependencies: - acorn: 8.12.0 + acorn: 8.12.1 acorn-walk@7.2.0: {} acorn@7.4.1: {} - acorn@8.12.0: {} - acorn@8.12.1: {} add-stream@1.0.0: {} @@ -11975,62 +11916,14 @@ snapshots: dependencies: tslib: 2.6.3 - array-buffer-byte-length@1.0.1: - dependencies: - call-bind: 1.0.7 - is-array-buffer: 3.0.4 - array-differ@3.0.0: {} array-flatten@1.1.1: {} array-ify@1.0.0: {} - array-includes@3.1.8: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-object-atoms: 1.0.0 - get-intrinsic: 1.2.4 - is-string: 1.0.7 - array-union@2.1.0: {} - array.prototype.findlastindex@1.2.5: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-errors: 1.3.0 - es-object-atoms: 1.0.0 - es-shim-unscopables: 1.0.2 - - array.prototype.flat@1.3.2: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-shim-unscopables: 1.0.2 - - array.prototype.flatmap@1.3.2: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-shim-unscopables: 1.0.2 - - arraybuffer.prototype.slice@1.0.3: - dependencies: - array-buffer-byte-length: 1.0.1 - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-errors: 1.3.0 - get-intrinsic: 1.2.4 - is-array-buffer: 3.0.4 - is-shared-array-buffer: 1.0.3 - arraybuffer.slice@0.0.7: {} arrify@1.0.1: {} @@ -12721,24 +12614,6 @@ snapshots: data-uri-to-buffer@0.0.3: {} - data-view-buffer@1.0.1: - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - is-data-view: 1.0.1 - - data-view-byte-length@1.0.1: - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - is-data-view: 1.0.1 - - data-view-byte-offset@1.0.0: - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - is-data-view: 1.0.1 - dateformat@3.0.3: {} dayjs@1.11.11: {} @@ -12751,10 +12626,6 @@ snapshots: dependencies: ms: 2.0.0 - debug@3.2.7: - dependencies: - ms: 2.1.3 - debug@4.3.4: dependencies: ms: 2.1.2 @@ -12843,10 +12714,6 @@ snapshots: dependencies: path-type: 4.0.0 - doctrine@2.1.0: - dependencies: - esutils: 2.0.3 - doctrine@3.0.0: dependencies: esutils: 2.0.3 @@ -12973,55 +12840,6 @@ snapshots: dependencies: stackframe: 1.3.4 - es-abstract@1.23.3: - dependencies: - array-buffer-byte-length: 1.0.1 - arraybuffer.prototype.slice: 1.0.3 - available-typed-arrays: 1.0.7 - call-bind: 1.0.7 - data-view-buffer: 1.0.1 - data-view-byte-length: 1.0.1 - data-view-byte-offset: 1.0.0 - es-define-property: 1.0.0 - es-errors: 1.3.0 - es-object-atoms: 1.0.0 - es-set-tostringtag: 2.0.3 - es-to-primitive: 1.2.1 - function.prototype.name: 1.1.6 - get-intrinsic: 1.2.4 - get-symbol-description: 1.0.2 - globalthis: 1.0.4 - gopd: 1.0.1 - has-property-descriptors: 1.0.2 - has-proto: 1.0.3 - has-symbols: 1.0.3 - hasown: 2.0.2 - internal-slot: 1.0.7 - is-array-buffer: 3.0.4 - is-callable: 1.2.7 - is-data-view: 1.0.1 - is-negative-zero: 2.0.3 - is-regex: 1.1.4 - is-shared-array-buffer: 1.0.3 - is-string: 1.0.7 - is-typed-array: 1.1.13 - is-weakref: 1.0.2 - object-inspect: 1.13.1 - object-keys: 1.1.1 - object.assign: 4.1.5 - regexp.prototype.flags: 1.5.2 - safe-array-concat: 1.1.2 - safe-regex-test: 1.0.3 - string.prototype.trim: 1.2.9 - string.prototype.trimend: 1.0.8 - string.prototype.trimstart: 1.0.8 - typed-array-buffer: 1.0.2 - typed-array-byte-length: 1.0.1 - typed-array-byte-offset: 1.0.2 - typed-array-length: 1.0.6 - unbox-primitive: 1.0.2 - which-typed-array: 1.1.15 - es-define-property@1.0.0: dependencies: get-intrinsic: 1.2.4 @@ -13032,26 +12850,6 @@ snapshots: es-module-lexer@1.5.4: {} - es-object-atoms@1.0.0: - dependencies: - es-errors: 1.3.0 - - es-set-tostringtag@2.0.3: - dependencies: - get-intrinsic: 1.2.4 - has-tostringtag: 1.0.2 - hasown: 2.0.2 - - es-shim-unscopables@1.0.2: - dependencies: - hasown: 2.0.2 - - es-to-primitive@1.2.1: - dependencies: - is-callable: 1.2.7 - is-date-object: 1.0.5 - is-symbol: 1.0.4 - esbuild-plugin-alias@0.2.1: {} esbuild-plugin-replace@1.4.0: @@ -13134,128 +12932,73 @@ snapshots: optionalDependencies: source-map: 0.6.1 - eslint-config-prettier@9.1.0(eslint@8.57.0): - dependencies: - eslint: 8.57.0 - - eslint-import-resolver-node@0.3.9: + eslint-config-prettier@9.1.0(eslint@9.7.0): dependencies: - debug: 3.2.7 - is-core-module: 2.13.1 - resolve: 1.22.8 - transitivePeerDependencies: - - supports-color - - eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint-plugin-import@2.29.1)(eslint@8.57.0): - dependencies: - debug: 4.3.5 - enhanced-resolve: 5.17.0 - eslint: 8.57.0 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) - fast-glob: 3.3.2 - get-tsconfig: 4.7.5 - is-core-module: 2.13.1 - is-glob: 4.0.3 - transitivePeerDependencies: - - '@typescript-eslint/parser' - - eslint-import-resolver-node - - eslint-import-resolver-webpack - - supports-color + eslint: 9.7.0 - eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0): + eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@9.7.0)(typescript@4.9.5))(eslint@9.7.0)(typescript@4.9.5))(eslint@9.7.0)(jest@29.7.0(@types/node@20.14.4))(typescript@4.9.5): dependencies: - debug: 3.2.7 + '@typescript-eslint/utils': 5.62.0(eslint@9.7.0)(typescript@4.9.5) + eslint: 9.7.0 optionalDependencies: - '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@4.9.5) - eslint: 8.57.0 - eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint-plugin-import@2.29.1)(eslint@8.57.0) - transitivePeerDependencies: - - supports-color - - eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): - dependencies: - array-includes: 3.1.8 - array.prototype.findlastindex: 1.2.5 - array.prototype.flat: 1.3.2 - array.prototype.flatmap: 1.3.2 - debug: 3.2.7 - doctrine: 2.1.0 - eslint: 8.57.0 - eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) - hasown: 2.0.2 - is-core-module: 2.13.1 - is-glob: 4.0.3 - minimatch: 3.1.2 - object.fromentries: 2.0.8 - object.groupby: 1.0.3 - object.values: 1.2.0 - semver: 6.3.1 - tsconfig-paths: 3.15.0 - optionalDependencies: - '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@4.9.5) + '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@9.7.0)(typescript@4.9.5))(eslint@9.7.0)(typescript@4.9.5) + jest: 29.7.0(@types/node@20.14.4) transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - supports-color + - typescript - eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(jest@29.7.0(@types/node@20.14.4))(typescript@4.9.5): + eslint-plugin-prettier@5.1.3(@types/eslint@8.56.10)(eslint-config-prettier@9.1.0(eslint@9.7.0))(eslint@9.7.0)(prettier@3.3.1): dependencies: - '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@4.9.5) - eslint: 8.57.0 + eslint: 9.7.0 + prettier: 3.3.1 + prettier-linter-helpers: 1.0.0 + synckit: 0.8.8 optionalDependencies: - '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(typescript@4.9.5) - jest: 29.7.0(@types/node@20.14.4) - transitivePeerDependencies: - - supports-color - - typescript + '@types/eslint': 8.56.10 + eslint-config-prettier: 9.1.0(eslint@9.7.0) eslint-scope@5.1.1: dependencies: esrecurse: 4.3.0 estraverse: 4.3.0 - eslint-scope@7.2.2: + eslint-scope@8.0.2: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 eslint-visitor-keys@3.4.3: {} - eslint@8.57.0: + eslint-visitor-keys@4.0.0: {} + + eslint@9.7.0: dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) - '@eslint-community/regexpp': 4.10.1 - '@eslint/eslintrc': 2.1.4 - '@eslint/js': 8.57.0 - '@humanwhocodes/config-array': 0.11.14 + '@eslint-community/eslint-utils': 4.4.0(eslint@9.7.0) + '@eslint-community/regexpp': 4.11.0 + '@eslint/config-array': 0.17.0 + '@eslint/eslintrc': 3.1.0 + '@eslint/js': 9.7.0 '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.3.0 '@nodelib/fs.walk': 1.2.8 - '@ungap/structured-clone': 1.2.0 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 debug: 4.3.5 - doctrine: 3.0.0 escape-string-regexp: 4.0.0 - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 + eslint-scope: 8.0.2 + eslint-visitor-keys: 4.0.0 + espree: 10.1.0 esquery: 1.5.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 + file-entry-cache: 8.0.0 find-up: 5.0.0 glob-parent: 6.0.2 - globals: 13.24.0 - graphemer: 1.4.0 ignore: 5.3.1 imurmurhash: 0.1.4 is-glob: 4.0.3 is-path-inside: 3.0.3 - js-yaml: 4.1.0 json-stable-stringify-without-jsonify: 1.0.1 levn: 0.4.1 lodash.merge: 4.6.2 @@ -13267,11 +13010,11 @@ snapshots: transitivePeerDependencies: - supports-color - espree@9.6.1: + espree@10.1.0: dependencies: - acorn: 8.12.0 - acorn-jsx: 5.3.2(acorn@8.12.0) - eslint-visitor-keys: 3.4.3 + acorn: 8.12.1 + acorn-jsx: 5.3.2(acorn@8.12.1) + eslint-visitor-keys: 4.0.0 esprima@4.0.1: {} @@ -13394,6 +13137,8 @@ snapshots: fast-deep-equal@3.1.3: {} + fast-diff@1.3.0: {} + fast-glob@3.3.2: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -13432,9 +13177,9 @@ snapshots: dependencies: escape-string-regexp: 1.0.5 - file-entry-cache@6.0.1: + file-entry-cache@8.0.0: dependencies: - flat-cache: 3.2.0 + flat-cache: 4.0.1 file-entry-cache@9.0.0: dependencies: @@ -13503,11 +13248,10 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 - flat-cache@3.2.0: + flat-cache@4.0.1: dependencies: flatted: 3.3.1 keyv: 4.5.4 - rimraf: 3.0.2 flat-cache@5.0.0: dependencies: @@ -13584,15 +13328,6 @@ snapshots: function-bind@1.1.2: {} - function.prototype.name@1.1.6: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - functions-have-names: 1.2.3 - - functions-have-names@1.2.3: {} - gensync@1.0.0-beta.2: {} get-caller-file@2.0.5: {} @@ -13638,16 +13373,6 @@ snapshots: get-stream@8.0.1: {} - get-symbol-description@1.0.2: - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - get-intrinsic: 1.2.4 - - get-tsconfig@4.7.5: - dependencies: - resolve-pkg-maps: 1.0.0 - getpass@0.1.7: dependencies: assert-plus: 1.0.0 @@ -13731,14 +13456,9 @@ snapshots: globals@11.12.0: {} - globals@13.24.0: - dependencies: - type-fest: 0.20.2 + globals@14.0.0: {} - globalthis@1.0.4: - dependencies: - define-properties: 1.2.1 - gopd: 1.0.1 + globals@15.8.0: {} globby@11.1.0: dependencies: @@ -13788,8 +13508,6 @@ snapshots: hard-rejection@2.1.0: {} - has-bigints@1.0.2: {} - has-binary2@1.0.3: dependencies: isarray: 2.0.1 @@ -13924,12 +13642,6 @@ snapshots: css-in-js-utils: 3.1.0 fast-loops: 1.1.3 - internal-slot@1.0.7: - dependencies: - es-errors: 1.3.0 - hasown: 2.0.2 - side-channel: 1.0.6 - intl-messageformat@10.5.14: dependencies: '@formatjs/ecma402-abstract': 2.0.0 @@ -13954,26 +13666,12 @@ snapshots: call-bind: 1.0.7 has-tostringtag: 1.0.2 - is-array-buffer@3.0.4: - dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 - is-arrayish@0.2.1: {} - is-bigint@1.0.4: - dependencies: - has-bigints: 1.0.2 - is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 - is-boolean-object@1.1.2: - dependencies: - call-bind: 1.0.7 - has-tostringtag: 1.0.2 - is-buffer@1.1.6: {} is-callable@1.2.7: {} @@ -13982,14 +13680,6 @@ snapshots: dependencies: hasown: 2.0.2 - is-data-view@1.0.1: - dependencies: - is-typed-array: 1.1.13 - - is-date-object@1.0.5: - dependencies: - has-tostringtag: 1.0.2 - is-deflate@1.0.0: {} is-docker@2.2.1: {} @@ -14019,12 +13709,6 @@ snapshots: call-bind: 1.0.7 define-properties: 1.2.1 - is-negative-zero@2.0.3: {} - - is-number-object@1.0.7: - dependencies: - has-tostringtag: 1.0.2 - is-number@7.0.0: {} is-obj@2.0.0: {} @@ -14041,27 +13725,10 @@ snapshots: is-plain-object@5.0.0: {} - is-regex@1.1.4: - dependencies: - call-bind: 1.0.7 - has-tostringtag: 1.0.2 - - is-shared-array-buffer@1.0.3: - dependencies: - call-bind: 1.0.7 - is-stream@2.0.1: {} is-stream@3.0.0: {} - is-string@1.0.7: - dependencies: - has-tostringtag: 1.0.2 - - is-symbol@1.0.4: - dependencies: - has-symbols: 1.0.3 - is-text-path@1.0.1: dependencies: text-extensions: 1.9.0 @@ -14074,10 +13741,6 @@ snapshots: is-unicode-supported@0.1.0: {} - is-weakref@1.0.2: - dependencies: - call-bind: 1.0.7 - is-what@3.14.1: {} is-wsl@2.2.0: @@ -14090,8 +13753,6 @@ snapshots: isarray@2.0.1: {} - isarray@2.0.5: {} - isexe@2.0.0: {} isobject@3.0.1: {} @@ -14538,10 +14199,6 @@ snapshots: json-stringify-safe@5.0.1: {} - json5@1.0.2: - dependencies: - minimist: 1.2.8 - json5@2.2.3: {} jsonfile@6.1.0: @@ -15065,25 +14722,6 @@ snapshots: has-symbols: 1.0.3 object-keys: 1.1.1 - object.fromentries@2.0.8: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-object-atoms: 1.0.0 - - object.groupby@1.0.3: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - - object.values@1.2.0: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-object-atoms: 1.0.0 - ohash@1.1.3: {} omggif@1.0.10: {} @@ -15325,10 +14963,14 @@ snapshots: prelude-ls@1.2.1: {} - prettier@2.4.0: {} + prettier-linter-helpers@1.0.0: + dependencies: + fast-diff: 1.3.0 prettier@2.8.8: {} + prettier@3.3.1: {} + pretty-format@29.7.0: dependencies: '@jest/schemas': 29.6.3 @@ -15820,13 +15462,6 @@ snapshots: dependencies: '@babel/runtime': 7.24.7 - regexp.prototype.flags@1.5.2: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-errors: 1.3.0 - set-function-name: 2.0.2 - regexpu-core@5.3.2: dependencies: '@babel/regjsgen': 0.8.0 @@ -15895,8 +15530,6 @@ snapshots: resolve-from@5.0.0: {} - resolve-pkg-maps@1.0.0: {} - resolve.exports@2.0.2: {} resolve@1.22.8: @@ -15965,23 +15598,10 @@ snapshots: dependencies: queue-microtask: 1.2.3 - safe-array-concat@1.1.2: - dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 - has-symbols: 1.0.3 - isarray: 2.0.5 - safe-buffer@5.1.2: {} safe-buffer@5.2.1: {} - safe-regex-test@1.0.3: - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - is-regex: 1.1.4 - safer-buffer@2.1.2: {} sass@1.77.6: @@ -16053,13 +15673,6 @@ snapshots: gopd: 1.0.1 has-property-descriptors: 1.0.2 - set-function-name@2.0.2: - dependencies: - define-data-property: 1.1.4 - es-errors: 1.3.0 - functions-have-names: 1.2.3 - has-property-descriptors: 1.0.2 - set-harmonic-interval@1.0.1: {} setprototypeof@1.2.0: {} @@ -16269,25 +15882,6 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.0 - string.prototype.trim@1.2.9: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-object-atoms: 1.0.0 - - string.prototype.trimend@1.0.8: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-object-atoms: 1.0.0 - - string.prototype.trimstart@1.0.8: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-object-atoms: 1.0.0 - string_decoder@0.10.31: {} string_decoder@1.1.1: @@ -16457,6 +16051,11 @@ snapshots: synchronous-promise@2.0.17: {} + synckit@0.8.8: + dependencies: + '@pkgr/core': 0.1.1 + tslib: 2.6.3 + table@6.8.2: dependencies: ajv: 8.16.0 @@ -16631,13 +16230,6 @@ snapshots: optionalDependencies: typescript: 4.9.5 - tsconfig-paths@3.15.0: - dependencies: - '@types/json5': 0.0.29 - json5: 1.0.2 - minimist: 1.2.8 - strip-bom: 3.0.0 - tsconfig-paths@4.2.0: dependencies: json5: 2.2.3 @@ -16694,8 +16286,6 @@ snapshots: type-fest@0.18.1: {} - type-fest@0.20.2: {} - type-fest@0.21.3: {} type-fest@0.6.0: {} @@ -16711,39 +16301,18 @@ snapshots: media-typer: 0.3.0 mime-types: 2.1.35 - typed-array-buffer@1.0.2: - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - is-typed-array: 1.1.13 - - typed-array-byte-length@1.0.1: - dependencies: - call-bind: 1.0.7 - for-each: 0.3.3 - gopd: 1.0.1 - has-proto: 1.0.3 - is-typed-array: 1.1.13 - - typed-array-byte-offset@1.0.2: - dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.7 - for-each: 0.3.3 - gopd: 1.0.1 - has-proto: 1.0.3 - is-typed-array: 1.1.13 + typedarray@0.0.6: {} - typed-array-length@1.0.6: + typescript-eslint@7.16.1(eslint@9.7.0)(typescript@4.9.5): dependencies: - call-bind: 1.0.7 - for-each: 0.3.3 - gopd: 1.0.1 - has-proto: 1.0.3 - is-typed-array: 1.1.13 - possible-typed-array-names: 1.0.0 - - typedarray@0.0.6: {} + '@typescript-eslint/eslint-plugin': 7.16.1(@typescript-eslint/parser@7.16.1(eslint@9.7.0)(typescript@4.9.5))(eslint@9.7.0)(typescript@4.9.5) + '@typescript-eslint/parser': 7.16.1(eslint@9.7.0)(typescript@4.9.5) + '@typescript-eslint/utils': 7.16.1(eslint@9.7.0)(typescript@4.9.5) + eslint: 9.7.0 + optionalDependencies: + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color typescript-plugin-css-modules@5.1.0(typescript@4.9.5): dependencies: @@ -16775,13 +16344,6 @@ snapshots: uglify-js@3.18.0: optional: true - unbox-primitive@1.0.2: - dependencies: - call-bind: 1.0.7 - has-bigints: 1.0.2 - has-symbols: 1.0.3 - which-boxed-primitive: 1.0.2 - undici-types@5.26.5: {} unicode-canonical-property-names-ecmascript@2.0.0: {} @@ -16820,7 +16382,7 @@ snapshots: unplugin@1.10.1: dependencies: - acorn: 8.12.0 + acorn: 8.12.1 chokidar: 3.6.0 webpack-sources: 3.2.3 webpack-virtual-modules: 0.6.2 @@ -17018,14 +16580,6 @@ snapshots: tr46: 1.0.1 webidl-conversions: 4.0.2 - which-boxed-primitive@1.0.2: - dependencies: - is-bigint: 1.0.4 - is-boolean-object: 1.1.2 - is-number-object: 1.0.7 - is-string: 1.0.7 - is-symbol: 1.0.4 - which-typed-array@1.1.15: dependencies: available-typed-arrays: 1.0.7 diff --git a/src/chat/components/UserAvatar/index.tsx b/src/chat/components/UserAvatar/index.tsx index becd694f4..cc5f6e676 100644 --- a/src/chat/components/UserAvatar/index.tsx +++ b/src/chat/components/UserAvatar/index.tsx @@ -4,7 +4,7 @@ import UiKitAvatar from '~/core/components/Avatar'; import { SIZE_ALIAS } from '~/core/hocs/withSize'; export interface UserAvatarProps { - size?: typeof SIZE_ALIAS[keyof typeof SIZE_ALIAS] | null; + size?: (typeof SIZE_ALIAS)[keyof typeof SIZE_ALIAS] | null; avatarUrl?: string | null; avatarFileId?: string | null; avatarFile?: File | null; diff --git a/src/core/components/InputText/InsideInputText.tsx b/src/core/components/InputText/InsideInputText.tsx index 4c81f5564..c6e9595be 100644 --- a/src/core/components/InputText/InsideInputText.tsx +++ b/src/core/components/InputText/InsideInputText.tsx @@ -15,7 +15,9 @@ const Container = styled.div` background: ${({ theme }) => theme.palette.base.shade4}; border: 1px solid #e3e4e8; border-radius: 4px; - transition: background 0.2s, border-color 0.2s; + transition: + background 0.2s, + border-color 0.2s; ${({ theme }) => theme.typography.global} diff --git a/src/core/components/Uploaders/File/File.stories.tsx b/src/core/components/Uploaders/File/File.stories.tsx index de492fcd6..da076f285 100644 --- a/src/core/components/Uploaders/File/File.stories.tsx +++ b/src/core/components/Uploaders/File/File.stories.tsx @@ -10,7 +10,7 @@ export default { export const SimpleFile = { render: () => { const [{ remove, ...args }] = useArgs(); - // eslint-disable-next-line no-param-reassign + if (!remove) delete args.onRemove; return <UiKitFile {...args} />; }, diff --git a/src/core/components/Uploaders/Image/Image.stories.tsx b/src/core/components/Uploaders/Image/Image.stories.tsx index 342440d47..e494134e7 100644 --- a/src/core/components/Uploaders/Image/Image.stories.tsx +++ b/src/core/components/Uploaders/Image/Image.stories.tsx @@ -11,7 +11,7 @@ export const SimpleImage = { render: () => { const [props] = useArgs(); const { remove: removeFn, ...rest } = props; - // eslint-disable-next-line no-param-reassign + if (!removeFn) { return <UiKitImage {...rest} />; } diff --git a/src/core/components/Uploaders/Image/styles.tsx b/src/core/components/Uploaders/Image/styles.tsx index 3c9847cac..da6a7cc54 100644 --- a/src/core/components/Uploaders/Image/styles.tsx +++ b/src/core/components/Uploaders/Image/styles.tsx @@ -71,7 +71,10 @@ export const ImageSkeleton = () => ( </SizeMe> ); -const StyledRemoveIcon = styled(RemoveIcon).attrs<{ icon?: ReactNode }>({width: 24, height: 24})``; +const StyledRemoveIcon = styled(RemoveIcon).attrs<{ icon?: ReactNode }>({ + width: 24, + height: 24, +})``; export const RemoveButton = styled(Button).attrs<{ variant?: string; diff --git a/src/core/components/Uploaders/Video/Video.stories.tsx b/src/core/components/Uploaders/Video/Video.stories.tsx index 4a5c3a401..2ee1db2d4 100644 --- a/src/core/components/Uploaders/Video/Video.stories.tsx +++ b/src/core/components/Uploaders/Video/Video.stories.tsx @@ -10,7 +10,7 @@ export default { export const VideoStory = { render: () => { const [{ remove, ...args }] = useArgs(); - // eslint-disable-next-line no-param-reassign + if (!remove) delete args.onRemove; return <UiKitVideo {...args} />; }, diff --git a/src/core/components/Uploaders/Video/styles.tsx b/src/core/components/Uploaders/Video/styles.tsx index 2c5ff7faa..020702fd5 100644 --- a/src/core/components/Uploaders/Video/styles.tsx +++ b/src/core/components/Uploaders/Video/styles.tsx @@ -83,7 +83,7 @@ export const VideoSkeleton = () => ( </SizeMe> ); -const StyledRemoveIcon = styled(Remove).attrs<{ icon?: ReactNode }>({width: 24, height: 24})``; +const StyledRemoveIcon = styled(Remove).attrs<{ icon?: ReactNode }>({ width: 24, height: 24 })``; export const RemoveButton = styled(Button).attrs<{ variant?: string; diff --git a/src/core/components/UserSelector/index.tsx b/src/core/components/UserSelector/index.tsx index 12906e1b3..976fbb823 100644 --- a/src/core/components/UserSelector/index.tsx +++ b/src/core/components/UserSelector/index.tsx @@ -14,8 +14,8 @@ interface UserSelectorProps { onChange?: (newValues: string[]) => void; } -const UserSelector = ({ value, onChange }: UserSelectorProps) => { - const [selectedUserIds, setSelectedUserIds] = useState<string[]>([] || value); +const UserSelector = ({ value = [], onChange }: UserSelectorProps) => { + const [selectedUserIds, setSelectedUserIds] = useState<string[]>(value); const [query, setQuery] = useState(''); diff --git a/src/core/hooks/useUser.ts b/src/core/hooks/useUser.ts index d79f9e2df..a60ea405c 100644 --- a/src/core/hooks/useUser.ts +++ b/src/core/hooks/useUser.ts @@ -1,5 +1,3 @@ -/* eslint-disable no-nested-ternary */ - import { SubscriptionLevels, UserRepository } from '@amityco/ts-sdk'; import useLiveObject from './useLiveObject'; diff --git a/src/core/providers/UiKitProvider/theme/palette.ts b/src/core/providers/UiKitProvider/theme/palette.ts index 3c08eff62..edbbc23a0 100644 --- a/src/core/providers/UiKitProvider/theme/palette.ts +++ b/src/core/providers/UiKitProvider/theme/palette.ts @@ -45,7 +45,7 @@ export const lightenHex = (lightenAmount: number, hexColorString: string) => { // Lightness values for color variations. export const COLOR_SHADES = [0.25, 0.4, 0.5, 0.75]; -type PaletteTheme = typeof defaultTheme['palette']; +type PaletteTheme = (typeof defaultTheme)['palette']; /** * Converts all colors from hex format to hsl/hsla format. @@ -70,11 +70,14 @@ export const buildPaletteTheme = (mergedPaletteTheme: PaletteTheme) => { const colorThemeExtended = (Object.keys(hslColorTheme) as string[]).reduce( (acc, colorKey) => { const currentHexColor = otherColors[colorKey as keyof typeof otherColors]; - const shades = COLOR_SHADES.reduce((shadeAcc, shade, index) => { - const shadeKey = `shade${index + 1}`; - shadeAcc[shadeKey] = lightenHex(shade, currentHexColor); - return shadeAcc; - }, {} as Record<string, string>); + const shades = COLOR_SHADES.reduce( + (shadeAcc, shade, index) => { + const shadeKey = `shade${index + 1}`; + shadeAcc[shadeKey] = lightenHex(shade, currentHexColor); + return shadeAcc; + }, + {} as Record<string, string>, + ); const main = hslColorTheme[colorKey]; if (main && typeof main === 'object') { acc[colorKey] = { ...shades, ...main }; diff --git a/src/core/providers/UiKitProvider/theme/typography.ts b/src/core/providers/UiKitProvider/theme/typography.ts index d9ef96992..72c2b5b05 100644 --- a/src/core/providers/UiKitProvider/theme/typography.ts +++ b/src/core/providers/UiKitProvider/theme/typography.ts @@ -12,13 +12,16 @@ export const filterStyleKeys = (styleObject: { fontWeight?: string | number; fontSize?: string; }) => { - const filteredStyleObject = Object.keys(styleObject).reduce((acc, styleKey) => { - const styleObjectForKey = styleObject[styleKey]; - if (styleKey && styleObjectForKey && ALLOWED_PROPERTIES.includes(styleKey)) { - acc[styleKey] = styleObjectForKey; - } - return acc; - }, {} as Record<string, string | number>); + const filteredStyleObject = Object.keys(styleObject).reduce( + (acc, styleKey) => { + const styleObjectForKey = styleObject[styleKey]; + if (styleKey && styleObjectForKey && ALLOWED_PROPERTIES.includes(styleKey)) { + acc[styleKey] = styleObjectForKey; + } + return acc; + }, + {} as Record<string, string | number>, + ); return filteredStyleObject; }; @@ -35,13 +38,16 @@ export const buildTypographyTheme = (mergedTypographyTheme: { fontSize?: string; }; }) => { - const filteredTheme = Object.keys(mergedTypographyTheme).reduce((acc, curr) => { - acc[curr] = filterStyleKeys({ - ...mergedTypographyTheme.global, - ...mergedTypographyTheme[curr], - }); - return acc; - }, {} as Record<string, Record<string, string | number>>); + const filteredTheme = Object.keys(mergedTypographyTheme).reduce( + (acc, curr) => { + acc[curr] = filterStyleKeys({ + ...mergedTypographyTheme.global, + ...mergedTypographyTheme[curr], + }); + return acc; + }, + {} as Record<string, Record<string, string | number>>, + ); return filteredTheme; }; diff --git a/src/social/components/ExploreHeader/index.tsx b/src/social/components/ExploreHeader/index.tsx index 3bf385ed9..44f690fb1 100644 --- a/src/social/components/ExploreHeader/index.tsx +++ b/src/social/components/ExploreHeader/index.tsx @@ -19,7 +19,9 @@ import { useNavigation } from '~/social/providers/NavigationProvider'; const Background = styled.div` width: 100%; height: 100%; - background: ${SkyBg} right bottom no-repeat, linear-gradient(to right, #111f48 0, #2b4491 100%); + background: + ${SkyBg} right bottom no-repeat, + linear-gradient(to right, #111f48 0, #2b4491 100%); `; const Foreground = styled.div` diff --git a/src/social/components/Images/styles.tsx b/src/social/components/Images/styles.tsx index c527f6a95..118783c66 100644 --- a/src/social/components/Images/styles.tsx +++ b/src/social/components/Images/styles.tsx @@ -22,7 +22,7 @@ export const CloseIcon = styled(Close).attrs({ width: 18, height: 18 })` fill: #fff; `; -export const ImageContainer = styled.div<{ editing?: Boolean }>` +export const ImageContainer = styled.div<{ editing?: boolean }>` position: relative; overflow: hidden; ${({ editing }) => !editing && 'cursor: pointer;'} @@ -148,9 +148,9 @@ export const FileInput = styled.input.attrs({ type: 'file' })` } `; -export const Label = styled.label<{ disabled?: Boolean }>``; +export const Label = styled.label<{ disabled?: boolean }>``; -export const ImageIcon = styled(ImageAttachment).attrs<{ disabled?: Boolean; icon?: ReactNode }>({ +export const ImageIcon = styled(ImageAttachment).attrs<{ disabled?: boolean; icon?: ReactNode }>({ width: 18, height: 18, })` diff --git a/src/social/components/community/Card/styles.tsx b/src/social/components/community/Card/styles.tsx index c7865f1a3..c19fb2c74 100644 --- a/src/social/components/community/Card/styles.tsx +++ b/src/social/components/community/Card/styles.tsx @@ -6,7 +6,9 @@ export const Container = styled.div` min-width: 278px; min-height: 289px; cursor: pointer; - box-shadow: 0 0 1px rgba(40, 41, 61, 0.08), 0 0.5px 2px rgba(96, 97, 112, 0.16); + box-shadow: + 0 0 1px rgba(40, 41, 61, 0.08), + 0 0.5px 2px rgba(96, 97, 112, 0.16); border-radius: 8px; background: ${({ theme }) => theme.palette.system.background}; overflow: hidden; diff --git a/src/social/components/post/Creator/components/ImagesUploaded.tsx b/src/social/components/post/Creator/components/ImagesUploaded.tsx index 1ffe59ab7..d0048a7f2 100644 --- a/src/social/components/post/Creator/components/ImagesUploaded.tsx +++ b/src/social/components/post/Creator/components/ImagesUploaded.tsx @@ -71,7 +71,14 @@ interface ImagesUploadedProps { onError: (error: string) => void; } -const ImagesUploaded = ({ files, uploadedFiles, onChange, onLoadingChange, uploadLoading, onError }: ImagesUploadedProps) => { +const ImagesUploaded = ({ + files, + uploadedFiles, + onChange, + onLoadingChange, + uploadLoading, + onError, +}: ImagesUploadedProps) => { const useFileUploadProps = useFileUpload({ files, uploadedFiles, @@ -84,7 +91,7 @@ const ImagesUploaded = ({ files, uploadedFiles, onChange, onLoadingChange, uploa if (allFiles.length === 0) return null; - return <ImagesGallery {...useFileUploadProps} uploadLoading={uploadLoading} /> + return <ImagesGallery {...useFileUploadProps} uploadLoading={uploadLoading} />; }; export default ImagesUploaded; diff --git a/src/social/components/post/Creator/index.tsx b/src/social/components/post/Creator/index.tsx index e9b43aa8f..c86652f04 100644 --- a/src/social/components/post/Creator/index.tsx +++ b/src/social/components/post/Creator/index.tsx @@ -120,9 +120,8 @@ const PostCreatorBar = ({ // default to me if (targetType === 'global' || targetType === 'myFeed') { - /* eslint-disable no-param-reassign */ targetType = 'user'; - /* eslint-disable no-param-reassign */ + targetId = currentUserId || ''; } diff --git a/src/social/components/post/Editor/Content/index.tsx b/src/social/components/post/Editor/Content/index.tsx index d3e3cd40a..727036894 100644 --- a/src/social/components/post/Editor/Content/index.tsx +++ b/src/social/components/post/Editor/Content/index.tsx @@ -14,13 +14,13 @@ const ALL_DATA_TYPE = [...TEXT_DATA_TYPE, ...BASE_DATA_TYPE] as const; type ItemsProps = { data: Array<unknown>; - dataType: typeof BASE_DATA_TYPE[number]; + dataType: (typeof BASE_DATA_TYPE)[number]; onRemoveChild: (postId: string) => void; }; type ItemProps = { data: unknown; - dataType: typeof BASE_DATA_TYPE[number]; + dataType: (typeof BASE_DATA_TYPE)[number]; placeholder: string; onChangeText: (newValue: { text: string; @@ -37,7 +37,7 @@ type ItemProps = { type TextItemProps = { data?: string | null; - dataType: typeof TEXT_DATA_TYPE[number]; + dataType: (typeof TEXT_DATA_TYPE)[number]; placeholder?: string | null; onChangeText?: (newValue: { text: string; diff --git a/src/social/pages/NewsFeed/styles.tsx b/src/social/pages/NewsFeed/styles.tsx index fb6397818..ee9f08309 100644 --- a/src/social/pages/NewsFeed/styles.tsx +++ b/src/social/pages/NewsFeed/styles.tsx @@ -41,7 +41,9 @@ export const CommunitySideMenuOverlay = styled.div<{ isOpen: boolean }>` z-index: 998; opacity: ${({ isOpen }) => (isOpen ? 1 : 0)}; visibility: ${({ isOpen }) => (isOpen ? 'visible' : 'hidden')}; - transition: opacity 0.3s ease-in-out, visibility 0.3s ease-in-out; + transition: + opacity 0.3s ease-in-out, + visibility 0.3s ease-in-out; cursor: pointer; `; diff --git a/src/utils.ts b/src/utils.ts index 5b97409f0..fe94123a8 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -126,7 +126,7 @@ const SUPPORTED_URL_PROTOCOLS = new Set(['http:', 'https:', 'mailto:', 'sms:', ' export function sanitizeUrl(url: string): string { try { const parsedUrl = new URL(url); - // eslint-disable-next-line no-script-url + if (!SUPPORTED_URL_PROTOCOLS.has(parsedUrl.protocol)) { return 'about:blank'; } diff --git a/src/v4/chat/components/ChatHeader/ChatHeader.stories.tsx b/src/v4/chat/components/ChatHeader/ChatHeader.stories.tsx index 48e95a9fa..ef6b52282 100644 --- a/src/v4/chat/components/ChatHeader/ChatHeader.stories.tsx +++ b/src/v4/chat/components/ChatHeader/ChatHeader.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; import useChannelsCollection from '~/chat/hooks/collections/useChannelsCollection'; -import { ChatHeader } from './index'; +import { ChatHeader } from './'; export default { title: 'V4/ChatHeader', diff --git a/src/v4/chat/components/ChatHeader/ChatHeader.tsx b/src/v4/chat/components/ChatHeader/ChatHeader.tsx index dd4903b7f..e8737ba17 100644 --- a/src/v4/chat/components/ChatHeader/ChatHeader.tsx +++ b/src/v4/chat/components/ChatHeader/ChatHeader.tsx @@ -7,7 +7,7 @@ import ConnectionSpinner from '~/v4/icons/ConnectionSpinner'; import { Typography } from '~/v4/core/components'; import useChatInfo from '~/v4/chat/hooks/useChatInfo'; import { Avatar } from '~/v4/core/components/Avatar/Avatar'; -import { useAmityComponent } from '~/v4/core/hooks/uikit/index'; +import { useAmityComponent } from '~/v4/core/hooks/uikit'; import styles from './ChatHeader.module.css'; interface ChatHeaderProps { diff --git a/src/v4/chat/components/MessageComposer/MessageComposer.tsx b/src/v4/chat/components/MessageComposer/MessageComposer.tsx index d3bd68539..a2994ada8 100644 --- a/src/v4/chat/components/MessageComposer/MessageComposer.tsx +++ b/src/v4/chat/components/MessageComposer/MessageComposer.tsx @@ -2,14 +2,14 @@ import React, { RefObject, useCallback, useMemo, useRef, useState } from 'react' import ReactDOM from 'react-dom'; import { MessageRepository } from '@amityco/ts-sdk'; import ArrowTop from '~/v4/icons/ArrowTop'; -import { HomeIndicator } from '../../internal-components/HomeIndicator/index'; +import { HomeIndicator } from '~/v4/chat/internal-components/HomeIndicator'; import { useChannelPermission } from '~/v4/chat/hooks/useChannelPermission'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; import { useLiveChatNotifications } from '~/v4/chat/providers/LiveChatNotificationProvider'; import { useAmityComponent } from '~/v4/core/hooks/uikit'; import styles from './MessageComposer.module.css'; -import { useSearchChannelUser } from '../../hooks/collections/useSearchChannelUser'; +import { useSearchChannelUser } from '~/v4/chat/hooks/collections/useSearchChannelUser'; import { $getRoot, COMMAND_PRIORITY_HIGH, @@ -263,7 +263,7 @@ export const MessageComposer = ({ onQueryChange={onQueryChange} $createNode={(data) => $createMentionNode({ - text: `@${data.displayName}` || '', + text: `@${data.displayName || ''}`, data, }) } diff --git a/src/v4/chat/internal-components/LiveChatMessageContent/MessageBubble/index.tsx b/src/v4/chat/internal-components/LiveChatMessageContent/MessageBubble/index.tsx index 524e7758f..7936d6dd4 100644 --- a/src/v4/chat/internal-components/LiveChatMessageContent/MessageBubble/index.tsx +++ b/src/v4/chat/internal-components/LiveChatMessageContent/MessageBubble/index.tsx @@ -2,7 +2,7 @@ import React from 'react'; import styles from './styles.module.css'; import useUser from '~/core/hooks/useUser'; import useMessage from '~/chat/hooks/useMessage'; -import MessageTextWithMention from '../MessageTextWithMention/index'; +import MessageTextWithMention from '~/v4/chat/internal-components/LiveChatMessageContent/MessageTextWithMention'; import { Typography } from '~/v4/core/components'; import useSDK from '~/core/hooks/useSDK'; diff --git a/src/v4/chat/internal-components/LiveChatMessageContent/MessageTextWithMention/index.tsx b/src/v4/chat/internal-components/LiveChatMessageContent/MessageTextWithMention/index.tsx index 4cd103569..90445710a 100644 --- a/src/v4/chat/internal-components/LiveChatMessageContent/MessageTextWithMention/index.tsx +++ b/src/v4/chat/internal-components/LiveChatMessageContent/MessageTextWithMention/index.tsx @@ -1,7 +1,7 @@ import React from 'react'; import styles from './styles.module.css'; import { Typography } from '~/v4/core/components'; -import HyperLinkText from '~/v4/core/components/HyperlinkText/index'; +import HyperLinkText from '~/v4/core/components/HyperlinkText'; interface MessageTextWithMentionProps { message: Amity.Message<'text'>; diff --git a/src/v4/chat/internal-components/LiveChatMessageContent/index.tsx b/src/v4/chat/internal-components/LiveChatMessageContent/index.tsx index cb46e6f57..3727c5afa 100644 --- a/src/v4/chat/internal-components/LiveChatMessageContent/index.tsx +++ b/src/v4/chat/internal-components/LiveChatMessageContent/index.tsx @@ -2,13 +2,13 @@ import React, { useState } from 'react'; import dayjs from 'dayjs'; import { Typography } from '~/v4/core/components'; import { MessageAction, MessageActionType } from './MessageAction'; -import MessageBubbleContainer from './MessageBubbleContainer/index'; +import MessageBubbleContainer from './MessageBubbleContainer'; import Bin from '~/v4/icons/Bin'; import useSDK from '~/core/hooks/useSDK'; -import MessageBubble from './MessageBubble/index'; +import MessageBubble from './MessageBubble'; import { useChannelPermission } from '~/v4/chat/hooks/useChannelPermission'; import Flag from '~/v4/icons/Flag'; -import { MessageReaction } from './MessageReaction/index'; +import { MessageReaction } from './MessageReaction'; import { MessageReactionPreview } from '~/v4/chat/components/MessageReactionPreview'; import Sheet from 'react-modal-sheet'; import { ReactionList } from '~/v4/social/components'; diff --git a/src/v4/chat/internal-components/LiveChatNotification/LiveChatNotification.tsx b/src/v4/chat/internal-components/LiveChatNotification/LiveChatNotification.tsx index a9174bda4..ff83a4708 100644 --- a/src/v4/chat/internal-components/LiveChatNotification/LiveChatNotification.tsx +++ b/src/v4/chat/internal-components/LiveChatNotification/LiveChatNotification.tsx @@ -1,6 +1,6 @@ import React, { ReactNode } from 'react'; import clsx from 'clsx'; -import { Typography } from '~/v4/core/components/index'; +import { Typography } from '~/v4/core/components'; import { useLiveChatNotificationData } from '~/v4/chat/providers/LiveChatNotificationProvider'; import styles from './LiveChatNotification.module.css'; diff --git a/src/v4/chat/pages/LiveChat/LiveChat.tsx b/src/v4/chat/pages/LiveChat/LiveChat.tsx index e7808d27b..fe5102a07 100644 --- a/src/v4/chat/pages/LiveChat/LiveChat.tsx +++ b/src/v4/chat/pages/LiveChat/LiveChat.tsx @@ -1,7 +1,7 @@ import React, { useRef } from 'react'; import { useChannel } from '~/v4/chat/hooks/useChannel'; -import { ChatHeader } from '~/v4/chat/components/ChatHeader/index'; -import ChatContainer from './ChatContainer/index'; +import { ChatHeader } from '~/v4/chat/components/ChatHeader'; +import ChatContainer from './ChatContainer'; import { LiveChatNotificationProvider } from '~/v4/chat/providers/LiveChatNotificationProvider'; import { useAmityPage } from '~/v4/core/hooks/uikit'; diff --git a/src/v4/core/components/Drawer/Drawer.tsx b/src/v4/core/components/Drawer/Drawer.tsx index 1afd98ef3..92bcc51d3 100644 --- a/src/v4/core/components/Drawer/Drawer.tsx +++ b/src/v4/core/components/Drawer/Drawer.tsx @@ -1,6 +1,6 @@ import React from 'react'; import styles from './Drawer.module.css'; -import { useDrawer, useDrawerData } from '../../providers/DrawerProvider'; +import { useDrawer, useDrawerData } from '~/v4/core/providers/DrawerProvider'; import { Drawer } from 'vaul'; export const DrawerContainer = () => { diff --git a/src/v4/core/components/Modal/index.tsx b/src/v4/core/components/Modal/index.tsx index 5bdc39f6c..925f356e9 100644 --- a/src/v4/core/components/Modal/index.tsx +++ b/src/v4/core/components/Modal/index.tsx @@ -2,7 +2,7 @@ import React, { ReactNode, useEffect, useRef } from 'react'; import styles from './styles.module.css'; import clsx from 'clsx'; import Close from '~/v4/icons/Close'; -import { useAmityElement } from '../../hooks/uikit'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; export interface ModalProps { pageId?: string; diff --git a/src/v4/core/components/SocialMentionItem/index.tsx b/src/v4/core/components/SocialMentionItem/index.tsx index 76b8f9778..5a0c7c774 100644 --- a/src/v4/core/components/SocialMentionItem/index.tsx +++ b/src/v4/core/components/SocialMentionItem/index.tsx @@ -7,7 +7,7 @@ import useImage from '~/core/hooks/useImage'; import styles from './styles.module.css'; import { MentionIcon } from '~/icons'; import { FormattedMessage } from 'react-intl'; -import { Typography } from '../index'; +import { Typography } from '~/v4/core/components/Typography'; import { Avatar } from '~/v4/core/components/Avatar'; import User from '~/v4/icons/User'; diff --git a/src/v4/core/hooks/usePaginator.ts b/src/v4/core/hooks/usePaginator.ts index 56765ed18..c71844638 100644 --- a/src/v4/core/hooks/usePaginator.ts +++ b/src/v4/core/hooks/usePaginator.ts @@ -1,7 +1,7 @@ import { useMemo, useState } from 'react'; import useLiveCollection from '~/v4/core/hooks/useLiveCollection'; -import { AdEngine } from '../AdEngine'; -import { useAdSettings, useRecommendAds } from '../providers/AdEngineProvider'; +import { AdEngine } from '~/v4/core/AdEngine'; +import { useAdSettings, useRecommendAds } from '~/v4/core/providers/AdEngineProvider'; import { isNonNullable } from '~/v4/helpers/utils'; const usePaginatorCore = <T>({ diff --git a/src/v4/core/providers/AdEngineProvider.tsx b/src/v4/core/providers/AdEngineProvider.tsx index c30fd5f6b..c7cae6549 100644 --- a/src/v4/core/providers/AdEngineProvider.tsx +++ b/src/v4/core/providers/AdEngineProvider.tsx @@ -1,7 +1,7 @@ import React, { useContext, useEffect, useState, createContext } from 'react'; -import { AdEngine } from '../AdEngine'; -import { AdSupplier } from '../AdSupplier'; -import { TimeWindowTracker } from '../TimeWindowTracker'; +import { AdEngine } from '~/v4/core/AdEngine'; +import { AdSupplier } from '~/v4/core/AdSupplier'; +import { TimeWindowTracker } from '~/v4/core/TimeWindowTracker'; export const AdEngineContext = createContext<{ ads: Amity.Ad[]; diff --git a/src/v4/core/providers/AmityUIKitProvider.tsx b/src/v4/core/providers/AmityUIKitProvider.tsx index 883feb88d..d82c3454c 100644 --- a/src/v4/core/providers/AmityUIKitProvider.tsx +++ b/src/v4/core/providers/AmityUIKitProvider.tsx @@ -1,6 +1,6 @@ -import '../../../core/providers/UiKitProvider/inter.css'; +import '~/core/providers/UiKitProvider/inter.css'; import './index.css'; -import '../../styles/global.css'; +import '~/v4/styles/global.css'; import React, { useEffect, useMemo, useState } from 'react'; import useUser from '~/core/hooks/useUser'; @@ -27,7 +27,7 @@ import { defaultConfig, Config, CustomizationProvider } from './CustomizationPro import { ThemeProvider } from './ThemeProvider'; import { PageBehavior, PageBehaviorProvider } from './PageBehaviorProvider'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { AmityUIKitManager } from '../AmityUIKitManager'; +import { AmityUIKitManager } from '~/v4/core/AmityUIKitManager'; import { ConfirmProvider } from '~/v4/core/providers/ConfirmProvider'; import { ConfirmProvider as LegacyConfirmProvider } from '~/core/providers/ConfirmProvider'; import { NotificationProvider } from '~/v4/core/providers/NotificationProvider'; @@ -35,7 +35,7 @@ import { DrawerProvider } from '~/v4/core/providers/DrawerProvider'; import { NotificationProvider as LegacyNotificationProvider } from '~/core/providers/NotificationProvider'; import { CustomReactionProvider } from './CustomReactionProvider'; import { AdEngineProvider } from './AdEngineProvider'; -import { AdEngine } from '../AdEngine'; +import { AdEngine } from '~/v4/core/AdEngine'; import { GlobalFeedProvider } from '~/v4/social/providers/GlobalFeedProvider'; export type AmityUIKitConfig = Config; diff --git a/src/v4/helpers/utils.ts b/src/v4/helpers/utils.ts index b7b69ff93..64eef684d 100644 --- a/src/v4/helpers/utils.ts +++ b/src/v4/helpers/utils.ts @@ -210,7 +210,7 @@ const SUPPORTED_URL_PROTOCOLS = new Set(['http:', 'https:', 'mailto:', 'sms:', ' export function sanitizeUrl(url: string): string { try { const parsedUrl = new URL(url); - // eslint-disable-next-line no-script-url + if (!SUPPORTED_URL_PROTOCOLS.has(parsedUrl.protocol)) { return 'about:blank'; } diff --git a/src/v4/social/components/CommentEdition/CommentEdition.tsx b/src/v4/social/components/CommentEdition/CommentEdition.tsx index f402b04f0..bc5f0303d 100644 --- a/src/v4/social/components/CommentEdition/CommentEdition.tsx +++ b/src/v4/social/components/CommentEdition/CommentEdition.tsx @@ -5,9 +5,9 @@ import { ButtonContainer, CommentEditContainer, CommentEditTextarea } from './st import { QueryMentioneesFnType } from '~/v4/chat/hooks/useMention'; import { useTheme } from 'styled-components'; -import { SaveButton } from '../../elements'; +import { SaveButton } from '~/v4/social/elements'; import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; -import { EditCancelButton } from '../../elements/EditCancelButton/EditCancelButton'; +import { EditCancelButton } from '~/v4/social/elements/EditCancelButton/EditCancelButton'; interface CommentEditionProps { pageId?: '*'; diff --git a/src/v4/social/components/CommentOptions/CommentOptions.tsx b/src/v4/social/components/CommentOptions/CommentOptions.tsx index e5cb95c11..fa1f62cb3 100644 --- a/src/v4/social/components/CommentOptions/CommentOptions.tsx +++ b/src/v4/social/components/CommentOptions/CommentOptions.tsx @@ -3,9 +3,9 @@ import { useCommentFlaggedByMe } from '~/v4/social/hooks/useCommentFlaggedByMe'; import { useNotifications } from '~/v4/core/providers/NotificationProvider'; import useCommentPermission from '~/social/hooks/useCommentPermission'; import useSDK from '~/v4/core/hooks/useSDK'; -import { Typography } from '~/v4/core/components/index'; +import { Typography } from '~/v4/core/components'; import { isNonNullable } from '~/v4/helpers/utils'; -import { FlagIcon, PenIcon, TrashIcon } from '../../icons/index'; +import { FlagIcon, PenIcon, TrashIcon } from '~/v4/social/icons'; import styles from './CommentOptions.module.css'; interface CommentOptionsProps { diff --git a/src/v4/social/components/CreatePostMenu/index.tsx b/src/v4/social/components/CreatePostMenu/index.tsx index fcd3d0bd7..864d86a9f 100644 --- a/src/v4/social/components/CreatePostMenu/index.tsx +++ b/src/v4/social/components/CreatePostMenu/index.tsx @@ -1 +1 @@ -export {CreatePostMenu} from './CreatePostMenu' \ No newline at end of file +export { CreatePostMenu } from './CreatePostMenu'; diff --git a/src/v4/social/components/DetailedMediaAttachment/index.tsx b/src/v4/social/components/DetailedMediaAttachment/index.tsx index b48a316a2..e2834d8eb 100644 --- a/src/v4/social/components/DetailedMediaAttachment/index.tsx +++ b/src/v4/social/components/DetailedMediaAttachment/index.tsx @@ -1 +1 @@ -export { DetailedMediaAttachment } from './DetailedMediaAttachment'; \ No newline at end of file +export { DetailedMediaAttachment } from './DetailedMediaAttachment'; diff --git a/src/v4/social/components/GlobalFeed/GlobalFeed.tsx b/src/v4/social/components/GlobalFeed/GlobalFeed.tsx index f3518f6fb..b4d1c6d25 100644 --- a/src/v4/social/components/GlobalFeed/GlobalFeed.tsx +++ b/src/v4/social/components/GlobalFeed/GlobalFeed.tsx @@ -1,5 +1,5 @@ import React, { useRef } from 'react'; -import { PostContent, PostContentSkeleton } from '../PostContent'; +import { PostContent, PostContentSkeleton } from '~/v4/social/components/PostContent'; import { EmptyNewsfeed } from '~/v4/social/components/EmptyNewsFeed/EmptyNewsFeed'; import useIntersectionObserver from '~/v4/core/hooks/useIntersectionObserver'; import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; diff --git a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx index ee6beb090..a6165d4d2 100644 --- a/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx +++ b/src/v4/social/components/HyperLinkConfig/HyperLinkConfig.tsx @@ -90,11 +90,14 @@ export const HyperLinkConfig = ({ customText: z .string() .optional() - .refine(async (value) => { - if (!value) return true; - const hasBlockedWord = await client?.validateTexts([value]).catch(() => false); - return hasBlockedWord; - }, formatMessage({ id: 'storyCreation.hyperlink.validation.error.blocked' })), + .refine( + async (value) => { + if (!value) return true; + const hasBlockedWord = await client?.validateTexts([value]).catch(() => false); + return hasBlockedWord; + }, + formatMessage({ id: 'storyCreation.hyperlink.validation.error.blocked' }), + ), }); type HyperLinkFormInputs = z.infer<typeof schema>; diff --git a/src/v4/social/components/Newsfeed/Newsfeed.tsx b/src/v4/social/components/Newsfeed/Newsfeed.tsx index eb179a4d0..741aa2244 100644 --- a/src/v4/social/components/Newsfeed/Newsfeed.tsx +++ b/src/v4/social/components/Newsfeed/Newsfeed.tsx @@ -5,7 +5,7 @@ import { GlobalFeed } from '~/v4/social/components/GlobalFeed'; import styles from './Newsfeed.module.css'; import { useAmityComponent } from '~/v4/core/hooks/uikit'; -import { useGlobalFeedContext } from '../../providers/GlobalFeedProvider'; +import { useGlobalFeedContext } from '~/v4/social/providers/GlobalFeedProvider'; const Spinner = (props: React.SVGProps<SVGSVGElement>) => { return ( diff --git a/src/v4/social/components/PostComment/PostComment.tsx b/src/v4/social/components/PostComment/PostComment.tsx index 77acbdb27..1dbc9c1a2 100644 --- a/src/v4/social/components/PostComment/PostComment.tsx +++ b/src/v4/social/components/PostComment/PostComment.tsx @@ -6,20 +6,20 @@ import { UserAvatar } from '~/v4/social/internal-components/UserAvatar'; import styles from './PostComment.module.css'; import { useAmityComponent } from '~/v4/core/hooks/uikit'; import ReplyComment from '~/v4/icons/ReplyComment'; -import { PostReplyCommentList } from '../PostReplyCommentList/PostReplyCommentList'; -import { MinusCircleIcon } from '../../icons/index'; +import { PostReplyCommentList } from '~/v4/social/components/PostReplyCommentList/PostReplyCommentList'; +import { MinusCircleIcon } from '~/v4/social/icons'; import { Mentionees } from '~/v4/helpers/utils'; import { CommentRepository, ReactionRepository } from '@amityco/ts-sdk'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; -import { LIKE_REACTION_KEY } from '../../constants/reactions'; -import { EditCancelButton } from '../../elements/EditCancelButton/EditCancelButton'; -import { SaveButton } from '../../elements/SaveButton/SaveButton'; +import { LIKE_REACTION_KEY } from '~/v4/social/constants/reactions'; +import { EditCancelButton } from '~/v4/social/elements/EditCancelButton/EditCancelButton'; +import { SaveButton } from '~/v4/social/elements/SaveButton/SaveButton'; import clsx from 'clsx'; -import { PostCommentInput } from '../PostCommentComposer/PostCommentInput'; -import { CommentOptions } from '../CommentOptions/CommentOptions'; -import { CreateCommentParams } from '../PostCommentComposer/PostCommentComposer'; +import { PostCommentInput } from '~/v4/social/components/PostCommentComposer/PostCommentInput'; +import { CommentOptions } from '~/v4/social/components/CommentOptions/CommentOptions'; +import { CreateCommentParams } from '~/v4/social/components/PostCommentComposer/PostCommentComposer'; import useCommentSubscription from '~/v4/core/hooks/subscriptions/useCommentSubscription'; -import { TextWithMention } from '../../internal-components/TextWithMention/TextWithMention'; +import { TextWithMention } from '~/v4/social/internal-components/TextWithMention/TextWithMention'; import millify from 'millify'; const EllipsisH = ({ ...props }: React.SVGProps<SVGSVGElement>) => ( diff --git a/src/v4/social/components/PostCommentComposer/PostCommentComposer.tsx b/src/v4/social/components/PostCommentComposer/PostCommentComposer.tsx index cdf76dd71..9c1f4388e 100644 --- a/src/v4/social/components/PostCommentComposer/PostCommentComposer.tsx +++ b/src/v4/social/components/PostCommentComposer/PostCommentComposer.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useRef, useState } from 'react'; -import { Avatar, Button } from '~/v4/core/components/index'; +import { Avatar, Button } from '~/v4/core/components'; import { useUser } from '~/v4/core/hooks/objects/useUser'; import { useImage } from '~/v4/core/hooks/useImage'; import useSDK from '~/v4/core/hooks/useSDK'; diff --git a/src/v4/social/components/PostCommentComposer/PostCommentInput.tsx b/src/v4/social/components/PostCommentComposer/PostCommentInput.tsx index 81a8f8ae8..41d07b906 100644 --- a/src/v4/social/components/PostCommentComposer/PostCommentInput.tsx +++ b/src/v4/social/components/PostCommentComposer/PostCommentInput.tsx @@ -20,9 +20,9 @@ import { SerializedMentionNode, } from '~/v4/social/internal-components/MentionTextInput/MentionNodes'; import styles from './PostCommentInput.module.css'; -import { PostCommentMentionInput } from '../PostCommentMentionInput'; -import { useMentionUsers } from '../../hooks/useMentionUser'; -import { CreateCommentParams } from '../PostCommentComposer/PostCommentComposer'; +import { PostCommentMentionInput } from '~/v4/social/components/PostCommentMentionInput'; +import { useMentionUsers } from '~/v4/social/hooks/useMentionUser'; +import { CreateCommentParams } from '~/v4/social/components/PostCommentComposer/PostCommentComposer'; import { Mentioned, Mentionees } from '~/v4/helpers/utils'; const theme = { diff --git a/src/v4/social/components/PostCommentList/PostCommentList.tsx b/src/v4/social/components/PostCommentList/PostCommentList.tsx index 2cf0ef9fb..4baeb8d40 100644 --- a/src/v4/social/components/PostCommentList/PostCommentList.tsx +++ b/src/v4/social/components/PostCommentList/PostCommentList.tsx @@ -1,14 +1,14 @@ import React, { useRef } from 'react'; import styles from './PostCommentList.module.css'; -import { PostComment } from '../PostComment/PostComment'; +import { PostComment } from '~/v4/social/components/PostComment/PostComment'; import useIntersectionObserver from '~/v4/core/hooks/useIntersectionObserver'; -import useCommentsCollection from '../../hooks/collections/useCommentsCollection'; -import { useAmityComponent } from '~/v4/core/hooks/uikit/index'; +import useCommentsCollection from '~/v4/social/hooks/collections/useCommentsCollection'; +import { useAmityComponent } from '~/v4/core/hooks/uikit'; import useUserSubscription from '~/v4/core/hooks/subscriptions/useUserSubscription'; import { CommentRepository, SubscriptionLevels } from '@amityco/ts-sdk'; import useCommunitySubscription from '~/v4/core/hooks/subscriptions/useCommunitySubscription'; import { usePaginator } from '~/v4/core/hooks/usePaginator'; -import { CommentAd } from '../../internal-components/CommentAd/CommentAd'; +import { CommentAd } from '~/v4/social/internal-components/CommentAd/CommentAd'; type PostCommentListProps = { post: Amity.Post; diff --git a/src/v4/social/components/PostCommentMentionInput/PostCommentMentionInput.tsx b/src/v4/social/components/PostCommentMentionInput/PostCommentMentionInput.tsx index b747ca970..dbcf13bdf 100644 --- a/src/v4/social/components/PostCommentMentionInput/PostCommentMentionInput.tsx +++ b/src/v4/social/components/PostCommentMentionInput/PostCommentMentionInput.tsx @@ -8,8 +8,8 @@ import { import { TextNode } from 'lexical'; import React, { RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import ReactDOM from 'react-dom'; -import { $createMentionNode } from '../../internal-components/MentionTextInput/MentionNodes'; -import { CommunityMember } from '../../internal-components/CommunityMember/CommunityMember'; +import { $createMentionNode } from '~/v4/social/internal-components/MentionTextInput/MentionNodes'; +import { CommunityMember } from '~/v4/social/internal-components/CommunityMember/CommunityMember'; const MAX_LENGTH = 5000; diff --git a/src/v4/social/components/PostContent/ImageContent/ImageContent.tsx b/src/v4/social/components/PostContent/ImageContent/ImageContent.tsx index 50aeece54..34e67c542 100644 --- a/src/v4/social/components/PostContent/ImageContent/ImageContent.tsx +++ b/src/v4/social/components/PostContent/ImageContent/ImageContent.tsx @@ -2,7 +2,7 @@ import React, { useMemo } from 'react'; import { useImage } from '~/v4/core/hooks/useImage'; import { Typography } from '~/v4/core/components'; -import { useAmityElement } from '~/v4/core/hooks/uikit/index'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; import styles from './ImageContent.module.css'; import usePost from '~/v4/core/hooks/objects/usePost'; import { Button } from '~/v4/core/natives/Button'; diff --git a/src/v4/social/components/PostContent/PostContent.tsx b/src/v4/social/components/PostContent/PostContent.tsx index 5f5950a8c..cf2ddaf69 100644 --- a/src/v4/social/components/PostContent/PostContent.tsx +++ b/src/v4/social/components/PostContent/PostContent.tsx @@ -31,7 +31,7 @@ import { VideoViewer } from '~/v4/social/internal-components/VideoViewer/VideoVi import usePost from '~/v4/core/hooks/objects/usePost'; import { PostMenu } from '~/v4/social/internal-components/PostMenu/PostMenu'; import usePostSubscription from '~/v4/core/hooks/subscriptions/usePostSubscription'; -import { ReactionList } from '../index'; +import { ReactionList } from '~/v4/social/components/ReactionList/ReactionList'; import { usePostedUserInformation } from '~/v4/core/hooks/usePostedUserInformation'; import millify from 'millify'; import { Button } from '~/v4/core/natives/Button'; diff --git a/src/v4/social/components/PostContent/TextContent/TextContent.tsx b/src/v4/social/components/PostContent/TextContent/TextContent.tsx index 794994ebb..0303dc108 100644 --- a/src/v4/social/components/PostContent/TextContent/TextContent.tsx +++ b/src/v4/social/components/PostContent/TextContent/TextContent.tsx @@ -3,7 +3,7 @@ import React, { useState, useMemo, ReactNode, useEffect } from 'react'; import { Linkify } from '~/v4/social/internal-components/Linkify'; import { Mentioned, findChunks, processChunks } from '~/v4/helpers/utils'; import { Typography } from '~/v4/core/components'; -import { LinkPreview } from '../LinkPreview/LinkPreview'; +import { LinkPreview } from '~/v4/social/components/PostContent/LinkPreview/LinkPreview'; import styles from './TextContent.module.css'; import * as linkify from 'linkifyjs'; diff --git a/src/v4/social/components/PostReplyComment/PostReplyComment.tsx b/src/v4/social/components/PostReplyComment/PostReplyComment.tsx index 902c83bb9..3f132b55f 100644 --- a/src/v4/social/components/PostReplyComment/PostReplyComment.tsx +++ b/src/v4/social/components/PostReplyComment/PostReplyComment.tsx @@ -3,23 +3,23 @@ import clsx from 'clsx'; import millify from 'millify'; import React, { useCallback, useState } from 'react'; import EllipsisH from '~/icons/EllipsisH'; -import { BottomSheet, Typography } from '~/v4/core/components/index'; +import { BottomSheet, Typography } from '~/v4/core/components'; import useCommentSubscription from '~/v4/core/hooks/subscriptions/useCommentSubscription'; -import { useAmityComponent } from '~/v4/core/hooks/uikit/index'; +import { useAmityComponent } from '~/v4/core/hooks/uikit'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; import { Mentionees } from '~/v4/helpers/utils'; -import { LIKE_REACTION_KEY } from '../../constants/reactions'; -import { EditCancelButton } from '../../elements/EditCancelButton/EditCancelButton'; -import { SaveButton } from '../../elements/index'; -import { ModeratorBadge } from '../../elements/ModeratorBadge/ModeratorBadge'; -import Like from '../../elements/ReactionButton/Like'; -import { Timestamp } from '../../elements/Timestamp/Timestamp'; -import { MinusCircleIcon } from '../../icons/index'; -import { TextWithMention } from '../../internal-components/TextWithMention/TextWithMention'; -import { UserAvatar } from '../../internal-components/UserAvatar/UserAvatar'; -import { CommentOptions } from '../CommentOptions/CommentOptions'; -import { CreateCommentParams } from '../PostCommentComposer/PostCommentComposer'; -import { PostCommentInput } from '../PostCommentComposer/PostCommentInput'; +import { LIKE_REACTION_KEY } from '~/v4/social/constants/reactions'; +import { EditCancelButton } from '~/v4/social/elements/EditCancelButton/EditCancelButton'; +import { SaveButton } from '~/v4/social/elements'; +import { ModeratorBadge } from '~/v4/social/elements/ModeratorBadge/ModeratorBadge'; +import Like from '~/v4/social/elements/ReactionButton/Like'; +import { Timestamp } from '~/v4/social/elements/Timestamp/Timestamp'; +import { MinusCircleIcon } from '~/v4/social/icons'; +import { TextWithMention } from '~/v4/social/internal-components/TextWithMention/TextWithMention'; +import { UserAvatar } from '~/v4/social/internal-components/UserAvatar/UserAvatar'; +import { CommentOptions } from '~/v4/social/components/CommentOptions/CommentOptions'; +import { CreateCommentParams } from '~/v4/social/components/PostCommentComposer/PostCommentComposer'; +import { PostCommentInput } from '~/v4/social/components/PostCommentComposer/PostCommentInput'; import styles from './PostReplyComment.module.css'; type PostReplyCommentProps = { diff --git a/src/v4/social/components/PostReplyCommentList/PostReplyCommentList.tsx b/src/v4/social/components/PostReplyCommentList/PostReplyCommentList.tsx index c14fda533..75d9f8e0c 100644 --- a/src/v4/social/components/PostReplyCommentList/PostReplyCommentList.tsx +++ b/src/v4/social/components/PostReplyCommentList/PostReplyCommentList.tsx @@ -1,8 +1,8 @@ import React from 'react'; -import { Typography } from '~/v4/core/components/index'; +import { Typography } from '~/v4/core/components'; import ReplyComment from '~/v4/icons/ReplyComment'; -import useCommentsCollection from '../../hooks/collections/useCommentsCollection'; -import PostReplyComment from '../PostReplyComment/PostReplyComment'; +import useCommentsCollection from '~/v4/social/hooks/collections/useCommentsCollection'; +import PostReplyComment from '~/v4/social/components/PostReplyComment/PostReplyComment'; import styles from './PostReplyCommentList.module.css'; interface PostReplyCommentProps { diff --git a/src/v4/social/components/TopNavigation/TopNavigation.tsx b/src/v4/social/components/TopNavigation/TopNavigation.tsx index a2f4cd95d..bfbaa5609 100644 --- a/src/v4/social/components/TopNavigation/TopNavigation.tsx +++ b/src/v4/social/components/TopNavigation/TopNavigation.tsx @@ -5,7 +5,7 @@ import { HeaderLabel } from '~/v4/social/elements/HeaderLabel'; import styles from './TopNavigation.module.css'; import { useAmityComponent } from '~/v4/core/hooks/uikit'; import { useNavigation } from '~/v4/core/providers/NavigationProvider'; -import { HomePageTab } from '../../pages/SocialHomePage/SocialHomePage'; +import { HomePageTab } from '~/v4/social/pages/SocialHomePage/SocialHomePage'; export interface TopNavigationProps { pageId?: string; diff --git a/src/v4/social/components/UserSearchResult/UserSearchItem.tsx b/src/v4/social/components/UserSearchResult/UserSearchItem.tsx index a1fc8cf74..70572f40c 100644 --- a/src/v4/social/components/UserSearchResult/UserSearchItem.tsx +++ b/src/v4/social/components/UserSearchResult/UserSearchItem.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { UserAvatar } from '~/v4/social/internal-components/UserAvatar/UserAvatar'; -import { Typography } from '~/v4/core/components/index'; +import { Typography } from '~/v4/core/components'; import styles from './UserSearchItem.module.css'; interface UserSearchItemProps { diff --git a/src/v4/social/elements/CreateNewPostButton/CreateNewPostButton.tsx b/src/v4/social/elements/CreateNewPostButton/CreateNewPostButton.tsx index 6c31f0227..cc77a28ae 100644 --- a/src/v4/social/elements/CreateNewPostButton/CreateNewPostButton.tsx +++ b/src/v4/social/elements/CreateNewPostButton/CreateNewPostButton.tsx @@ -1,4 +1,4 @@ -import React, { FormEventHandler } from 'react'; +import React, { FormEventHandler } from 'react'; import { useAmityElement } from '~/v4/core/hooks/uikit'; import styles from './CreateNewPostButton.module.css'; diff --git a/src/v4/social/elements/CreatePostButton/index.tsx b/src/v4/social/elements/CreatePostButton/index.tsx index 32e08009a..b064d13c3 100644 --- a/src/v4/social/elements/CreatePostButton/index.tsx +++ b/src/v4/social/elements/CreatePostButton/index.tsx @@ -1 +1 @@ -export { CreatePostButton } from "./CreatePostButton"; \ No newline at end of file +export { CreatePostButton } from './CreatePostButton'; diff --git a/src/v4/social/elements/MyTimelineAvatar/MyTimelineAvatar.tsx b/src/v4/social/elements/MyTimelineAvatar/MyTimelineAvatar.tsx index d741a4b60..25a9bdd01 100644 --- a/src/v4/social/elements/MyTimelineAvatar/MyTimelineAvatar.tsx +++ b/src/v4/social/elements/MyTimelineAvatar/MyTimelineAvatar.tsx @@ -9,7 +9,11 @@ interface MyTimelineAvatarProps { userId?: string | null; } -export function MyTimelineAvatar({ pageId = '*', componentId = '*', userId }: MyTimelineAvatarProps) { +export function MyTimelineAvatar({ + pageId = '*', + componentId = '*', + userId, +}: MyTimelineAvatarProps) { const elementId = 'my_timeline_avatar'; const { accessibilityId, isExcluded } = useAmityElement({ pageId, diff --git a/src/v4/social/elements/MyTimelineAvatar/index.tsx b/src/v4/social/elements/MyTimelineAvatar/index.tsx index 082c877c5..dc36435fd 100644 --- a/src/v4/social/elements/MyTimelineAvatar/index.tsx +++ b/src/v4/social/elements/MyTimelineAvatar/index.tsx @@ -1 +1 @@ -export { MyTimelineAvatar } from './MyTimelineAvatar' \ No newline at end of file +export { MyTimelineAvatar } from './MyTimelineAvatar'; diff --git a/src/v4/social/elements/PostTextField/index.tsx b/src/v4/social/elements/PostTextField/index.tsx index 1afb1f1eb..d20bf3695 100644 --- a/src/v4/social/elements/PostTextField/index.tsx +++ b/src/v4/social/elements/PostTextField/index.tsx @@ -1 +1 @@ -export { PostTextField } from './PostTextField'; \ No newline at end of file +export { PostTextField } from './PostTextField'; diff --git a/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx b/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx index 13d351080..c1741b270 100644 --- a/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx +++ b/src/v4/social/elements/ShareStoryButton/ShareStoryButton.tsx @@ -1,7 +1,7 @@ import React from 'react'; import clsx from 'clsx'; import { Typography } from '~/v4/core/components'; -import { useAmityElement } from '~/v4/core/hooks/uikit/index'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; import styles from './ShareStoryButton.module.css'; import { CommunityAvatar } from '~/v4/social/elements/CommunityAvatar'; diff --git a/src/v4/social/elements/StoryCommentButton/StoryCommentButton.tsx b/src/v4/social/elements/StoryCommentButton/StoryCommentButton.tsx index 0b387d9b8..5f1bc7515 100644 --- a/src/v4/social/elements/StoryCommentButton/StoryCommentButton.tsx +++ b/src/v4/social/elements/StoryCommentButton/StoryCommentButton.tsx @@ -1,9 +1,8 @@ import clsx from 'clsx'; import millify from 'millify'; import React from 'react'; -import { PressEvent } from 'react-aria'; -import { Typography } from '~/v4/core/components/index'; -import { useAmityElement } from '~/v4/core/hooks/uikit/index'; +import { Typography } from '~/v4/core/components'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; import { IconComponent } from '~/v4/core/IconComponent'; import { Button, ButtonProps } from '~/v4/core/natives/Button'; diff --git a/src/v4/social/elements/StoryProgressBar/StoryProgressBar.tsx b/src/v4/social/elements/StoryProgressBar/StoryProgressBar.tsx index 88b89f25b..31b98f5de 100644 --- a/src/v4/social/elements/StoryProgressBar/StoryProgressBar.tsx +++ b/src/v4/social/elements/StoryProgressBar/StoryProgressBar.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react'; -import { useAmityElement } from '~/v4/core/hooks/uikit/index'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; import clsx from 'clsx'; import styles from './StoryProgressBar.module.css'; diff --git a/src/v4/social/elements/StoryReactionButton/StoryReactionButton.tsx b/src/v4/social/elements/StoryReactionButton/StoryReactionButton.tsx index b4dcd831f..24ed56722 100644 --- a/src/v4/social/elements/StoryReactionButton/StoryReactionButton.tsx +++ b/src/v4/social/elements/StoryReactionButton/StoryReactionButton.tsx @@ -5,7 +5,7 @@ import { useAmityElement } from '~/v4/core/hooks/uikit'; import { Button, ButtonProps } from '~/v4/core/natives/Button'; import styles from './StoryReactionButton.module.css'; -import { Typography } from '~/v4/core/components/index'; +import { Typography } from '~/v4/core/components'; import millify from 'millify'; const StoryReactionSvg = (props: React.SVGProps<SVGSVGElement>) => { diff --git a/src/v4/social/hooks/useLiveCollection.ts b/src/v4/social/hooks/useLiveCollection.ts index a78b0dd14..dcb5ae63a 100644 --- a/src/v4/social/hooks/useLiveCollection.ts +++ b/src/v4/social/hooks/useLiveCollection.ts @@ -1,7 +1,6 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { useSDKLiveCollectionConnector } from '~/v4/core/providers/SDKConnectorProvider'; - function useLiveCollection<TCallback, TParams>({ fetcher, params, diff --git a/src/v4/social/internal-components/Comment/UIComment.tsx b/src/v4/social/internal-components/Comment/UIComment.tsx index 1589fc716..615047a25 100644 --- a/src/v4/social/internal-components/Comment/UIComment.tsx +++ b/src/v4/social/internal-components/Comment/UIComment.tsx @@ -17,7 +17,7 @@ import InputText from '~/v4/core/components/InputText'; import { Avatar, Typography } from '~/v4/core/components'; import Button from '~/v4/core/components/Button/Button'; import clsx from 'clsx'; -import { ModeratorBadge } from '../../elements/ModeratorBadge/ModeratorBadge'; +import { ModeratorBadge } from '~/v4/social/elements/ModeratorBadge/ModeratorBadge'; import User from '~/v4/icons/User'; import styles from './UIComment.module.css'; diff --git a/src/v4/social/internal-components/CommentAd/CommentAd.tsx b/src/v4/social/internal-components/CommentAd/CommentAd.tsx index bb8a3214b..2f4743e97 100644 --- a/src/v4/social/internal-components/CommentAd/CommentAd.tsx +++ b/src/v4/social/internal-components/CommentAd/CommentAd.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useAmityComponent } from '~/v4/core/hooks/uikit/index'; +import { useAmityComponent } from '~/v4/core/hooks/uikit'; import { UICommentAd } from './UICommentAd'; import { useImage } from '~/v4/core/hooks/useImage'; diff --git a/src/v4/social/internal-components/CommentAd/UICommentAd.tsx b/src/v4/social/internal-components/CommentAd/UICommentAd.tsx index 4d68f2315..eabf7afe1 100644 --- a/src/v4/social/internal-components/CommentAd/UICommentAd.tsx +++ b/src/v4/social/internal-components/CommentAd/UICommentAd.tsx @@ -1,11 +1,11 @@ import React, { useState } from 'react'; import styles from './UICommentAd.module.css'; import { Avatar, Typography } from '~/v4/core/components'; -import { AdsBadge } from '../AdsBadge/AdsBadge'; +import { AdsBadge } from '~/v4/social/internal-components/AdsBadge/AdsBadge'; import Broadcast from '~/v4/icons/Broadcast'; import InfoCircle from '~/v4/icons/InfoCircle'; import { Button } from '~/v4/core/natives/Button'; -import { AdInformation } from '../AdInformation/AdInformation'; +import { AdInformation } from '~/v4/social/internal-components/AdInformation/AdInformation'; interface UICommentAdProps { ad: Amity.Ad; diff --git a/src/v4/social/internal-components/CommentList/CommentList.tsx b/src/v4/social/internal-components/CommentList/CommentList.tsx index 7510ef267..28090f483 100644 --- a/src/v4/social/internal-components/CommentList/CommentList.tsx +++ b/src/v4/social/internal-components/CommentList/CommentList.tsx @@ -5,8 +5,7 @@ import styles from './CommentList.module.css'; import { ExpandIcon, MinusCircleIcon } from '~/v4/social/icons'; import { LoadMoreWrapper } from '~/v4/core/components/LoadMoreWrapper/LoadMoreWrapper'; import useCommentsCollection from '~/v4/social/hooks/collections/useCommentsCollection'; -import { CommentBubbleDeleted } from '~/v4/social/elements/CommentBubbleDeleted'; -import { Typography } from '~/v4/core/components/index'; +import { Typography } from '~/v4/core/components'; interface CommentListProps { parentId?: string; diff --git a/src/v4/social/internal-components/CommunityMember/CommunityMember.tsx b/src/v4/social/internal-components/CommunityMember/CommunityMember.tsx index d1dd6a14a..c01cfe9ae 100644 --- a/src/v4/social/internal-components/CommunityMember/CommunityMember.tsx +++ b/src/v4/social/internal-components/CommunityMember/CommunityMember.tsx @@ -1,7 +1,7 @@ import React from 'react'; import styles from './CommunityMember.module.css'; -import { UserAvatar } from '~/v4/social/internal-components/UserAvatar/UserAvatar'; -import { MentionTypeaheadOption } from '../MentionTextInput/MentionTextInput'; +import { UserAvatar } from '~/v4/social/internal-components/UserAvatar'; +import { MentionTypeaheadOption } from '~/v4/social/internal-components/MentionTextInput/MentionTextInput'; interface CommunityMemberProps { isSelected: boolean; diff --git a/src/v4/social/internal-components/ImageViewer/ImageViewer.tsx b/src/v4/social/internal-components/ImageViewer/ImageViewer.tsx index d26bfcd21..5f6de3b55 100644 --- a/src/v4/social/internal-components/ImageViewer/ImageViewer.tsx +++ b/src/v4/social/internal-components/ImageViewer/ImageViewer.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import useImage from '~/core/hooks/useImage'; import usePostByIds from '~/social/hooks/usePostByIds'; import { useAmityElement } from '~/v4/core/hooks/uikit'; -import { ClearButton } from '../../elements/ClearButton/ClearButton'; +import { ClearButton } from '~/v4/social/elements/ClearButton'; import styles from './ImageViewer.module.css'; diff --git a/src/v4/social/internal-components/MentionTextInput/MentionNodes.ts b/src/v4/social/internal-components/MentionTextInput/MentionNodes.ts index 6203fa2d2..64a89d867 100644 --- a/src/v4/social/internal-components/MentionTextInput/MentionNodes.ts +++ b/src/v4/social/internal-components/MentionTextInput/MentionNodes.ts @@ -100,7 +100,6 @@ export class MentionNode extends TextNode { this.__userId = userId; this.__userInternalId = userInternalId; this.__userPublicId = userPublicId; - } exportJSON(): SerializedMentionNode { diff --git a/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx b/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx index 372a08da1..e9da47e1b 100644 --- a/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx +++ b/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx @@ -9,7 +9,7 @@ import { TextNode } from 'lexical'; import React, { RefObject, useCallback, useMemo, useRef, useState } from 'react'; import ReactDOM from 'react-dom'; import { $createMentionNode } from './MentionNodes'; -import { CommunityMember } from '../CommunityMember'; +import { CommunityMember } from '~/v4/social/internal-components/CommunityMember'; import { useMemberQueryByDisplayName } from '~/v4/social/hooks/useMemberQueryByDisplayName'; import { useCommunity } from '~/v4/chat/hooks/useCommunity'; import { useUserQueryByDisplayName } from '~/v4/core/hooks/collections/useUsersCollection'; diff --git a/src/v4/social/internal-components/PostAd/PostAd.tsx b/src/v4/social/internal-components/PostAd/PostAd.tsx index 85380fe39..ad2ec25c3 100644 --- a/src/v4/social/internal-components/PostAd/PostAd.tsx +++ b/src/v4/social/internal-components/PostAd/PostAd.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useAmityComponent } from '~/v4/core/hooks/uikit/index'; +import { useAmityComponent } from '~/v4/core/hooks/uikit'; import { UIPostAd } from './UIPostAd'; import { useImage } from '~/v4/core/hooks/useImage'; diff --git a/src/v4/social/internal-components/PostAd/UIPostAd.tsx b/src/v4/social/internal-components/PostAd/UIPostAd.tsx index 0df9765c7..b4d7e70b8 100644 --- a/src/v4/social/internal-components/PostAd/UIPostAd.tsx +++ b/src/v4/social/internal-components/PostAd/UIPostAd.tsx @@ -1,10 +1,10 @@ import React, { useState } from 'react'; import { Avatar, Typography } from '~/v4/core/components'; -import { AdsBadge } from '../AdsBadge/AdsBadge'; +import { AdsBadge } from '~/v4/social/internal-components/AdsBadge/AdsBadge'; import Broadcast from '~/v4/icons/Broadcast'; import InfoCircle from '~/v4/icons/InfoCircle'; import { Button } from '~/v4/core/natives/Button'; -import { AdInformation } from '../AdInformation/AdInformation'; +import { AdInformation } from '~/v4/social/internal-components/AdInformation/AdInformation'; import styles from './UIPostAd.module.css'; diff --git a/src/v4/social/internal-components/StoryAd/UIStoryAd.tsx b/src/v4/social/internal-components/StoryAd/UIStoryAd.tsx index 21be56df3..86b194024 100644 --- a/src/v4/social/internal-components/StoryAd/UIStoryAd.tsx +++ b/src/v4/social/internal-components/StoryAd/UIStoryAd.tsx @@ -4,10 +4,10 @@ import clsx from 'clsx'; import { Button } from '~/v4/core/natives/Button'; import { StoryProgressBar } from '~/v4/social/elements/StoryProgressBar/StoryProgressBar'; import InfoCircle from '~/v4/icons/InfoCircle'; -import { Avatar, Typography } from '~/v4/core/components/index'; +import { Avatar, Typography } from '~/v4/core/components'; import Broadcast from '~/v4/icons/Broadcast'; -import { PauseIcon, PlayIcon } from '~/icons/index'; -import { CloseButton } from '~/v4/social/elements/index'; +import { PauseIcon, PlayIcon } from '~/icons'; +import { CloseButton } from '~/v4/social/elements'; import { StoryAdInformation } from '~/v4/social/internal-components/StoryAdInformation/StoryAdInformation'; import { AdsBadge } from '~/v4/social/internal-components/AdsBadge/AdsBadge'; diff --git a/src/v4/social/internal-components/StoryCommentComposeBar/StoryCommentComposeBar.tsx b/src/v4/social/internal-components/StoryCommentComposeBar/StoryCommentComposeBar.tsx index 455a4ac17..12e39766f 100644 --- a/src/v4/social/internal-components/StoryCommentComposeBar/StoryCommentComposeBar.tsx +++ b/src/v4/social/internal-components/StoryCommentComposeBar/StoryCommentComposeBar.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; import { Mentionees, Metadata } from '~/v4/helpers/utils'; import { Close, Lock2Icon } from '~/icons'; -import { CommentComposeBar } from '../CommentComposeBar'; +import { CommentComposeBar } from '~/v4/social/internal-components/CommentComposeBar'; import styles from './StoryCommentComposeBar.module.css'; interface StoryCommentComposeBarProps { diff --git a/src/v4/social/internal-components/StoryViewer/Renderers/storyAd.tsx b/src/v4/social/internal-components/StoryViewer/Renderers/storyAd.tsx index ea781dd1d..7b38188f2 100644 --- a/src/v4/social/internal-components/StoryViewer/Renderers/storyAd.tsx +++ b/src/v4/social/internal-components/StoryViewer/Renderers/storyAd.tsx @@ -2,7 +2,7 @@ import { Tester, CustomRenderer } from './types'; import React, { useState, useEffect, useCallback } from 'react'; -import { UIStoryAd } from '../../StoryAd/UIStoryAd'; +import { UIStoryAd } from '~/v4/social/internal-components/StoryAd/UIStoryAd'; import { useImage } from '~/v4/core/hooks/useImage'; export const renderer: CustomRenderer = ({ story, action, config, onClose }) => { diff --git a/src/v4/social/internal-components/TextWithMention/TextWithMention.tsx b/src/v4/social/internal-components/TextWithMention/TextWithMention.tsx index 4a92a2ed0..372928f88 100644 --- a/src/v4/social/internal-components/TextWithMention/TextWithMention.tsx +++ b/src/v4/social/internal-components/TextWithMention/TextWithMention.tsx @@ -1,8 +1,8 @@ import { SerializedTextNode } from 'lexical'; import React, { useMemo } from 'react'; -import { Typography } from '~/v4/core/components/index'; +import { Typography } from '~/v4/core/components'; import { Mentioned } from '~/v4/helpers/utils'; -import { TextToEditorState } from '../../components/PostCommentComposer/PostCommentInput'; +import { TextToEditorState } from '~/v4/social/components/PostCommentComposer/PostCommentInput'; import styles from './TextWithMention.module.css'; interface TextWithMentionProps { diff --git a/src/v4/social/internal-components/VideoThumbnail/index.tsx b/src/v4/social/internal-components/VideoThumbnail/index.tsx index 4d54e05a1..e5f0e5226 100644 --- a/src/v4/social/internal-components/VideoThumbnail/index.tsx +++ b/src/v4/social/internal-components/VideoThumbnail/index.tsx @@ -1 +1 @@ -export { VideoThumbnail } from './VideoThumbnail'; \ No newline at end of file +export { VideoThumbnail } from './VideoThumbnail'; diff --git a/src/v4/social/pages/Application/index.tsx b/src/v4/social/pages/Application/index.tsx index 71cbb9516..50ccb6bee 100644 --- a/src/v4/social/pages/Application/index.tsx +++ b/src/v4/social/pages/Application/index.tsx @@ -7,8 +7,8 @@ import { PageTypes, useNavigation } from '~/v4/core/providers/NavigationProvider import { StoryProvider } from '~/v4/social/providers/StoryProvider'; import { SocialGlobalSearchPage } from '~/v4/social/pages/SocialGlobalSearchPage'; import { ViewStoryPage } from '~/v4/social/pages/StoryPage'; -import { SelectPostTargetPage } from '../SelectPostTargetPage'; -import { MyCommunitiesSearchPage } from '../MyCommunitiesSearchPage/MyCommunitiesSearchPage'; +import { SelectPostTargetPage } from '~/v4/social/pages/SelectPostTargetPage'; +import { MyCommunitiesSearchPage } from '~/v4/social/pages/MyCommunitiesSearchPage/MyCommunitiesSearchPage'; import styles from './Application.module.css'; import { AmityDraftStoryPage } from '..'; @@ -16,14 +16,12 @@ import { StoryTargetSelectionPage } from '~/v4/social/pages/StoryTargetSelection import CommunityFeed from '~/social/pages/CommunityFeed'; import UserFeedPage from '~/social/pages/UserFeed'; - const Application = () => { const { page } = useNavigation(); const [open, setOpen] = useState(false); const [socialSettings, setSocialSettings] = useState<Amity.SocialSettings | null>(null); - const toggleOpen = () => { setOpen(!open); }; diff --git a/src/v4/social/pages/PostComposerPage/index.tsx b/src/v4/social/pages/PostComposerPage/index.tsx index 79c9d9cd8..be4e93791 100644 --- a/src/v4/social/pages/PostComposerPage/index.tsx +++ b/src/v4/social/pages/PostComposerPage/index.tsx @@ -1 +1 @@ -export { PostComposerPage, Mode } from './PostComposerPage'; \ No newline at end of file +export { PostComposerPage, Mode } from './PostComposerPage'; diff --git a/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx b/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx index 4d7915e2f..a06fd3cc9 100644 --- a/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx +++ b/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx @@ -11,8 +11,8 @@ import { BackButton } from '~/v4/social/elements/BackButton'; import { useAmityPage } from '~/v4/core/hooks/uikit'; import styles from './PostDetailPage.module.css'; import { useDrawer } from '~/v4/core/providers/DrawerProvider'; -import { PostCommentComposer } from '../../components/PostCommentComposer/PostCommentComposer'; -import { PostCommentList } from '../../components/PostCommentList/PostCommentList'; +import { PostCommentComposer } from '~/v4/social/components/PostCommentComposer/PostCommentComposer'; +import { PostCommentList } from '~/v4/social/components/PostCommentList/PostCommentList'; interface PostDetailPageProps { id: string; diff --git a/src/v4/social/pages/SocialGlobalSearchPage/SocialGlobalSearchPage.tsx b/src/v4/social/pages/SocialGlobalSearchPage/SocialGlobalSearchPage.tsx index c228e7e13..7e326d07b 100644 --- a/src/v4/social/pages/SocialGlobalSearchPage/SocialGlobalSearchPage.tsx +++ b/src/v4/social/pages/SocialGlobalSearchPage/SocialGlobalSearchPage.tsx @@ -6,7 +6,7 @@ import { CommunitySearchResult } from '~/v4/social/components/CommunitySearchRes import { TabsBar } from '~/v4/social/internal-components/TabsBar'; import useCommunitiesCollection from '~/v4/core/hooks/collections/useCommunitiesCollection'; import { useAmityPage } from '~/v4/core/hooks/uikit'; -import { UserSearchResult } from '../../components/UserSearchResult'; +import { UserSearchResult } from '~/v4/social/components/UserSearchResult'; import { useUserQueryByDisplayName } from '~/v4/core/hooks/collections/useUsersCollection'; enum AmityGlobalSearchType { diff --git a/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx b/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx index de3bfa5aa..aa1419160 100644 --- a/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx +++ b/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx @@ -9,7 +9,7 @@ import { MyCommunitiesButton } from '~/v4/social/elements/MyCommunitiesButton'; import { Newsfeed } from '~/v4/social/components/Newsfeed'; import { useAmityPage } from '~/v4/core/hooks/uikit'; import { CreatePostMenu } from '~/v4/social/components/CreatePostMenu'; -import { useGlobalFeedContext } from '../../providers/GlobalFeedProvider'; +import { useGlobalFeedContext } from '~/v4/social/providers/GlobalFeedProvider'; import ExplorePage from '~/social/pages/Explore'; export enum HomePageTab { @@ -92,7 +92,7 @@ export function SocialHomePage() { </div> </div> <div className={styles.socialHomePage__contents} ref={containerRef} onScroll={handleScroll}> - {activeTab === HomePageTab.Newsfeed && <Newsfeed pageId={pageId} />} + {activeTab === HomePageTab.Newsfeed && <Newsfeed pageId={pageId} />} {activeTab === HomePageTab.Explore && <ExplorePage />} {activeTab === HomePageTab.MyCommunities && <MyCommunities pageId={pageId} />} </div> From 5a40ac3a9499edc5a755b29f01589641e6f548e6 Mon Sep 17 00:00:00 2001 From: Chaiwat Trisuwan <chaiwattsw@gmail.com> Date: Tue, 16 Jul 2024 14:58:16 +0700 Subject: [PATCH 257/300] fix: ASC-00000 - v4 comment list component (#505) * fix: done button * fix: add todo comment for type * fix: submit form function * fix: remove console.log * fix: apply themeStyle * fix: type * fix: modal overlay * fix: remove console.log * fix: integrate comment v4 * fix: comment list * fix: remove pkg version * fix: type * fix: type * fix: comment component * fix: comment component * fix: remove console.log * fix: css * fix: import name --- .../BottomSheet/BottomSheet.module.css | 1 - src/v4/core/hooks/objects/usePost.ts | 1 - .../Comment.module.css} | 0 .../Comment.stories.tsx} | 10 +- .../PostComment.tsx => Comment/Comment.tsx} | 92 ++++++++++--------- .../CommentSkeleton.module.css} | 0 .../CommentSkeleton.stories.tsx} | 8 +- .../CommentSkeleton.tsx} | 9 +- src/v4/social/components/Comment/index.tsx | 2 + .../CommentComposer.module.css} | 56 +++++++---- .../CommentComposer.tsx} | 86 ++++++++++------- .../CommentInput.module.css} | 0 .../CommentInput.tsx} | 34 +++---- .../components/CommentComposer/index.tsx | 1 + .../CommentList/CommentList.module.css | 26 ++++++ .../CommentList.tsx} | 72 +++++++++++---- src/v4/social/components/CommentList/index.ts | 1 + .../CommentMentionInput.module.css} | 0 .../CommentMentionInput.tsx} | 4 +- .../CommentMentionNode.tsx} | 0 .../MentionUser.module.css} | 0 .../MentionUser.tsx} | 13 +-- .../components/CommentMentionInput/index.ts | 1 + .../CommentTray/CommentTray.module.css | 1 + .../components/CommentTray/CommentTray.tsx | 36 +++----- .../social/components/PostComment/index.tsx | 2 - .../components/PostCommentComposer/index.tsx | 1 - .../PostCommentList.module.css | 18 ---- .../components/PostCommentList/index.ts | 1 - .../PostCommentMentionInput/index.ts | 1 - .../ReplyComment.module.css} | 0 .../ReplyComment.tsx} | 25 ++--- .../ReplyCommentList.module.css} | 0 .../ReplyCommentList.tsx} | 30 +++--- src/v4/social/hooks/useMentionUser.ts | 21 +++-- .../StoryPreviewThumbnail.tsx | 1 - .../TextWithMention/TextWithMention.tsx | 2 +- .../PostDetailPage/PostDetailPage.module.css | 1 + .../pages/PostDetailPage/PostDetailPage.tsx | 15 +-- 39 files changed, 318 insertions(+), 254 deletions(-) rename src/v4/social/components/{PostComment/PostComment.module.css => Comment/Comment.module.css} (100%) rename src/v4/social/components/{PostComment/PostComment.stories.tsx => Comment/Comment.stories.tsx} (51%) rename src/v4/social/components/{PostComment/PostComment.tsx => Comment/Comment.tsx} (83%) rename src/v4/social/components/{PostComment/PostCommentSkeleton.module.css => Comment/CommentSkeleton.module.css} (100%) rename src/v4/social/components/{PostComment/PostCommentSkeleton.stories.tsx => Comment/CommentSkeleton.stories.tsx} (56%) rename src/v4/social/components/{PostComment/PostCommentSkeleton.tsx => Comment/CommentSkeleton.tsx} (74%) create mode 100644 src/v4/social/components/Comment/index.tsx rename src/v4/social/components/{PostCommentComposer/PostCommentComposer.module.css => CommentComposer/CommentComposer.module.css} (53%) rename src/v4/social/components/{PostCommentComposer/PostCommentComposer.tsx => CommentComposer/CommentComposer.tsx} (54%) rename src/v4/social/components/{PostCommentComposer/PostCommentInput.module.css => CommentComposer/CommentInput.module.css} (100%) rename src/v4/social/components/{PostCommentComposer/PostCommentInput.tsx => CommentComposer/CommentInput.tsx} (90%) create mode 100644 src/v4/social/components/CommentComposer/index.tsx create mode 100644 src/v4/social/components/CommentList/CommentList.module.css rename src/v4/social/components/{PostCommentList/PostCommentList.tsx => CommentList/CommentList.tsx} (58%) create mode 100644 src/v4/social/components/CommentList/index.ts rename src/v4/social/components/{PostCommentMentionInput/PostCommentMentionInput.module.css => CommentMentionInput/CommentMentionInput.module.css} (100%) rename src/v4/social/components/{PostCommentMentionInput/PostCommentMentionInput.tsx => CommentMentionInput/CommentMentionInput.tsx} (98%) rename src/v4/social/components/{PostCommentMentionInput/PostCommentMentionNode.tsx => CommentMentionInput/CommentMentionNode.tsx} (100%) rename src/v4/social/components/{PostCommentMentionInput/PostMentionUser.module.css => CommentMentionInput/MentionUser.module.css} (100%) rename src/v4/social/components/{PostCommentMentionInput/PostMentionUser.tsx => CommentMentionInput/MentionUser.tsx} (77%) create mode 100644 src/v4/social/components/CommentMentionInput/index.ts delete mode 100644 src/v4/social/components/PostComment/index.tsx delete mode 100644 src/v4/social/components/PostCommentComposer/index.tsx delete mode 100644 src/v4/social/components/PostCommentList/PostCommentList.module.css delete mode 100644 src/v4/social/components/PostCommentList/index.ts delete mode 100644 src/v4/social/components/PostCommentMentionInput/index.ts rename src/v4/social/components/{PostReplyComment/PostReplyComment.module.css => ReplyComment/ReplyComment.module.css} (100%) rename src/v4/social/components/{PostReplyComment/PostReplyComment.tsx => ReplyComment/ReplyComment.tsx} (93%) rename src/v4/social/components/{PostReplyCommentList/PostReplyCommentList.module.css => ReplyCommentList/ReplyCommentList.module.css} (100%) rename src/v4/social/components/{PostReplyCommentList/PostReplyCommentList.tsx => ReplyCommentList/ReplyCommentList.tsx} (61%) diff --git a/src/v4/core/components/BottomSheet/BottomSheet.module.css b/src/v4/core/components/BottomSheet/BottomSheet.module.css index 91eb82254..a81aa414e 100644 --- a/src/v4/core/components/BottomSheet/BottomSheet.module.css +++ b/src/v4/core/components/BottomSheet/BottomSheet.module.css @@ -20,7 +20,6 @@ which have higher specificity. .bottomSheet__content { background-color: var(--asc-color-background-default); - padding: var(--asc-spacing-m1); } .bottomSheet__backdrop { diff --git a/src/v4/core/hooks/objects/usePost.ts b/src/v4/core/hooks/objects/usePost.ts index dcd859c02..ae9a21436 100644 --- a/src/v4/core/hooks/objects/usePost.ts +++ b/src/v4/core/hooks/objects/usePost.ts @@ -1,5 +1,4 @@ import { PostRepository } from '@amityco/ts-sdk'; - import useLiveObject from '~/v4/core/hooks/useLiveObject'; const usePost = (postId?: string) => { diff --git a/src/v4/social/components/PostComment/PostComment.module.css b/src/v4/social/components/Comment/Comment.module.css similarity index 100% rename from src/v4/social/components/PostComment/PostComment.module.css rename to src/v4/social/components/Comment/Comment.module.css diff --git a/src/v4/social/components/PostComment/PostComment.stories.tsx b/src/v4/social/components/Comment/Comment.stories.tsx similarity index 51% rename from src/v4/social/components/PostComment/PostComment.stories.tsx rename to src/v4/social/components/Comment/Comment.stories.tsx index a003f065c..0aca057d3 100644 --- a/src/v4/social/components/PostComment/PostComment.stories.tsx +++ b/src/v4/social/components/Comment/Comment.stories.tsx @@ -1,20 +1,20 @@ import React from 'react'; import useOneComment from '~/mock/useOneComment'; -import { PostComment } from './PostComment'; +import { Comment } from './Comment'; export default { - title: 'v4-social/components/PostComment', + title: 'v4-social/components/Comment', }; -export const PostCommentStory = { +export const CommentStory = { render: () => { const [comment] = useOneComment(); if (comment == null) return null; - return <PostComment comment={comment} />; + return <Comment comment={comment} />; }, - name: 'PostComment', + name: 'Comment', }; diff --git a/src/v4/social/components/PostComment/PostComment.tsx b/src/v4/social/components/Comment/Comment.tsx similarity index 83% rename from src/v4/social/components/PostComment/PostComment.tsx rename to src/v4/social/components/Comment/Comment.tsx index 1dbc9c1a2..19f9268d9 100644 --- a/src/v4/social/components/PostComment/PostComment.tsx +++ b/src/v4/social/components/Comment/Comment.tsx @@ -3,10 +3,10 @@ import { Typography, BottomSheet } from '~/v4/core/components'; import { ModeratorBadge } from '~/v4/social/elements/ModeratorBadge'; import { Timestamp } from '~/v4/social/elements/Timestamp'; import { UserAvatar } from '~/v4/social/internal-components/UserAvatar'; -import styles from './PostComment.module.css'; +import styles from './Comment.module.css'; import { useAmityComponent } from '~/v4/core/hooks/uikit'; import ReplyComment from '~/v4/icons/ReplyComment'; -import { PostReplyCommentList } from '~/v4/social/components/PostReplyCommentList/PostReplyCommentList'; +import { ReplyCommentList } from '~/v4/social/components/ReplyCommentList/ReplyCommentList'; import { MinusCircleIcon } from '~/v4/social/icons'; import { Mentionees } from '~/v4/helpers/utils'; import { CommentRepository, ReactionRepository } from '@amityco/ts-sdk'; @@ -15,9 +15,9 @@ import { LIKE_REACTION_KEY } from '~/v4/social/constants/reactions'; import { EditCancelButton } from '~/v4/social/elements/EditCancelButton/EditCancelButton'; import { SaveButton } from '~/v4/social/elements/SaveButton/SaveButton'; import clsx from 'clsx'; -import { PostCommentInput } from '~/v4/social/components/PostCommentComposer/PostCommentInput'; +import { CommentInput } from '~/v4/social/components/CommentComposer/CommentInput'; import { CommentOptions } from '~/v4/social/components/CommentOptions/CommentOptions'; -import { CreateCommentParams } from '~/v4/social/components/PostCommentComposer/PostCommentComposer'; +import { CreateCommentParams } from '~/v4/social/components/CommentComposer/CommentComposer'; import useCommentSubscription from '~/v4/core/hooks/subscriptions/useCommentSubscription'; import { TextWithMention } from '~/v4/social/internal-components/TextWithMention/TextWithMention'; import millify from 'millify'; @@ -65,23 +65,25 @@ const Like = ({ ...props }: React.SVGProps<SVGSVGElement>) => ( </svg> ); -interface PostCommentProps { +interface CommentProps { pageId?: string; componentId?: string; comment: Amity.Comment; - postTargetType: Amity.PostTargetType; - postTargetId: string; + targetType: Amity.PostTargetType | Amity.StoryTargetType; + targetId: string; onClickReply: (comment: Amity.Comment) => void; + shoudAllowInteraction?: boolean; } -export const PostComment = ({ +export const Comment = ({ pageId = '*', componentId = 'comment_bubble', comment, - postTargetType, - postTargetId, + targetType, + targetId, onClickReply, -}: PostCommentProps) => { + shoudAllowInteraction = true, +}: CommentProps) => { const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = useAmityComponent({ pageId, @@ -162,9 +164,9 @@ export const PostComment = ({ <UserAvatar userId={comment.userId} /> <div className={styles.postComment__edit__inputWrap}> <div className={styles.postComment__edit__input}> - <PostCommentInput - postTargetType={postTargetType} - postTargetId={postTargetId} + <CommentInput + targetType={targetType} + targetId={targetId} value={{ data: { text: (comment.data as Amity.ContentDataText).text, @@ -219,35 +221,37 @@ export const PostComment = ({ /> </div> <div className={styles.postComment__secondRow}> - <div className={styles.postComment__secondRow__leftPane}> - <Typography.Caption className={styles.postComment__secondRow__timestamp}> - <Timestamp - pageId={pageId} - componentId={componentId} - timestamp={comment.createdAt} - /> - {comment.createdAt !== comment.editedAt && ' (edited)'} - </Typography.Caption> + {shoudAllowInteraction && ( + <div className={styles.postComment__secondRow__leftPane}> + <Typography.Caption className={styles.postComment__secondRow__timestamp}> + <Timestamp + pageId={pageId} + componentId={componentId} + timestamp={comment.createdAt} + /> + {comment.createdAt !== comment.editedAt && ' (edited)'} + </Typography.Caption> - <div onClick={handleLike}> - <Typography.CaptionBold - className={styles.postComment__secondRow__like} - data-is-liked={isLiked} - > - {isLiked ? 'Liked' : 'Like'} - </Typography.CaptionBold> - </div> - <div onClick={() => onClickReply(comment)}> - <Typography.CaptionBold className={styles.postComment__secondRow__reply}> - Reply - </Typography.CaptionBold> - </div> + <div onClick={handleLike}> + <Typography.CaptionBold + className={styles.postComment__secondRow__like} + data-is-liked={isLiked} + > + {isLiked ? 'Liked' : 'Like'} + </Typography.CaptionBold> + </div> + <div onClick={() => onClickReply(comment)}> + <Typography.CaptionBold className={styles.postComment__secondRow__reply}> + Reply + </Typography.CaptionBold> + </div> - <EllipsisH - className={styles.postComment__secondRow__actionButton} - onClick={() => setBottomSheetOpen(true)} - /> - </div> + <EllipsisH + className={styles.postComment__secondRow__actionButton} + onClick={() => setBottomSheetOpen(true)} + /> + </div> + )} {comment.reactionsCount > 0 && ( <div className={styles.postComment__secondRow__rightPane}> <Typography.Caption>{millify(comment.reactionsCount)}</Typography.Caption> @@ -269,9 +273,9 @@ export const PostComment = ({ )} {hasClickLoadMore && ( - <PostReplyCommentList - postTargetType={postTargetType} - postTargetId={postTargetId} + <ReplyCommentList + targetType={targetType} + targetId={targetId} referenceId={comment.referenceId} parentId={comment.commentId} /> diff --git a/src/v4/social/components/PostComment/PostCommentSkeleton.module.css b/src/v4/social/components/Comment/CommentSkeleton.module.css similarity index 100% rename from src/v4/social/components/PostComment/PostCommentSkeleton.module.css rename to src/v4/social/components/Comment/CommentSkeleton.module.css diff --git a/src/v4/social/components/PostComment/PostCommentSkeleton.stories.tsx b/src/v4/social/components/Comment/CommentSkeleton.stories.tsx similarity index 56% rename from src/v4/social/components/PostComment/PostCommentSkeleton.stories.tsx rename to src/v4/social/components/Comment/CommentSkeleton.stories.tsx index 869f47b89..03c6555ce 100644 --- a/src/v4/social/components/PostComment/PostCommentSkeleton.stories.tsx +++ b/src/v4/social/components/Comment/CommentSkeleton.stories.tsx @@ -1,10 +1,10 @@ import React from 'react'; import useOnePost from '~/mock/useOnePost'; -import { PostCommentSkeleton } from './PostCommentSkeleton'; +import { CommentSkeleton } from './CommentSkeleton'; export default { - title: 'v4-social/components/PostCommentSkeleton', + title: 'v4-social/components/CommentSkeleton', }; export const PostCommentSkeletonStory = { @@ -13,8 +13,8 @@ export const PostCommentSkeletonStory = { if (post == null) return null; - return <PostCommentSkeleton />; + return <CommentSkeleton />; }, - name: 'PostCommentSkeleton', + name: 'CommentSkeleton', }; diff --git a/src/v4/social/components/PostComment/PostCommentSkeleton.tsx b/src/v4/social/components/Comment/CommentSkeleton.tsx similarity index 74% rename from src/v4/social/components/PostComment/PostCommentSkeleton.tsx rename to src/v4/social/components/Comment/CommentSkeleton.tsx index 7ad2cbb0f..2c1de67f1 100644 --- a/src/v4/social/components/PostComment/PostCommentSkeleton.tsx +++ b/src/v4/social/components/Comment/CommentSkeleton.tsx @@ -1,17 +1,14 @@ import React from 'react'; import clsx from 'clsx'; - -import styles from './PostCommentSkeleton.module.css'; -import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; -import { useGenerateStylesShadeColors } from '~/v4/core/providers/ThemeProvider'; +import styles from './CommentSkeleton.module.css'; import { useAmityComponent } from '~/v4/core/hooks/uikit'; interface PostCommentSkeletonProps { pageId?: string; + componentId?: string; } -export const PostCommentSkeleton = ({ pageId = '*' }: PostCommentSkeletonProps) => { - const componentId = 'post_comment'; +export const CommentSkeleton = ({ pageId = '*', componentId = '*' }: PostCommentSkeletonProps) => { const { accessibilityId, isExcluded, themeStyles } = useAmityComponent({ pageId, componentId, diff --git a/src/v4/social/components/Comment/index.tsx b/src/v4/social/components/Comment/index.tsx new file mode 100644 index 000000000..82e6f1126 --- /dev/null +++ b/src/v4/social/components/Comment/index.tsx @@ -0,0 +1,2 @@ +export { Comment } from './Comment'; +export { CommentSkeleton } from './CommentSkeleton'; diff --git a/src/v4/social/components/PostCommentComposer/PostCommentComposer.module.css b/src/v4/social/components/CommentComposer/CommentComposer.module.css similarity index 53% rename from src/v4/social/components/PostCommentComposer/PostCommentComposer.module.css rename to src/v4/social/components/CommentComposer/CommentComposer.module.css index 554f64b3e..dd5872c63 100644 --- a/src/v4/social/components/PostCommentComposer/PostCommentComposer.module.css +++ b/src/v4/social/components/CommentComposer/CommentComposer.module.css @@ -1,43 +1,53 @@ -.postCommentComposer__container { +.container { + display: flex; + align-items: center; + justify-content: center; + width: 100%; +} + +.commentComposer__container { position: relative; display: flex; - flex-direction: row; - background-color: var(--asc-color-background-default); align-items: flex-end; - padding: var(--asc-spacing-s1) 0 var(--asc-spacing-s1) var(--asc-spacing-m1); + gap: 0.5rem; + padding: 0.5rem 0.75rem; + background-color: var(--asc-color-background-default); border-top: 1px solid var(--asc-color-base-shade4); width: 100%; + flex-direction: row; } -.postCommentComposer__avatar { +.commentComposer__avatar { width: 2rem; height: 2rem; - margin-bottom: var(--asc-spacing-xxs2); border-radius: var(--asc-border-radius-full); - margin-right: var(--asc-spacing-s1); + margin: 0 auto; + display: flex; + margin-bottom: 0.25rem; + flex-shrink: 0; } -.postCommentComposer__button { - border: none; - margin-bottom: var(--asc-spacing-xxs2); +.commentComposer__button { + cursor: pointer; + margin-bottom: 0.5rem; } -.postCommentComposer__button:hover { +.commentComposer__button:hover { background-color: transparent !important; } -.postCommentComposer__button:disabled { +.commentComposer__button:disabled { color: var(--asc-color-primary-shade2); } -.postCommentComposer__input { - flex-grow: 1; +.commentComposer__input { + width: 100%; background-color: var(--asc-color-base-shade4); border-radius: 1.25rem; padding: 0.625rem 0.75rem; } -.postCommentComposer__replyContainer { +.commentComposer__replyContainer { position: absolute; left: 0; width: 100%; @@ -48,20 +58,30 @@ background-color: var(--asc-color-base-shade4); } -.postCommentComposer__replyContainer__text { +.commentComposer__replyContainer__text { font-size: var(--asc-text-font-size-sm); font-weight: var(--asc-text-font-weight-normal); line-height: var(--asc-line-height-sm); color: var(--asc-color-base-shade1); } -.postCommentComposer__replyContainer__username { +.commentComposer__replyContainer__username { font-weight: var(--asc-text-font-weight-bold); } -.postCommentComposer__replyContainer__closeButton { +.commentComposer__replyContainer__closeButton { fill: var(--asc-color-base-shade2); width: 1.25rem; height: 1.25rem; cursor: pointer; } + +.commentComposer__disableContainer { + display: flex; + justify-content: center; + padding: 0.625rem 1rem; + gap: var(--asc-spacing-m1); + align-items: center; + border-top: 1px solid var(--asc-color-base-shade4); + color: var(--asc-color-base-shade2); +} diff --git a/src/v4/social/components/PostCommentComposer/PostCommentComposer.tsx b/src/v4/social/components/CommentComposer/CommentComposer.tsx similarity index 54% rename from src/v4/social/components/PostCommentComposer/PostCommentComposer.tsx rename to src/v4/social/components/CommentComposer/CommentComposer.tsx index 9c1f4388e..35eb68cbd 100644 --- a/src/v4/social/components/PostCommentComposer/PostCommentComposer.tsx +++ b/src/v4/social/components/CommentComposer/CommentComposer.tsx @@ -1,16 +1,29 @@ import React, { useEffect, useRef, useState } from 'react'; -import { Avatar, Button } from '~/v4/core/components'; +import { Avatar, Typography } from '~/v4/core/components'; import { useUser } from '~/v4/core/hooks/objects/useUser'; import { useImage } from '~/v4/core/hooks/useImage'; import useSDK from '~/v4/core/hooks/useSDK'; import User from '~/v4/icons/User'; -import { PostCommentInput, PostCommentInputRef } from './PostCommentInput'; -import styles from './PostCommentComposer.module.css'; +import { Button } from '~/v4/core/natives/Button'; +import { CommentInput, CommentInputRef } from './CommentInput'; import { useMutation } from '@tanstack/react-query'; import { CommentRepository } from '@amityco/ts-sdk'; import Close from '~/v4/icons/Close'; import { Mentionees, Metadata } from '~/v4/helpers/utils'; +import styles from './CommentComposer.module.css'; + +const LockSvg = () => { + return ( + <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" viewBox="0 0 20 20"> + <path + fill="currentColor" + d="M15.5 8c.813 0 1.5.688 1.5 1.5v7a1.5 1.5 0 01-1.5 1.5h-11A1.48 1.48 0 013 16.5v-7A1.5 1.5 0 014.5 8h1V6.5C5.5 4.031 7.5 2 10 2c2.5.031 4.5 2.063 4.5 4.563V8h1zM7 6.5V8h6V6.5c0-1.625-1.375-3-3-3-1.656 0-3 1.375-3 3zm8.5 10v-7h-11v7h11z" + ></path> + </svg> + ); +}; + export type CreateCommentParams = { data: { text: string; @@ -19,19 +32,25 @@ export type CreateCommentParams = { metadata: Metadata; }; -export const PostCommentComposer = ({ - post, - replyTo, - onCancelReply, -}: { - post: Amity.Post; +interface CommentComposerProps { + referenceId: string; + referenceType: Amity.CommentReferenceType; replyTo?: Amity.Comment; onCancelReply: () => void; -}) => { + shouldAllowCreation?: boolean; +} + +export const CommentComposer = ({ + referenceId, + referenceType, + replyTo, + onCancelReply, + shouldAllowCreation = true, +}: CommentComposerProps) => { const userId = useSDK().currentUserId; const { user } = useUser(userId); const avatarUrl = useImage({ fileId: user?.avatar?.fileId, imageSize: 'small' }); - const editorRef = useRef<PostCommentInputRef | null>(null); + const editorRef = useRef<CommentInputRef | null>(null); const composerRef = useRef<HTMLDivElement | null>(null); const [composerHeight, setComposerHeight] = useState(0); @@ -55,12 +74,10 @@ export const PostCommentComposer = ({ const { mutateAsync } = useMutation({ mutationFn: async ({ params }: { params: CreateCommentParams }) => { - const referenceId = replyTo ? replyTo.referenceId : post.postId; - const referenceType = replyTo ? replyTo.referenceType : 'post'; const parentId = replyTo ? replyTo.commentId : undefined; await CommentRepository.createComment({ - referenceId, + referenceId: replyTo ? replyTo.referenceId : referenceId, referenceType, parentId, ...params, @@ -85,44 +102,49 @@ export const PostCommentComposer = ({ } }, []); + if (!shouldAllowCreation) { + return ( + <div className={styles.commentComposer__disableContainer}> + <LockSvg /> + <Typography.Body>Comments are disabled for this story</Typography.Body> + </div> + ); + } + return ( - <div className={styles.postCommentComposer__container} ref={composerRef}> - <div className={styles.postCommentComposer__avatar}> + <div className={styles.commentComposer__container} ref={composerRef}> + <div className={styles.commentComposer__avatar}> <Avatar avatarUrl={avatarUrl} defaultImage={<User />} /> </div> - <div className={styles.postCommentComposer__input}> - <PostCommentInput + <div className={styles.commentComposer__input}> + <CommentInput ref={editorRef} onChange={onChange} - postTargetType={post.targetType} - postTargetId={post.targetId} + targetType={referenceType} + targetId={referenceId} mentionOffsetBottom={-composerHeight} value={textValue} placehoder="Say something nice..." /> </div> <Button - variant="ghost" - disabled={!textValue.data.text} - className={styles.postCommentComposer__button} - onClick={() => mutateAsync({ params: textValue })} + isDisabled={!textValue.data.text} + className={styles.commentComposer__button} + onPress={() => mutateAsync({ params: textValue })} > - Post + <Typography.Body>Post</Typography.Body> </Button> {replyTo && ( - <div - className={styles.postCommentComposer__replyContainer} - style={{ bottom: composerHeight }} - > - <div className={styles.postCommentComposer__replyContainer__text}> + <div className={styles.commentComposer__replyContainer} style={{ bottom: composerHeight }}> + <div className={styles.commentComposer__replyContainer__text}> <span>Replying to </span> - <span className={styles.postCommentComposer__replyContainer__username}> + <span className={styles.commentComposer__replyContainer__username}> {replyTo?.userId} </span> </div> <Close onClick={onCancelReply} - className={styles.postCommentComposer__replyContainer__closeButton} + className={styles.commentComposer__replyContainer__closeButton} /> </div> )} diff --git a/src/v4/social/components/PostCommentComposer/PostCommentInput.module.css b/src/v4/social/components/CommentComposer/CommentInput.module.css similarity index 100% rename from src/v4/social/components/PostCommentComposer/PostCommentInput.module.css rename to src/v4/social/components/CommentComposer/CommentInput.module.css diff --git a/src/v4/social/components/PostCommentComposer/PostCommentInput.tsx b/src/v4/social/components/CommentComposer/CommentInput.tsx similarity index 90% rename from src/v4/social/components/PostCommentComposer/PostCommentInput.tsx rename to src/v4/social/components/CommentComposer/CommentInput.tsx index 41d07b906..531284d53 100644 --- a/src/v4/social/components/PostCommentComposer/PostCommentInput.tsx +++ b/src/v4/social/components/CommentComposer/CommentInput.tsx @@ -19,10 +19,10 @@ import { MentionNode, SerializedMentionNode, } from '~/v4/social/internal-components/MentionTextInput/MentionNodes'; -import styles from './PostCommentInput.module.css'; -import { PostCommentMentionInput } from '~/v4/social/components/PostCommentMentionInput'; +import styles from './CommentInput.module.css'; +import { CommentMentionInput } from '~/v4/social/components/CommentMentionInput'; import { useMentionUsers } from '~/v4/social/hooks/useMentionUser'; -import { CreateCommentParams } from '~/v4/social/components/PostCommentComposer/PostCommentComposer'; +import { CreateCommentParams } from '~/v4/social/components/CommentComposer/CommentComposer'; import { Mentioned, Mentionees } from '~/v4/helpers/utils'; const theme = { @@ -33,7 +33,7 @@ const theme = { }; const editorConfig = { - namespace: 'PostCommentInput', + namespace: 'CommentInput', theme: theme, onError(error: Error) { throw error; @@ -45,9 +45,9 @@ interface EditorStateJson extends SerializedLexicalNode { children: []; } -interface PostCommentInputProps { - postTargetType: Amity.PostTargetType; - postTargetId: string; +interface CommentInputProps { + targetType: Amity.PostTargetType | Amity.StoryTargetType; + targetId: string; value?: CreateCommentParams; mentionOffsetBottom?: number; maxLines?: number; @@ -56,7 +56,7 @@ interface PostCommentInputProps { onChange: (data: CreateCommentParams) => void; } -export interface PostCommentInputRef { +export interface CommentInputRef { clearEditorState: () => void; } @@ -223,17 +223,9 @@ export function TextToEditorState(value: { return { root: rootNode }; } -export const PostCommentInput = forwardRef<PostCommentInputRef, PostCommentInputProps>( +export const CommentInput = forwardRef<CommentInputRef, CommentInputProps>( ( - { - postTargetType, - postTargetId, - mentionOffsetBottom = 0, - value, - onChange, - maxLines = 10, - placehoder, - }, + { targetType, targetId, mentionOffsetBottom = 0, value, onChange, maxLines = 10, placehoder }, ref, ) => { const editorRef = React.useRef<LexicalEditor | null | undefined>(null); @@ -241,8 +233,8 @@ export const PostCommentInput = forwardRef<PostCommentInputRef, PostCommentInput const { mentionUsers } = useMentionUsers({ displayName: queryMentionUser || '', - postTargetType: postTargetType, - postTargetId: postTargetId, + targetType, + targetId, }); const clearEditorState = () => { @@ -286,7 +278,7 @@ export const PostCommentInput = forwardRef<PostCommentInputRef, PostCommentInput <HistoryPlugin /> <AutoFocusPlugin /> <EditorRefPlugin editorRef={editorRef} /> - <PostCommentMentionInput + <CommentMentionInput mentionUsers={mentionUsers as Amity.User[]} onQueryChange={(query: string) => setQueryMentionUser(query)} offsetBottom={mentionOffsetBottom} diff --git a/src/v4/social/components/CommentComposer/index.tsx b/src/v4/social/components/CommentComposer/index.tsx new file mode 100644 index 000000000..c95a776c3 --- /dev/null +++ b/src/v4/social/components/CommentComposer/index.tsx @@ -0,0 +1 @@ +export { CommentComposer } from './CommentComposer'; diff --git a/src/v4/social/components/CommentList/CommentList.module.css b/src/v4/social/components/CommentList/CommentList.module.css new file mode 100644 index 000000000..dde2c02dc --- /dev/null +++ b/src/v4/social/components/CommentList/CommentList.module.css @@ -0,0 +1,26 @@ +.commentList__container { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.commentList__viewAllComments__button { + width: 100%; + padding: var(--asc-spacing-s2) 0; + margin: auto; + border-top: 0.0625rem solid var(--asc-color-base-shade4); + cursor: pointer; + text-align: center; +} + +.commentList__container_intersection { + height: 1px; +} + +.noCommentsContainer { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + color: var(--asc-color-base-shade2); +} diff --git a/src/v4/social/components/PostCommentList/PostCommentList.tsx b/src/v4/social/components/CommentList/CommentList.tsx similarity index 58% rename from src/v4/social/components/PostCommentList/PostCommentList.tsx rename to src/v4/social/components/CommentList/CommentList.tsx index 4baeb8d40..b2acf26da 100644 --- a/src/v4/social/components/PostCommentList/PostCommentList.tsx +++ b/src/v4/social/components/CommentList/CommentList.tsx @@ -1,6 +1,6 @@ import React, { useRef } from 'react'; -import styles from './PostCommentList.module.css'; -import { PostComment } from '~/v4/social/components/PostComment/PostComment'; + +import { Comment } from '~/v4/social/components/Comment/Comment'; import useIntersectionObserver from '~/v4/core/hooks/useIntersectionObserver'; import useCommentsCollection from '~/v4/social/hooks/collections/useCommentsCollection'; import { useAmityComponent } from '~/v4/core/hooks/uikit'; @@ -9,18 +9,37 @@ import { CommentRepository, SubscriptionLevels } from '@amityco/ts-sdk'; import useCommunitySubscription from '~/v4/core/hooks/subscriptions/useCommunitySubscription'; import { usePaginator } from '~/v4/core/hooks/usePaginator'; import { CommentAd } from '~/v4/social/internal-components/CommentAd/CommentAd'; +import styles from './CommentList.module.css'; +import useCommunityStoriesSubscription from '~/v4/social/hooks/useCommunityStoriesSubscription'; +import { Typography } from '~/v4/core/components'; -type PostCommentListProps = { - post: Amity.Post; +type CommentListProps = { + referenceId: string; + referenceType: Amity.CommentReferenceType; pageId?: string; onClickReply: (comment: Amity.Comment) => void; + limit?: number; + includeDeleted?: boolean; + community?: Amity.Community; + post?: Amity.Post; + shouldAllowInteraction?: boolean; }; const isAmityAd = (item: Amity.Comment | Amity.InternalComment | Amity.Ad): item is Amity.Ad => { return 'adId' in item; }; -export const PostCommentList = ({ post, pageId = '*', onClickReply }: PostCommentListProps) => { +export const CommentList = ({ + referenceId, + referenceType, + pageId = '*', + onClickReply, + limit = 5, + includeDeleted = false, + community, + post, + shouldAllowInteraction = true, +}: CommentListProps) => { const componentId = 'comment_tray_component'; const { themeStyles, accessibilityId } = useAmityComponent({ @@ -34,14 +53,15 @@ export const PostCommentList = ({ post, pageId = '*', onClickReply }: PostCommen const { items, loadMore, hasMore, isLoading } = usePaginator({ fetcher: CommentRepository.getComments, params: { - referenceId: post.postId, - referenceType: 'post', - limit: 5, - includeDeleted: true, + referenceId, + referenceType, + limit, + includeDeleted, }, placement: 'comment' as Amity.AdPlacement, pageSize: 5, getItemId: (item) => item.commentId, + shouldCall: true, }); useIntersectionObserver({ @@ -54,20 +74,35 @@ export const PostCommentList = ({ post, pageId = '*', onClickReply }: PostCommen }); useUserSubscription({ - userId: post.targetId, + userId: post?.targetId, level: SubscriptionLevels.COMMENT, - shouldSubscribe: post.targetType === 'user', + shouldSubscribe: !!post?.targetId, }); useCommunitySubscription({ - communityId: post.targetId, + communityId: community?.communityId, level: SubscriptionLevels.COMMENT, - shouldSubscribe: post.targetType === 'community', + shouldSubscribe: !!community?.communityId, + }); + + useCommunityStoriesSubscription({ + targetId: referenceId, + // TODO: fix type it's actually have the same type but different name + targetType: referenceType as Amity.StoryTargetType, + shouldSubscribe: referenceType === 'story' && !!referenceId, }); + if (items.length === 0) { + return ( + <div className={styles.noCommentsContainer}> + <Typography.Body>No comments yet</Typography.Body> + </div> + ); + } + return ( <div - className={styles.postCommentList__container} + className={styles.commentList__container} style={themeStyles} ref={containerRef} data-qa-anchor={accessibilityId} @@ -76,13 +111,14 @@ export const PostCommentList = ({ post, pageId = '*', onClickReply }: PostCommen return isAmityAd(item) ? ( <CommentAd key={item.adId} ad={item} /> ) : ( - <PostComment + <Comment key={item.commentId} comment={item as Amity.Comment} onClickReply={(comment) => onClickReply?.(comment)} componentId={componentId} - postTargetId={post.targetId} - postTargetType={post.targetType} + targetId={referenceId} + targetType={referenceType} + shoudAllowInteraction={shouldAllowInteraction} /> ); })} @@ -91,7 +127,7 @@ export const PostCommentList = ({ post, pageId = '*', onClickReply }: PostCommen <Typography.BodyBold>View all comments...</Typography.BodyBold> </div> */} {!isLoading && ( - <div ref={intersectionRef} className={styles.postCommentList__container_intersection} /> + <div ref={intersectionRef} className={styles.commentList__container_intersection} /> )} </div> ); diff --git a/src/v4/social/components/CommentList/index.ts b/src/v4/social/components/CommentList/index.ts new file mode 100644 index 000000000..063be3461 --- /dev/null +++ b/src/v4/social/components/CommentList/index.ts @@ -0,0 +1 @@ +export { CommentList } from './CommentList'; diff --git a/src/v4/social/components/PostCommentMentionInput/PostCommentMentionInput.module.css b/src/v4/social/components/CommentMentionInput/CommentMentionInput.module.css similarity index 100% rename from src/v4/social/components/PostCommentMentionInput/PostCommentMentionInput.module.css rename to src/v4/social/components/CommentMentionInput/CommentMentionInput.module.css diff --git a/src/v4/social/components/PostCommentMentionInput/PostCommentMentionInput.tsx b/src/v4/social/components/CommentMentionInput/CommentMentionInput.tsx similarity index 98% rename from src/v4/social/components/PostCommentMentionInput/PostCommentMentionInput.tsx rename to src/v4/social/components/CommentMentionInput/CommentMentionInput.tsx index dbcf13bdf..37ff30e4a 100644 --- a/src/v4/social/components/PostCommentMentionInput/PostCommentMentionInput.tsx +++ b/src/v4/social/components/CommentMentionInput/CommentMentionInput.tsx @@ -1,4 +1,4 @@ -import styles from './PostCommentMentionInput.module.css'; +import styles from './CommentMentionInput.module.css'; import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; import { LexicalTypeaheadMenuPlugin, @@ -108,7 +108,7 @@ export class MentionTypeaheadOption extends MenuOption { } } -export const PostCommentMentionInput = ({ +export const CommentMentionInput = ({ mentionUsers, offsetBottom = 0, onQueryChange, diff --git a/src/v4/social/components/PostCommentMentionInput/PostCommentMentionNode.tsx b/src/v4/social/components/CommentMentionInput/CommentMentionNode.tsx similarity index 100% rename from src/v4/social/components/PostCommentMentionInput/PostCommentMentionNode.tsx rename to src/v4/social/components/CommentMentionInput/CommentMentionNode.tsx diff --git a/src/v4/social/components/PostCommentMentionInput/PostMentionUser.module.css b/src/v4/social/components/CommentMentionInput/MentionUser.module.css similarity index 100% rename from src/v4/social/components/PostCommentMentionInput/PostMentionUser.module.css rename to src/v4/social/components/CommentMentionInput/MentionUser.module.css diff --git a/src/v4/social/components/PostCommentMentionInput/PostMentionUser.tsx b/src/v4/social/components/CommentMentionInput/MentionUser.tsx similarity index 77% rename from src/v4/social/components/PostCommentMentionInput/PostMentionUser.tsx rename to src/v4/social/components/CommentMentionInput/MentionUser.tsx index 2925cf29c..2fb5d5b10 100644 --- a/src/v4/social/components/PostCommentMentionInput/PostMentionUser.tsx +++ b/src/v4/social/components/CommentMentionInput/MentionUser.tsx @@ -1,21 +1,16 @@ import React from 'react'; -import styles from './PostMentionUser.module.css'; +import styles from './MentionUser.module.css'; import { UserAvatar } from '~/v4/social/internal-components/UserAvatar/UserAvatar'; -import { MentionTypeaheadOption } from './PostCommentMentionInput'; +import { MentionTypeaheadOption } from './CommentMentionInput'; -interface PostMentionUserProps { +interface MentionUserProps { isSelected: boolean; onClick: () => void; onMouseEnter: () => void; option: MentionTypeaheadOption; } -export function PostMentionUser({ - isSelected, - onClick, - onMouseEnter, - option, -}: PostMentionUserProps) { +export function MentionUser({ isSelected, onClick, onMouseEnter, option }: MentionUserProps) { let className = 'item'; if (isSelected) { className += ' selected'; diff --git a/src/v4/social/components/CommentMentionInput/index.ts b/src/v4/social/components/CommentMentionInput/index.ts new file mode 100644 index 000000000..0079b7c76 --- /dev/null +++ b/src/v4/social/components/CommentMentionInput/index.ts @@ -0,0 +1 @@ +export { CommentMentionInput } from './CommentMentionInput'; diff --git a/src/v4/social/components/CommentTray/CommentTray.module.css b/src/v4/social/components/CommentTray/CommentTray.module.css index 407b12b69..b91770422 100644 --- a/src/v4/social/components/CommentTray/CommentTray.module.css +++ b/src/v4/social/components/CommentTray/CommentTray.module.css @@ -55,6 +55,7 @@ .commentListContainer { overflow-y: auto; flex-grow: 1; + padding: var(--asc-spacing-m1); } .mobileSheetComposeBarContainer { diff --git a/src/v4/social/components/CommentTray/CommentTray.tsx b/src/v4/social/components/CommentTray/CommentTray.tsx index e4d393220..bfde921ad 100644 --- a/src/v4/social/components/CommentTray/CommentTray.tsx +++ b/src/v4/social/components/CommentTray/CommentTray.tsx @@ -1,14 +1,13 @@ import React, { useState } from 'react'; import { useAmityComponent } from '~/v4/core/hooks/uikit'; -import { CommentList } from '~/v4/social/internal-components/CommentList'; -import { StoryCommentComposeBar } from '~/v4/social/internal-components/StoryCommentComposeBar'; -import styles from './CommentTray.module.css'; +import { CommentComposer } from '~/v4/social/components/CommentComposer'; +import { CommentList } from '~/v4/social/components/CommentList'; -const REPLIES_PER_PAGE = 5; +import styles from './CommentTray.module.css'; interface CommentTrayProps { referenceType: Amity.CommentReferenceType; - referenceId?: string; + referenceId: string; community: Amity.Community; shouldAllowInteraction: boolean; shouldAllowCreation?: boolean; @@ -29,16 +28,13 @@ export const CommentTray = ({ componentId, }); - const [isReplying, setIsReplying] = useState(false); const [replyTo, setReplyTo] = useState<Amity.Comment | null>(null); const onClickReply = (comment: Amity.Comment) => { - setIsReplying(true); setReplyTo(comment); }; const onCancelReply = () => { - setIsReplying(false); setReplyTo(null); }; @@ -50,27 +46,21 @@ export const CommentTray = ({ > <div className={styles.commentListContainer}> <CommentList - pageId={pageId} - componentId={componentId} referenceId={referenceId} referenceType={referenceType} + community={community} + includeDeleted onClickReply={onClickReply} shouldAllowInteraction={shouldAllowInteraction} - limit={REPLIES_PER_PAGE} - includeDeleted - /> - </div> - <div className={styles.mobileSheetComposeBarContainer}> - <StoryCommentComposeBar - communityId={community.communityId} - referenceId={referenceId} - isReplying={isReplying} - replyTo={replyTo} - isJoined={community.isJoined} - shouldAllowCreation={shouldAllowCreation} - onCancelReply={onCancelReply} /> </div> + <CommentComposer + referenceId={referenceId} + referenceType={referenceType} + onCancelReply={onCancelReply} + replyTo={replyTo as Amity.Comment} + shouldAllowCreation={shouldAllowCreation} + /> </div> ); }; diff --git a/src/v4/social/components/PostComment/index.tsx b/src/v4/social/components/PostComment/index.tsx deleted file mode 100644 index 55aa22524..000000000 --- a/src/v4/social/components/PostComment/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export { PostComment } from './PostComment'; -export { PostCommentSkeleton } from './PostCommentSkeleton'; diff --git a/src/v4/social/components/PostCommentComposer/index.tsx b/src/v4/social/components/PostCommentComposer/index.tsx deleted file mode 100644 index 9226b06ef..000000000 --- a/src/v4/social/components/PostCommentComposer/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { PostCommentComposer } from './PostCommentComposer'; diff --git a/src/v4/social/components/PostCommentList/PostCommentList.module.css b/src/v4/social/components/PostCommentList/PostCommentList.module.css deleted file mode 100644 index fa8ccecac..000000000 --- a/src/v4/social/components/PostCommentList/PostCommentList.module.css +++ /dev/null @@ -1,18 +0,0 @@ -.postCommentList__container { - display: flex; - flex-direction: column; - gap: 1rem; -} - -.postCommentList__viewAllComments__button { - width: 100%; - padding: var(--asc-spacing-s2) 0; - margin: auto; - border-top: 0.0625rem solid var(--asc-color-base-shade4); - cursor: pointer; - text-align: center; -} - -.postCommentList__container_intersection { - height: 1px; -} diff --git a/src/v4/social/components/PostCommentList/index.ts b/src/v4/social/components/PostCommentList/index.ts deleted file mode 100644 index 646fb933f..000000000 --- a/src/v4/social/components/PostCommentList/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { PostCommentList } from './PostCommentList'; diff --git a/src/v4/social/components/PostCommentMentionInput/index.ts b/src/v4/social/components/PostCommentMentionInput/index.ts deleted file mode 100644 index 485bb6726..000000000 --- a/src/v4/social/components/PostCommentMentionInput/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { PostCommentMentionInput } from './PostCommentMentionInput'; diff --git a/src/v4/social/components/PostReplyComment/PostReplyComment.module.css b/src/v4/social/components/ReplyComment/ReplyComment.module.css similarity index 100% rename from src/v4/social/components/PostReplyComment/PostReplyComment.module.css rename to src/v4/social/components/ReplyComment/ReplyComment.module.css diff --git a/src/v4/social/components/PostReplyComment/PostReplyComment.tsx b/src/v4/social/components/ReplyComment/ReplyComment.tsx similarity index 93% rename from src/v4/social/components/PostReplyComment/PostReplyComment.tsx rename to src/v4/social/components/ReplyComment/ReplyComment.tsx index 3f132b55f..2a1fcc812 100644 --- a/src/v4/social/components/PostReplyComment/PostReplyComment.tsx +++ b/src/v4/social/components/ReplyComment/ReplyComment.tsx @@ -18,23 +18,18 @@ import { MinusCircleIcon } from '~/v4/social/icons'; import { TextWithMention } from '~/v4/social/internal-components/TextWithMention/TextWithMention'; import { UserAvatar } from '~/v4/social/internal-components/UserAvatar/UserAvatar'; import { CommentOptions } from '~/v4/social/components/CommentOptions/CommentOptions'; -import { CreateCommentParams } from '~/v4/social/components/PostCommentComposer/PostCommentComposer'; -import { PostCommentInput } from '~/v4/social/components/PostCommentComposer/PostCommentInput'; -import styles from './PostReplyComment.module.css'; +import { CreateCommentParams } from '~/v4/social/components/CommentComposer/CommentComposer'; +import { CommentInput } from '~/v4/social/components/CommentComposer/CommentInput'; +import styles from './ReplyComment.module.css'; -type PostReplyCommentProps = { +type ReplyCommentProps = { pageId?: string; - postTargetId: string; - postTargetType: Amity.PostTargetType; + targetId: string; + targetType: Amity.PostTargetType; comment: Amity.Comment; }; -const PostReplyComment = ({ - pageId = '*', - postTargetId, - postTargetType, - comment, -}: PostReplyCommentProps) => { +const PostReplyComment = ({ pageId = '*', targetId, targetType, comment }: ReplyCommentProps) => { const componentId = 'post_comment'; const { confirm } = useConfirmContext(); const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = @@ -112,9 +107,9 @@ const PostReplyComment = ({ <UserAvatar userId={comment.userId} /> <div className={styles.postReplyComment__edit__inputWrap}> <div className={styles.postReplyComment__edit__input}> - <PostCommentInput - postTargetType={postTargetType} - postTargetId={postTargetId} + <CommentInput + targetType={targetType} + targetId={targetId} value={{ data: { text: (comment.data as Amity.ContentDataText).text, diff --git a/src/v4/social/components/PostReplyCommentList/PostReplyCommentList.module.css b/src/v4/social/components/ReplyCommentList/ReplyCommentList.module.css similarity index 100% rename from src/v4/social/components/PostReplyCommentList/PostReplyCommentList.module.css rename to src/v4/social/components/ReplyCommentList/ReplyCommentList.module.css diff --git a/src/v4/social/components/PostReplyCommentList/PostReplyCommentList.tsx b/src/v4/social/components/ReplyCommentList/ReplyCommentList.tsx similarity index 61% rename from src/v4/social/components/PostReplyCommentList/PostReplyCommentList.tsx rename to src/v4/social/components/ReplyCommentList/ReplyCommentList.tsx index 75d9f8e0c..09e42f4ac 100644 --- a/src/v4/social/components/PostReplyCommentList/PostReplyCommentList.tsx +++ b/src/v4/social/components/ReplyCommentList/ReplyCommentList.tsx @@ -1,26 +1,26 @@ import React from 'react'; import { Typography } from '~/v4/core/components'; -import ReplyComment from '~/v4/icons/ReplyComment'; +import ReplyCommentIcon from '~/v4/icons/ReplyComment'; import useCommentsCollection from '~/v4/social/hooks/collections/useCommentsCollection'; -import PostReplyComment from '~/v4/social/components/PostReplyComment/PostReplyComment'; -import styles from './PostReplyCommentList.module.css'; +import ReplyComment from '~/v4/social/components/ReplyComment/ReplyComment'; +import styles from './ReplyCommentList.module.css'; -interface PostReplyCommentProps { - postTargetType: Amity.PostTargetType; - postTargetId: string; +interface ReplyCommentProps { + targetType: Amity.PostTargetType; + targetId: string; referenceId: string; parentId: string; } -export const PostReplyCommentList = ({ +export const ReplyCommentList = ({ referenceId, - postTargetId, - postTargetType, + targetId, + targetType, parentId, -}: PostReplyCommentProps) => { +}: ReplyCommentProps) => { const { comments, hasMore, loadMore } = useCommentsCollection({ referenceId: referenceId, - referenceType: 'post', + referenceType: targetType, parentId: parentId, limit: 5, shouldCall: true, @@ -35,9 +35,9 @@ export const PostReplyCommentList = ({ <div> {comments.map((comment) => { return ( - <PostReplyComment - postTargetId={postTargetId} - postTargetType={postTargetType} + <ReplyComment + targetId={targetId} + targetType={targetType} comment={comment as Amity.Comment} /> ); @@ -47,7 +47,7 @@ export const PostReplyCommentList = ({ className={styles.postReplyCommentList__viewReply_button} onClick={handleClickLoadMore} > - <ReplyComment className={styles.postReplyCommentList__viewReply_icon} /> + <ReplyCommentIcon className={styles.postReplyCommentList__viewReply_icon} /> <Typography.CaptionBold className={styles.postReplyCommentList__viewReply_text}> View more replies </Typography.CaptionBold> diff --git a/src/v4/social/hooks/useMentionUser.ts b/src/v4/social/hooks/useMentionUser.ts index 7ea33569c..9f400f153 100644 --- a/src/v4/social/hooks/useMentionUser.ts +++ b/src/v4/social/hooks/useMentionUser.ts @@ -7,25 +7,25 @@ export type MentionUser = Amity.Membership<'community'> | Amity.User; export const useMentionUsers = ({ displayName, limit = 10, - postTargetType, - postTargetId, + targetType, + targetId, }: { displayName: string; limit?: number; - postTargetType: Amity.PostTargetType; - postTargetId: string; + targetType: Amity.PostTargetType; + targetId: string; }) => { - const community = useCommunity(postTargetType === 'community' ? postTargetId : undefined); + const community = useCommunity(targetType === 'community' ? targetId : undefined); const fetcher = - postTargetType === 'community' && community && !community.isPublic + targetType === 'community' && community && !community.isPublic ? CommunityRepository.Membership.getMembers : UserRepository.getUsers; const params = - postTargetType === 'community' && community && !community.isPublic + targetType === 'community' && community && !community.isPublic ? ({ - communityId: postTargetId, + communityId: targetId, displayName, limit, } as Amity.SearchCommunityMemberLiveCollection) @@ -37,7 +37,10 @@ export const useMentionUsers = ({ >({ fetcher: fetcher as any, params, - shouldCall: (postTargetType === 'community' && !!community) || postTargetType === 'user', + shouldCall: + (targetType === 'community' && !!community) || + targetType === 'user' || + targetType === 'story', }); return { diff --git a/src/v4/social/internal-components/StoryPreview/StoryPreviewThumbnail/StoryPreviewThumbnail.tsx b/src/v4/social/internal-components/StoryPreview/StoryPreviewThumbnail/StoryPreviewThumbnail.tsx index de92ce30d..fc4a3b3b0 100644 --- a/src/v4/social/internal-components/StoryPreview/StoryPreviewThumbnail/StoryPreviewThumbnail.tsx +++ b/src/v4/social/internal-components/StoryPreview/StoryPreviewThumbnail/StoryPreviewThumbnail.tsx @@ -36,7 +36,6 @@ export const StoryPreviewThumbnail: React.FC<StoryPreviewThumbnailProps> = ({ const imageRef = useRef<HTMLImageElement>(null); useEffect(() => { - console.log('Effect start - thumbnailUrl:', thumbnailUrl); setIsLoading(true); setImageError(false); diff --git a/src/v4/social/internal-components/TextWithMention/TextWithMention.tsx b/src/v4/social/internal-components/TextWithMention/TextWithMention.tsx index 372928f88..f51070b7e 100644 --- a/src/v4/social/internal-components/TextWithMention/TextWithMention.tsx +++ b/src/v4/social/internal-components/TextWithMention/TextWithMention.tsx @@ -2,7 +2,7 @@ import { SerializedTextNode } from 'lexical'; import React, { useMemo } from 'react'; import { Typography } from '~/v4/core/components'; import { Mentioned } from '~/v4/helpers/utils'; -import { TextToEditorState } from '~/v4/social/components/PostCommentComposer/PostCommentInput'; +import { TextToEditorState } from '~/v4/social/components/CommentComposer/CommentInput'; import styles from './TextWithMention.module.css'; interface TextWithMentionProps { diff --git a/src/v4/social/pages/PostDetailPage/PostDetailPage.module.css b/src/v4/social/pages/PostDetailPage/PostDetailPage.module.css index 881e97d33..15a271496 100644 --- a/src/v4/social/pages/PostDetailPage/PostDetailPage.module.css +++ b/src/v4/social/pages/PostDetailPage/PostDetailPage.module.css @@ -84,6 +84,7 @@ flex-direction: column; gap: 1rem; padding: 0.75rem 1rem; + height: 100%; } @keyframes skeleton-pulse { diff --git a/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx b/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx index a06fd3cc9..cb2ed9a2b 100644 --- a/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx +++ b/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx @@ -11,8 +11,8 @@ import { BackButton } from '~/v4/social/elements/BackButton'; import { useAmityPage } from '~/v4/core/hooks/uikit'; import styles from './PostDetailPage.module.css'; import { useDrawer } from '~/v4/core/providers/DrawerProvider'; -import { PostCommentComposer } from '~/v4/social/components/PostCommentComposer/PostCommentComposer'; -import { PostCommentList } from '~/v4/social/components/PostCommentList/PostCommentList'; +import { CommentComposer } from '~/v4/social/components/CommentComposer/CommentComposer'; +import { CommentList } from '~/v4/social/components/CommentList/CommentList'; interface PostDetailPageProps { id: string; @@ -20,7 +20,7 @@ interface PostDetailPageProps { export function PostDetailPage({ id }: PostDetailPageProps) { const pageId = 'post_detail_page'; - const { post, isLoading: isPostLoading, error } = usePost(id); + const { post, isLoading: isPostLoading } = usePost(id); const { themeStyles } = useAmityPage({ pageId, }); @@ -42,7 +42,9 @@ export function PostDetailPage({ id }: PostDetailPageProps) { <div className={styles.postDetailPage__comments__divider} data-is-loading={isPostLoading} /> <div className={styles.postDetailPage__comments}> {post && ( - <PostCommentList + <CommentList + referenceId={post.postId} + referenceType="post" post={post} onClickReply={(comment: Amity.Comment) => setReplyComment(comment)} /> @@ -70,8 +72,9 @@ export function PostDetailPage({ id }: PostDetailPageProps) { </div> </div> {post && ( - <PostCommentComposer - post={post} + <CommentComposer + referenceId={post.targetId} + referenceType={post.targetType} replyTo={replyComment} onCancelReply={() => setReplyComment(undefined)} /> From df871034c3e0c3b860975280cd27246bfd71ea0d Mon Sep 17 00:00:00 2001 From: "Pitchaya T." <33589608+ptchayap@users.noreply.github.com> Date: Tue, 16 Jul 2024 18:13:27 +0700 Subject: [PATCH 258/300] feat: ASC-00000 - comment skeleton loading and see more (#546) * feat: add skeletons on comment list * feat: change limit * feat: add see more button * feat: use line-clamp * fix: use max height instead * fix: mention panel position * fix: flex item gap on mention user * fix: remove unused * fix: reply offset bottom * fix: use variable * fix: react error * fix: use variable instead of inline style * fix: remove inline style * fix: use uuid to be key --- .../components/Comment/Comment.module.css | 8 +- src/v4/social/components/Comment/Comment.tsx | 8 +- .../Comment/CommentSkeleton.module.css | 6 ++ .../components/Comment/CommentSkeleton.tsx | 16 ++- .../CommentComposer.module.css | 2 + .../CommentComposer/CommentComposer.tsx | 24 ++++- .../components/CommentList/CommentList.tsx | 5 +- .../CommentMentionInput.module.css | 1 + .../CommentMentionInput.tsx | 8 +- .../ReplyCommentList/ReplyCommentList.tsx | 6 +- .../CommunityMember.module.css | 2 + .../TextWithMention.module.css | 47 ++++++++ .../TextWithMention/TextWithMention.tsx | 100 +++++++++++++----- .../pages/PostDetailPage/PostDetailPage.tsx | 4 +- 14 files changed, 194 insertions(+), 43 deletions(-) diff --git a/src/v4/social/components/Comment/Comment.module.css b/src/v4/social/components/Comment/Comment.module.css index 17d6b6287..ab93aa4e2 100644 --- a/src/v4/social/components/Comment/Comment.module.css +++ b/src/v4/social/components/Comment/Comment.module.css @@ -44,7 +44,8 @@ background-color: var(--asc-color-base-shade4); border-radius: 0 0.75rem 0.75rem; padding: 0.75rem; - max-width: max-content; + width: max-content; + max-width: 100%; } .postComment__content__username { @@ -173,3 +174,8 @@ border-radius: var(--asc-border-radius-sm); background: var(--asc-color-primary-default); } + +.postComment__seeMore { + cursor: pointer; + color: var(--asc-color-primary-default); +} diff --git a/src/v4/social/components/Comment/Comment.tsx b/src/v4/social/components/Comment/Comment.tsx index 19f9268d9..5884ec20a 100644 --- a/src/v4/social/components/Comment/Comment.tsx +++ b/src/v4/social/components/Comment/Comment.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Typography, BottomSheet } from '~/v4/core/components'; import { ModeratorBadge } from '~/v4/social/elements/ModeratorBadge'; import { Timestamp } from '~/v4/social/elements/Timestamp'; @@ -58,8 +58,8 @@ const Like = ({ ...props }: React.SVGProps<SVGSVGElement>) => ( y2="39.2" gradientUnits="userSpaceOnUse" > - <stop stop-color="#63A1FF" /> - <stop offset="1" stop-color="#0041BE" /> + <stop stopColor="#63A1FF" /> + <stop offset="1" stopColor="#0041BE" /> </linearGradient> </defs> </svg> @@ -97,6 +97,8 @@ export const Comment = ({ const [isEditing, setIsEditing] = useState(false); const [commentData, setCommentData] = useState<CreateCommentParams>(); + const [isShowMore, setIsShowMore] = useState(false); + const toggleBottomSheet = () => setBottomSheetOpen((prev) => !prev); const isLiked = (comment.myReactions || []).some((reaction) => reaction === 'like'); diff --git a/src/v4/social/components/Comment/CommentSkeleton.module.css b/src/v4/social/components/Comment/CommentSkeleton.module.css index 9539c2e3e..4a4ee1095 100644 --- a/src/v4/social/components/Comment/CommentSkeleton.module.css +++ b/src/v4/social/components/Comment/CommentSkeleton.module.css @@ -1,3 +1,9 @@ +.postCommentSkeleton__container { + display: flex; + flex-direction: column; + gap: 1rem; +} + .postCommentSkeleton { display: flex; gap: 0.5rem; diff --git a/src/v4/social/components/Comment/CommentSkeleton.tsx b/src/v4/social/components/Comment/CommentSkeleton.tsx index 2c1de67f1..da5178f2d 100644 --- a/src/v4/social/components/Comment/CommentSkeleton.tsx +++ b/src/v4/social/components/Comment/CommentSkeleton.tsx @@ -3,12 +3,17 @@ import clsx from 'clsx'; import styles from './CommentSkeleton.module.css'; import { useAmityComponent } from '~/v4/core/hooks/uikit'; -interface PostCommentSkeletonProps { +interface CommentSkeletonProps { pageId?: string; componentId?: string; + numberOfSkeletons?: number; } -export const CommentSkeleton = ({ pageId = '*', componentId = '*' }: PostCommentSkeletonProps) => { +export const CommentSkeleton = ({ + pageId = '*', + componentId = '*', + numberOfSkeletons = 1, +}: CommentSkeletonProps) => { const { accessibilityId, isExcluded, themeStyles } = useAmityComponent({ pageId, componentId, @@ -16,8 +21,9 @@ export const CommentSkeleton = ({ pageId = '*', componentId = '*' }: PostComment if (isExcluded) return null; - return ( + const skeletons = Array.from({ length: numberOfSkeletons }, (_, index) => ( <div + key={index} // Use index as key for each skeleton data-qa-anchor={accessibilityId} className={clsx(styles.postCommentSkeleton, styles.postCommentSkeleton__animation)} style={themeStyles} @@ -37,5 +43,7 @@ export const CommentSkeleton = ({ pageId = '*', componentId = '*' }: PostComment <div className={styles.postCommentSkeleton__content__bar} /> </div> </div> - ); + )); + + return <div className={styles.postCommentSkeleton__container}>{skeletons}</div>; }; diff --git a/src/v4/social/components/CommentComposer/CommentComposer.module.css b/src/v4/social/components/CommentComposer/CommentComposer.module.css index dd5872c63..b2f5bcc87 100644 --- a/src/v4/social/components/CommentComposer/CommentComposer.module.css +++ b/src/v4/social/components/CommentComposer/CommentComposer.module.css @@ -28,6 +28,7 @@ } .commentComposer__button { + color: var(--asc-color-primary-default); cursor: pointer; margin-bottom: 0.5rem; } @@ -56,6 +57,7 @@ justify-content: space-between; padding: 0.625rem 1rem; background-color: var(--asc-color-base-shade4); + bottom: var(--asc-reply-container-offset-bottom); } .commentComposer__replyContainer__text { diff --git a/src/v4/social/components/CommentComposer/CommentComposer.tsx b/src/v4/social/components/CommentComposer/CommentComposer.tsx index 35eb68cbd..2a64a2990 100644 --- a/src/v4/social/components/CommentComposer/CommentComposer.tsx +++ b/src/v4/social/components/CommentComposer/CommentComposer.tsx @@ -52,8 +52,10 @@ export const CommentComposer = ({ const avatarUrl = useImage({ fileId: user?.avatar?.fileId, imageSize: 'small' }); const editorRef = useRef<CommentInputRef | null>(null); const composerRef = useRef<HTMLDivElement | null>(null); + const composerInputRef = useRef<HTMLDivElement | null>(null); const [composerHeight, setComposerHeight] = useState(0); + const [mentionOffsetBottom, setMentionOffsetBottom] = useState(0); const [textValue, setTextValue] = useState<CreateCommentParams>({ data: { @@ -97,6 +99,15 @@ export const CommentComposer = ({ }); useEffect(() => { + if (composerInputRef.current) { + // NOTE: Cannot use ref to get padding of the container and inside input + const containerPaddingBottom = 8; + const inputPaddingBottom = 10; + setMentionOffsetBottom( + composerInputRef.current.offsetHeight - inputPaddingBottom + containerPaddingBottom, + ); + } + if (composerRef.current) { setComposerHeight(composerRef.current.offsetHeight); } @@ -116,13 +127,13 @@ export const CommentComposer = ({ <div className={styles.commentComposer__avatar}> <Avatar avatarUrl={avatarUrl} defaultImage={<User />} /> </div> - <div className={styles.commentComposer__input}> + <div className={styles.commentComposer__input} ref={composerInputRef}> <CommentInput ref={editorRef} onChange={onChange} targetType={referenceType} targetId={referenceId} - mentionOffsetBottom={-composerHeight} + mentionOffsetBottom={-mentionOffsetBottom} value={textValue} placehoder="Say something nice..." /> @@ -135,7 +146,14 @@ export const CommentComposer = ({ <Typography.Body>Post</Typography.Body> </Button> {replyTo && ( - <div className={styles.commentComposer__replyContainer} style={{ bottom: composerHeight }}> + <div + className={styles.commentComposer__replyContainer} + style={ + { + '--asc-reply-container-offset-bottom': `${composerHeight}px`, + } as React.CSSProperties + } + > <div className={styles.commentComposer__replyContainer__text}> <span>Replying to </span> <span className={styles.commentComposer__replyContainer__username}> diff --git a/src/v4/social/components/CommentList/CommentList.tsx b/src/v4/social/components/CommentList/CommentList.tsx index b2acf26da..fe6cae33d 100644 --- a/src/v4/social/components/CommentList/CommentList.tsx +++ b/src/v4/social/components/CommentList/CommentList.tsx @@ -2,13 +2,13 @@ import React, { useRef } from 'react'; import { Comment } from '~/v4/social/components/Comment/Comment'; import useIntersectionObserver from '~/v4/core/hooks/useIntersectionObserver'; -import useCommentsCollection from '~/v4/social/hooks/collections/useCommentsCollection'; import { useAmityComponent } from '~/v4/core/hooks/uikit'; import useUserSubscription from '~/v4/core/hooks/subscriptions/useUserSubscription'; import { CommentRepository, SubscriptionLevels } from '@amityco/ts-sdk'; import useCommunitySubscription from '~/v4/core/hooks/subscriptions/useCommunitySubscription'; import { usePaginator } from '~/v4/core/hooks/usePaginator'; import { CommentAd } from '~/v4/social/internal-components/CommentAd/CommentAd'; +import { CommentSkeleton } from '~/v4/social/components/Comment/CommentSkeleton'; import styles from './CommentList.module.css'; import useCommunityStoriesSubscription from '~/v4/social/hooks/useCommunityStoriesSubscription'; import { Typography } from '~/v4/core/components'; @@ -126,6 +126,9 @@ export const CommentList = ({ {/* <div className={styles.postCommentList__viewAllComments__button}> <Typography.BodyBold>View all comments...</Typography.BodyBold> </div> */} + {isLoading && ( + <CommentSkeleton pageId={pageId} componentId={componentId} numberOfSkeletons={3} /> + )} {!isLoading && ( <div ref={intersectionRef} className={styles.commentList__container_intersection} /> )} diff --git a/src/v4/social/components/CommentMentionInput/CommentMentionInput.module.css b/src/v4/social/components/CommentMentionInput/CommentMentionInput.module.css index 2b4a017b7..e78db3267 100644 --- a/src/v4/social/components/CommentMentionInput/CommentMentionInput.module.css +++ b/src/v4/social/components/CommentMentionInput/CommentMentionInput.module.css @@ -5,6 +5,7 @@ width: 100%; height: 12.5rem; overflow-y: scroll; + transform: translateY(var(--asc-mention-offset-bottom)); } .mentionTextInput_item::-webkit-scrollbar { diff --git a/src/v4/social/components/CommentMentionInput/CommentMentionInput.tsx b/src/v4/social/components/CommentMentionInput/CommentMentionInput.tsx index 37ff30e4a..f16e7f08e 100644 --- a/src/v4/social/components/CommentMentionInput/CommentMentionInput.tsx +++ b/src/v4/social/components/CommentMentionInput/CommentMentionInput.tsx @@ -195,9 +195,11 @@ function Mention({ anchorRef, mentionUsers, offsetBottom = 0, onQueryChange }: M ? ReactDOM.createPortal( <div className={styles.mentionTextInput_item} - style={{ - transform: `translateY(${offsetBottom}px)`, - }} + style={ + { + '--asc-mention-offset-bottom': `${offsetBottom}px`, + } as React.CSSProperties + } > {options.map((option, i: number) => ( <CommunityMember diff --git a/src/v4/social/components/ReplyCommentList/ReplyCommentList.tsx b/src/v4/social/components/ReplyCommentList/ReplyCommentList.tsx index 09e42f4ac..787343660 100644 --- a/src/v4/social/components/ReplyCommentList/ReplyCommentList.tsx +++ b/src/v4/social/components/ReplyCommentList/ReplyCommentList.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { Typography } from '~/v4/core/components'; +import { CommentSkeleton } from '~/v4/social/components/Comment/CommentSkeleton'; import ReplyCommentIcon from '~/v4/icons/ReplyComment'; import useCommentsCollection from '~/v4/social/hooks/collections/useCommentsCollection'; import ReplyComment from '~/v4/social/components/ReplyComment/ReplyComment'; @@ -18,11 +19,11 @@ export const ReplyCommentList = ({ targetType, parentId, }: ReplyCommentProps) => { - const { comments, hasMore, loadMore } = useCommentsCollection({ + const { comments, hasMore, isLoading, loadMore } = useCommentsCollection({ referenceId: referenceId, referenceType: targetType, parentId: parentId, - limit: 5, + limit: 10, shouldCall: true, includeDeleted: true, }); @@ -33,6 +34,7 @@ export const ReplyCommentList = ({ return ( <div> + {isLoading && <CommentSkeleton numberOfSkeletons={3} />} {comments.map((comment) => { return ( <ReplyComment diff --git a/src/v4/social/internal-components/CommunityMember/CommunityMember.module.css b/src/v4/social/internal-components/CommunityMember/CommunityMember.module.css index 2167f73ff..9cfc4a71a 100644 --- a/src/v4/social/internal-components/CommunityMember/CommunityMember.module.css +++ b/src/v4/social/internal-components/CommunityMember/CommunityMember.module.css @@ -5,6 +5,8 @@ padding: 0.5rem 1rem; background-color: var(--asc-color-background-default); color: var(--asc-color-base-default); + gap: 0.75rem; + cursor: pointer; } .communityMember__item:focus { diff --git a/src/v4/social/internal-components/TextWithMention/TextWithMention.module.css b/src/v4/social/internal-components/TextWithMention/TextWithMention.module.css index 05cbd073d..7d43d48c7 100644 --- a/src/v4/social/internal-components/TextWithMention/TextWithMention.module.css +++ b/src/v4/social/internal-components/TextWithMention/TextWithMention.module.css @@ -1,3 +1,38 @@ +.textWithMention__container { + background-color: inherit; + overflow-wrap: break-word; + width: 100%; +} + +.textWithMention__clamp { + position: relative; + background-color: inherit; + overflow: hidden; + max-height: calc(var(--asc-text-with-mention-max-lines) * var(--asc-line-height-md)); +} + +.textWithMention__clamp[data-expanded='true'] { + position: relative; + background-color: inherit; + overflow: hidden; + max-height: max-content; +} + +.textWithMention__fullContent { + visibility: hidden; + position: relative; + top: 0; + left: 0; + height: auto; + overflow: visible; + overflow-wrap: break-word; + width: 100%; +} + +.textWithMention__fullContent[data-hidden='true'] { + display: none; +} + .textWithMention__mention { color: var(--asc-color-primary-default); display: inline; @@ -7,3 +42,15 @@ color: var(--asc-color-base-default); display: inline; } + +.textWithMention__showMoreLess { + padding-left: 0.2rem; + color: var(--asc-color-primary-default); + cursor: pointer; + text-decoration: none; + width: max-content; + position: absolute; + bottom: 0; + right: 0; + background-color: inherit; +} diff --git a/src/v4/social/internal-components/TextWithMention/TextWithMention.tsx b/src/v4/social/internal-components/TextWithMention/TextWithMention.tsx index f51070b7e..2c6667fdf 100644 --- a/src/v4/social/internal-components/TextWithMention/TextWithMention.tsx +++ b/src/v4/social/internal-components/TextWithMention/TextWithMention.tsx @@ -1,11 +1,13 @@ -import { SerializedTextNode } from 'lexical'; -import React, { useMemo } from 'react'; +import { SerializedParagraphNode, SerializedTextNode } from 'lexical'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; import { Typography } from '~/v4/core/components'; import { Mentioned } from '~/v4/helpers/utils'; import { TextToEditorState } from '~/v4/social/components/CommentComposer/CommentInput'; import styles from './TextWithMention.module.css'; +import { v4 } from 'uuid'; interface TextWithMentionProps { + maxLines?: number; data: { text: string; }; @@ -15,33 +17,83 @@ interface TextWithMentionProps { }; } -export const TextWithMention = (props: TextWithMentionProps) => { +export const TextWithMention = ({ maxLines = 8, ...props }: TextWithMentionProps) => { + const [isExpanded, setIsExpanded] = useState(false); + const [isClamped, setIsClamped] = useState(false); + const [isHidden, setIsHidden] = useState(false); + const contentRef = useRef<HTMLDivElement>(null); + const fullContentRef = useRef<HTMLDivElement>(null); + const editorState = useMemo(() => { return TextToEditorState(props); }, [props]); - return ( - <> - {editorState.root.children.map((child, index) => { + useEffect(() => { + // check if should be clamped or not, then hide the full content + const fullContentHeight = fullContentRef.current?.clientHeight || 0; + + const clampHeight = + parseFloat( + getComputedStyle(document.documentElement).getPropertyValue('--asc-line-height-md'), + ) * 16; + + if (fullContentHeight > clampHeight * maxLines) { + setIsClamped(true); + } + + setIsHidden(true); + }, [props.data.text]); + + const renderText = (paragraph: SerializedParagraphNode) => { + return paragraph.children.map((text) => { + const uid = v4(); + if ((text as SerializedTextNode).type === 'mention') { return ( - <Typography.Body key={index}> - {child.children.map((text, index) => { - if ((text as SerializedTextNode).type === 'mention') { - return ( - <span key={index} className={styles.textWithMention__mention}> - {(text as SerializedTextNode).text} - </span> - ); - } - return ( - <span key={index} className={styles.textWithMention__text}> - {(text as SerializedTextNode).text} - </span> - ); - })} - </Typography.Body> + <span key={uid} className={styles.textWithMention__mention}> + {(text as SerializedTextNode).text} + </span> ); - })} - </> + } + return ( + <span key={uid} className={styles.textWithMention__text}> + {(text as SerializedTextNode).text} + </span> + ); + }); + }; + + return ( + <div className={styles.textWithMention__container}> + <div + ref={fullContentRef} + className={styles.textWithMention__fullContent} + data-hidden={isHidden} + > + {editorState.root.children.map((child) => { + const uuid = v4(); + return <Typography.Body key={uuid}>{renderText(child)}</Typography.Body>; + })} + </div> + <div + key={isExpanded ? 'expanded' : 'collapsed'} + data-expanded={isExpanded} + className={styles.textWithMention__clamp} + style={ + { + '--asc-text-with-mention-max-lines': maxLines, + } as React.CSSProperties + } + > + {isClamped && !isExpanded && ( + <div onClick={() => setIsExpanded(true)} className={styles.textWithMention__showMoreLess}> + <Typography.Body>...See more</Typography.Body> + </div> + )} + {editorState.root.children.map((child, index) => { + const uuid = v4(); + return <Typography.Body key={uuid}>{renderText(child)}</Typography.Body>; + })} + </div> + </div> ); }; diff --git a/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx b/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx index cb2ed9a2b..0f5087473 100644 --- a/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx +++ b/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx @@ -73,8 +73,8 @@ export function PostDetailPage({ id }: PostDetailPageProps) { </div> {post && ( <CommentComposer - referenceId={post.targetId} - referenceType={post.targetType} + referenceId={post.postId} + referenceType={'post'} replyTo={replyComment} onCancelReply={() => setReplyComment(undefined)} /> From e69b3bd60dd483e5c58ddaf8ed4b2e02df84d5ef Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Thu, 18 Jul 2024 17:41:12 +0700 Subject: [PATCH 259/300] fix: ASC-23324 - combine v3 and v4 (#548) * fix: add button for user * refactor: condition Co-authored-by: Bonn <pittawat@amity.co> * fix: build fail * fix: handle navigation cross version * fix: remove console * fix: combine story --------- Co-authored-by: Bonn <pittawat@amity.co> --- src/v4/core/providers/NavigationProvider.tsx | 119 +++++++++++++----- .../components/PostContent/PostContent.tsx | 12 +- .../social/components/StoryTab/StoryTab.tsx | 12 +- src/v4/social/pages/Application/index.tsx | 10 +- .../pages/StoryPage/CommunityFeedStory.tsx | 29 ++--- .../pages/StoryPage/GlobalFeedStory.tsx | 22 ++-- .../pages/StoryPage/ViewGlobalFeedStory.tsx | 17 +-- .../social/pages/StoryPage/ViewStoryPage.tsx | 8 +- 8 files changed, 145 insertions(+), 84 deletions(-) diff --git a/src/v4/core/providers/NavigationProvider.tsx b/src/v4/core/providers/NavigationProvider.tsx index 95730fe3e..c15aaa0f0 100644 --- a/src/v4/core/providers/NavigationProvider.tsx +++ b/src/v4/core/providers/NavigationProvider.tsx @@ -1,10 +1,11 @@ import React, { createContext, useCallback, useContext, useState, useMemo, ReactNode } from 'react'; import { AmityStoryMediaType } from '~/v4/social/pages/DraftsPage/DraftsPage'; import { Mode } from '~/v4/social/pages/PostComposerPage/PostComposerPage'; +import { NavigationContext as NavigationContextV3 } from '~/social/providers/NavigationProvider'; export enum PageTypes { Explore = 'explore', - NewsFeed = 'newsFeed', + NewsFeed = 'newsfeed', CommunityFeed = 'communityFeed', CommunityEdit = 'communityEdit', Category = 'category', @@ -119,12 +120,12 @@ type ContextValue = { goToMyCommunitiesSearchPage: () => void; goToSelectPostTargetPage: () => void; goToStoryTargetSelectionPage: () => void; - goToDraftStoryPage: (context: { - targetId: string; - targetType: string; - mediaType: AmityStoryMediaType; - storyType: 'communityFeed' | 'globalFeed'; - }) => void; + goToDraftStoryPage: ( + targetId: string, + targetType: string, + mediaType: AmityStoryMediaType, + storyType: 'communityFeed' | 'globalFeed', + ) => void; goToViewStoryPage: (context: { targetId: string; targetType: Amity.StoryTargetType; @@ -154,6 +155,12 @@ type ContextValue = { storyType: 'communityFeed' | 'globalFeed'; }) => void; goToSocialHomePage: () => void; + //V3 functions + onClickStory: ( + storyId: string, + storyType: 'communityFeed' | 'globalFeed', + targetId?: string[], + ) => void; }; let defaultValue: ContextValue = { @@ -173,12 +180,12 @@ let defaultValue: ContextValue = { targetType: Amity.StoryTargetType; storyType: 'communityFeed' | 'globalFeed'; }) => {}, - goToDraftStoryPage: (context: { - targetId: string; - targetType: string; - mediaType: AmityStoryMediaType; - storyType: 'communityFeed' | 'globalFeed'; - }) => {}, + goToDraftStoryPage: ( + targetId: string, + targetType: string, + mediaType: AmityStoryMediaType, + storyType: 'communityFeed' | 'globalFeed', + ) => {}, goToCommunityProfilePage: (communityId: string) => {}, goToSocialGlobalSearchPage: (tab?: string) => {}, goToSelectPostTargetPage: () => {}, @@ -195,6 +202,12 @@ let defaultValue: ContextValue = { goToMyCommunitiesSearchPage: () => {}, setNavigationBlocker: () => {}, onBack: () => {}, + //V3 functions + onClickStory: ( + storyId: string, + storyType: 'communityFeed' | 'globalFeed', + targetId?: string[], + ) => {}, }; if (process.env.NODE_ENV !== 'production') { @@ -225,10 +238,7 @@ if (process.env.NODE_ENV !== 'production') { goToSelectPostTargetPage: () => console.log('NavigationContext goToTargetPage()'), goToStoryTargetSelectionPage: () => console.log('NavigationContext goToStoryTargetSelectionPage()'), - goToDraftStoryPage: ({ targetId, targetType, mediaType, storyType }) => - console.log( - `NavigationContext goToDraftStoryPage(${targetId}, ${targetType}, ${mediaType}), ${storyType})`, - ), + goToDraftStoryPage: (data) => console.log(`NavigationContext goToDraftStoryPage()`), goToPostComposerPage: (mode, targetId, targetType, community, post) => console.log( `NavigationContext goToPostComposerPage(${mode} ${targetId}) ${targetType} ${community} ${post}`, @@ -237,6 +247,10 @@ if (process.env.NODE_ENV !== 'production') { goToSocialHomePage: () => console.log('NavigationContext goToSocialHomePage()'), goToMyCommunitiesSearchPage: () => console.log('NavigationContext goToMyCommunitiesSearchPage()'), + + //V3 functions + onClickStory: (storyId, storyType, targetIds) => + console.log(`NavigationContext onClickStory(${storyId}, ${storyType}, ${targetIds})`), }; } @@ -271,17 +285,23 @@ interface NavigationProviderProps { storyType: 'communityFeed' | 'globalFeed'; targetType: Amity.StoryTargetType; }) => void; - goToDraftStoryPage?: (context: { - targetId: string; - targetType: string; - mediaType: AmityStoryMediaType; - storyType: 'communityFeed' | 'globalFeed'; - }) => void; + goToDraftStoryPage?: ( + targetId: string, + targetType: string, + mediaType: AmityStoryMediaType, + storyType: 'communityFeed' | 'globalFeed', + ) => void; onCommunityCreated?: (communityId: string) => void; onEditCommunity?: (communityId: string, options?: { tab?: string }) => void; onEditUser?: (userId: string) => void; onMessageUser?: (userId: string) => void; onBack?: () => void; + //V3 functions + onClickStory?: ( + storyId: string, + storyType: 'communityFeed' | 'globalFeed', + targetId?: string[], + ) => void; } export default function NavigationProvider({ @@ -412,7 +432,9 @@ export default function NavigationProvider({ (userId) => { const next = { type: PageTypes.UserEdit, - userId, + context: { + userId, + }, }; if (onChangePage) return onChangePage(next); @@ -427,8 +449,10 @@ export default function NavigationProvider({ (communityId, tab) => { const next = { type: PageTypes.CommunityEdit, - communityId, - tab, + context: { + communityId, + tab, + }, }; if (onChangePage) return onChangePage(next); @@ -506,7 +530,7 @@ export default function NavigationProvider({ const goToCommunityProfilePage = useCallback( (communityId) => { const next = { - type: PageTypes.CommunityProfilePage, + type: PageTypes.CommunityFeed, context: { communityId, }, @@ -562,7 +586,7 @@ export default function NavigationProvider({ ); const goToDraftStoryPage = useCallback( - ({ targetId, targetType, mediaType, storyType }) => { + (targetId, targetType, mediaType, storyType) => { const next = { type: PageTypes.DraftPage, context: { @@ -613,6 +637,24 @@ export default function NavigationProvider({ pushPage(next); }, [onChangePage, pushPage]); + const handleClickStory = useCallback( + (targetId, storyType, targetIds) => { + const next = { + type: PageTypes.ViewStoryPage, + context: { + targetId, + storyType, + targetIds, + }, + }; + + if (onChangePage) return onChangePage(next); + + pushPage(next); + }, + [onChangePage, pushPage], + ); + return ( <NavigationContext.Provider value={{ @@ -639,9 +681,28 @@ export default function NavigationProvider({ goToSocialHomePage, goToMyCommunitiesSearchPage, setNavigationBlocker, + onClickStory: handleClickStory, }} > - {children} + <NavigationContextV3.Provider + value={{ + page: currentPage as any, //TODO : Fix any type + onChangePage: handleChangePage, + onClickCategory: handleClickCategory, + onClickCommunity: handleClickCommunity, + onClickUser: handleClickUser, + onCommunityCreated: handleCommunityCreated, + onEditCommunity: handleEditCommunity, + onEditUser: handleEditUser, + onMessageUser: handleMessageUser, + onBack: handleBack, + setNavigationBlocker, + goToDraftStoryPage, + onClickStory: handleClickStory, + }} + > + {children} + </NavigationContextV3.Provider> </NavigationContext.Provider> ); } diff --git a/src/v4/social/components/PostContent/PostContent.tsx b/src/v4/social/components/PostContent/PostContent.tsx index cf2ddaf69..3eb10490d 100644 --- a/src/v4/social/components/PostContent/PostContent.tsx +++ b/src/v4/social/components/PostContent/PostContent.tsx @@ -57,9 +57,9 @@ const PostTitle = ({ pageId, post }: PostTitleProps) => { return ( <div className={styles.postTitle}> {postedUser && ( - <Button onPress={() => onClickUser(postedUser?.userId)}> + <Button onPress={() => onClickUser(postedUser.userId)}> <Typography.BodyBold className={styles.postTitle__text}> - {postedUser?.displayName} + {postedUser.displayName} </Typography.BodyBold> </Button> )} @@ -82,9 +82,11 @@ const PostTitle = ({ pageId, post }: PostTitleProps) => { } return ( - <Typography.BodyBold className={styles.postTitle__text}> - {postedUser?.displayName} - </Typography.BodyBold> + <Button onPress={() => postedUser && onClickUser(postedUser.userId)}> + <Typography.BodyBold className={styles.postTitle__text}> + {postedUser?.displayName} + </Typography.BodyBold> + </Button> ); }; diff --git a/src/v4/social/components/StoryTab/StoryTab.tsx b/src/v4/social/components/StoryTab/StoryTab.tsx index a1d16a988..acd217ac8 100644 --- a/src/v4/social/components/StoryTab/StoryTab.tsx +++ b/src/v4/social/components/StoryTab/StoryTab.tsx @@ -27,14 +27,14 @@ export const StoryTab: React.FC<StoryTabProps> = ({ pageId = '*', ...props }) => onFileChange={(file) => { setFile(file); if (file) { - goToDraftStoryPage({ - targetId: props.communityId || '', - targetType: 'community', - mediaType: file.type.includes('image') + goToDraftStoryPage( + props.communityId || '', + 'community', + file.type.includes('image') ? { type: 'image', url: URL.createObjectURL(file) } : { type: 'video', url: URL.createObjectURL(file) }, - storyType: 'communityFeed', - }); + 'communityFeed', + ); } }} onStoryClick={() => diff --git a/src/v4/social/pages/Application/index.tsx b/src/v4/social/pages/Application/index.tsx index 50ccb6bee..140a6f1b0 100644 --- a/src/v4/social/pages/Application/index.tsx +++ b/src/v4/social/pages/Application/index.tsx @@ -15,6 +15,8 @@ import { AmityDraftStoryPage } from '..'; import { StoryTargetSelectionPage } from '~/v4/social/pages/StoryTargetSelectionPage'; import CommunityFeed from '~/social/pages/CommunityFeed'; import UserFeedPage from '~/social/pages/UserFeed'; +import CommunityEditPage from '~/social/pages/CommunityEdit'; +import ProfileSettings from '~/social/components/ProfileSettings'; const Application = () => { const { page } = useNavigation(); @@ -34,7 +36,7 @@ const Application = () => { {page.type === PageTypes.PostDetailPage && <PostDetailPage id={page.context?.postId} />} {page.type === PageTypes.StoryTargetSelectionPage && <StoryTargetSelectionPage />} {page.type === PageTypes.ViewStoryPage && ( - <ViewStoryPage type="globalFeed" targetId={page.context?.targetId} /> + <ViewStoryPage type={page.context.storyType} targetId={page.context?.targetId} /> )} {page.type === PageTypes.DraftPage && ( <AmityDraftStoryPage @@ -67,6 +69,12 @@ const Application = () => { {page.type === PageTypes.UserFeed && ( <UserFeedPage userId={page.context.userId} socialSettings={socialSettings} /> )} + {page.type === PageTypes.CommunityEdit && ( + <CommunityEditPage communityId={page.context.communityId} tab={page.context.tab} /> + )} + + {page.type === PageTypes.UserEdit && <ProfileSettings userId={page.context.userId} />} + {/*End of V3 */} </div> </StoryProvider> diff --git a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx index 86db2327b..1f8f28323 100644 --- a/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/CommunityFeedStory.tsx @@ -36,17 +36,12 @@ interface CommunityFeedStoryProps { onClose: (communityId: string) => void; onSwipeDown: (communityId: string) => void; onClickCommunity: (communityId: string) => void; - goToDraftStoryPage: ({ - targetId, - targetType, - mediaType, - storyType, - }: { - targetId: string; - targetType: string; - mediaType: any; - storyType: 'communityFeed' | 'globalFeed'; - }) => void; + goToDraftStoryPage: ( + targetId: string, + targetType: string, + mediaType: any, + storyType: 'communityFeed' | 'globalFeed', + ) => void; } const MIN_IMAGE_DURATION = 5000; // 5 seconds @@ -302,14 +297,14 @@ export const CommunityFeedStory = ({ }); if (file) { - goToDraftStoryPage({ - targetId: communityId, - targetType: 'community', - mediaType: file.type.includes('image') + goToDraftStoryPage( + communityId, + 'community', + file.type.includes('image') ? { type: 'image', url: URL.createObjectURL(file) } : { type: 'video', url: URL.createObjectURL(file) }, - storyType: 'communityFeed', - }); + 'communityFeed', + ); } if (!stories || stories.length === 0) return null; diff --git a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx index f66899083..e09ac9809 100644 --- a/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/GlobalFeedStory.tsx @@ -38,12 +38,12 @@ interface GlobalFeedStoryProps { targetIds: string[]; onChangePage?: () => void; onClickStory: (targetId: string) => void; - goToDraftStoryPage: (data: { - mediaType: { type: 'image' | 'video'; url: string }; - targetId: string; - targetType: string; - storyType: 'globalFeed'; - }) => void; + goToDraftStoryPage: ( + targetId: string, + targetType: string, + mediaType: { type: 'image' | 'video'; url: string }, + storyType: 'globalFeed', + ) => void; onClose: (targetId: string) => void; onSwipeDown: (targetId: string) => void; onClickCommunity: (targetId: string) => void; @@ -309,14 +309,14 @@ export const GlobalFeedStory: React.FC<GlobalFeedStoryProps> = ({ useEffect(() => { if (!file) return; - goToDraftStoryPage({ + goToDraftStoryPage( targetId, - targetType: 'community', - mediaType: file.type.includes('image') + 'community', + file.type.includes('image') ? { type: 'image', url: URL.createObjectURL(file) } : { type: 'video', url: URL.createObjectURL(file) }, - storyType: 'globalFeed', - }); + 'globalFeed', + ); }, [file, goToDraftStoryPage, targetId]); useCommunityStoriesSubscription({ diff --git a/src/v4/social/pages/StoryPage/ViewGlobalFeedStory.tsx b/src/v4/social/pages/StoryPage/ViewGlobalFeedStory.tsx index 989008154..76a916b9a 100644 --- a/src/v4/social/pages/StoryPage/ViewGlobalFeedStory.tsx +++ b/src/v4/social/pages/StoryPage/ViewGlobalFeedStory.tsx @@ -17,17 +17,12 @@ export const ViewGlobalFeedStoryPage = ({ targetId: string; onChangePage?: () => void; onClickStory: (targetId: string) => void; - goToDraftStoryPage: ({ - targetId, - targetType, - mediaType, - storyType, - }: { - targetId: string; - targetType: string; - mediaType: AmityStoryMediaType; - storyType: 'globalFeed'; - }) => void; + goToDraftStoryPage: ( + targetId: string, + targetType: string, + mediaType: AmityStoryMediaType, + storyType: 'globalFeed', + ) => void; onClose: (targetId: string) => void; onSwipeDown: (targetId: string) => void; onClickCommunity: (targetId: string) => void; diff --git a/src/v4/social/pages/StoryPage/ViewStoryPage.tsx b/src/v4/social/pages/StoryPage/ViewStoryPage.tsx index 0d7928b4e..4d1778707 100644 --- a/src/v4/social/pages/StoryPage/ViewStoryPage.tsx +++ b/src/v4/social/pages/StoryPage/ViewStoryPage.tsx @@ -25,8 +25,8 @@ const ViewStoryPage: React.FC<AmityViewStoryPageProps> = ({ type, targetId }) => onClose={(communityId) => onClickCommunity(communityId)} onSwipeDown={(communityId) => onClickCommunity(communityId)} onClickCommunity={(communityId) => onClickCommunity(communityId)} - goToDraftStoryPage={({ targetId, targetType, mediaType, storyType }) => - goToDraftStoryPage({ targetId, targetType, mediaType, storyType }) + goToDraftStoryPage={(targetId, targetType, mediaType, storyType) => + goToDraftStoryPage(targetId, targetType, mediaType, storyType) } /> ); @@ -45,8 +45,8 @@ const ViewStoryPage: React.FC<AmityViewStoryPageProps> = ({ type, targetId }) => targetType: 'community', }) } - goToDraftStoryPage={({ targetId, targetType, mediaType, storyType }) => - goToDraftStoryPage({ targetId, targetType, mediaType, storyType }) + goToDraftStoryPage={(targetId, targetType, mediaType, storyType) => + goToDraftStoryPage(targetId, targetType, mediaType, storyType) } onClickCommunity={(targetId) => onClickCommunity(targetId)} /> From 06c0e95dbf08ca4ed2f8672ab447ca34a5939f39 Mon Sep 17 00:00:00 2001 From: "Pitchaya T." <33589608+ptchayap@users.noreply.github.com> Date: Fri, 19 Jul 2024 15:27:54 +0700 Subject: [PATCH 260/300] fix: ASC-24390 - mention is not show in comment (#549) * fix: mention is not show * fix: remove post props --- src/v4/social/components/Comment/Comment.tsx | 13 ++++----- .../CommentComposer/CommentInput.tsx | 11 ++----- .../components/CommentList/CommentList.tsx | 29 ++++++++++--------- .../CommentOptions/CommentOptions.module.css | 2 +- .../components/ReplyComment/ReplyComment.tsx | 9 ++---- .../ReplyCommentList/ReplyCommentList.tsx | 22 +++++--------- src/v4/social/hooks/useMentionUser.ts | 19 ++++-------- src/v4/social/hooks/useStory.ts | 15 ++++++++++ .../TextWithMention/TextWithMention.tsx | 2 +- .../pages/PostDetailPage/PostDetailPage.tsx | 1 - 10 files changed, 57 insertions(+), 66 deletions(-) create mode 100644 src/v4/social/hooks/useStory.ts diff --git a/src/v4/social/components/Comment/Comment.tsx b/src/v4/social/components/Comment/Comment.tsx index 5884ec20a..9df6922f4 100644 --- a/src/v4/social/components/Comment/Comment.tsx +++ b/src/v4/social/components/Comment/Comment.tsx @@ -69,8 +69,7 @@ interface CommentProps { pageId?: string; componentId?: string; comment: Amity.Comment; - targetType: Amity.PostTargetType | Amity.StoryTargetType; - targetId: string; + community?: Amity.Community; onClickReply: (comment: Amity.Comment) => void; shoudAllowInteraction?: boolean; } @@ -79,8 +78,7 @@ export const Comment = ({ pageId = '*', componentId = 'comment_bubble', comment, - targetType, - targetId, + community, onClickReply, shoudAllowInteraction = true, }: CommentProps) => { @@ -167,8 +165,7 @@ export const Comment = ({ <div className={styles.postComment__edit__inputWrap}> <div className={styles.postComment__edit__input}> <CommentInput - targetType={targetType} - targetId={targetId} + community={community} value={{ data: { text: (comment.data as Amity.ContentDataText).text, @@ -276,9 +273,9 @@ export const Comment = ({ {hasClickLoadMore && ( <ReplyCommentList - targetType={targetType} - targetId={targetId} + community={community} referenceId={comment.referenceId} + referenceType={comment.referenceType} parentId={comment.commentId} /> )} diff --git a/src/v4/social/components/CommentComposer/CommentInput.tsx b/src/v4/social/components/CommentComposer/CommentInput.tsx index 531284d53..ac13d1abe 100644 --- a/src/v4/social/components/CommentComposer/CommentInput.tsx +++ b/src/v4/social/components/CommentComposer/CommentInput.tsx @@ -46,8 +46,7 @@ interface EditorStateJson extends SerializedLexicalNode { } interface CommentInputProps { - targetType: Amity.PostTargetType | Amity.StoryTargetType; - targetId: string; + community?: Amity.Community; value?: CreateCommentParams; mentionOffsetBottom?: number; maxLines?: number; @@ -224,17 +223,13 @@ export function TextToEditorState(value: { } export const CommentInput = forwardRef<CommentInputRef, CommentInputProps>( - ( - { targetType, targetId, mentionOffsetBottom = 0, value, onChange, maxLines = 10, placehoder }, - ref, - ) => { + ({ community, mentionOffsetBottom = 0, value, onChange, maxLines = 10, placehoder }, ref) => { const editorRef = React.useRef<LexicalEditor | null | undefined>(null); const [queryMentionUser, setQueryMentionUser] = React.useState<string | null>(null); const { mentionUsers } = useMentionUsers({ displayName: queryMentionUser || '', - targetType, - targetId, + community, }); const clearEditorState = () => { diff --git a/src/v4/social/components/CommentList/CommentList.tsx b/src/v4/social/components/CommentList/CommentList.tsx index fe6cae33d..84f1ed9e6 100644 --- a/src/v4/social/components/CommentList/CommentList.tsx +++ b/src/v4/social/components/CommentList/CommentList.tsx @@ -12,6 +12,9 @@ import { CommentSkeleton } from '~/v4/social/components/Comment/CommentSkeleton' import styles from './CommentList.module.css'; import useCommunityStoriesSubscription from '~/v4/social/hooks/useCommunityStoriesSubscription'; import { Typography } from '~/v4/core/components'; +import useStory from '~/v4/social/hooks/useStory'; +import usePost from '~/v4/core/hooks/objects/usePost'; +import usePostSubscription from '~/v4/core/hooks/subscriptions/usePostSubscription'; type CommentListProps = { referenceId: string; @@ -21,7 +24,6 @@ type CommentListProps = { limit?: number; includeDeleted?: boolean; community?: Amity.Community; - post?: Amity.Post; shouldAllowInteraction?: boolean; }; @@ -37,7 +39,6 @@ export const CommentList = ({ limit = 5, includeDeleted = false, community, - post, shouldAllowInteraction = true, }: CommentListProps) => { const componentId = 'comment_tray_component'; @@ -64,6 +65,13 @@ export const CommentList = ({ shouldCall: true, }); + const { story } = useStory({ + storyId: referenceId, + shouldCall: referenceType === 'story', + }); + + const { post } = usePost(referenceType === 'post' ? referenceId : undefined); + useIntersectionObserver({ ref: intersectionRef, onIntersect: () => { @@ -73,16 +81,10 @@ export const CommentList = ({ }, }); - useUserSubscription({ - userId: post?.targetId, - level: SubscriptionLevels.COMMENT, - shouldSubscribe: !!post?.targetId, - }); - - useCommunitySubscription({ - communityId: community?.communityId, + usePostSubscription({ + postId: referenceId, level: SubscriptionLevels.COMMENT, - shouldSubscribe: !!community?.communityId, + shouldSubscribe: referenceType === 'post', }); useCommunityStoriesSubscription({ @@ -116,8 +118,9 @@ export const CommentList = ({ comment={item as Amity.Comment} onClickReply={(comment) => onClickReply?.(comment)} componentId={componentId} - targetId={referenceId} - targetType={referenceType} + community={community} + // targetId={post?.targetId || story?.targetId} + // targetType={post?.targetType || story?.targetType} shoudAllowInteraction={shouldAllowInteraction} /> ); diff --git a/src/v4/social/components/CommentOptions/CommentOptions.module.css b/src/v4/social/components/CommentOptions/CommentOptions.module.css index 4f60dd750..c1f20185a 100644 --- a/src/v4/social/components/CommentOptions/CommentOptions.module.css +++ b/src/v4/social/components/CommentOptions/CommentOptions.module.css @@ -1,7 +1,7 @@ .commentOptions__actionButton { display: flex; gap: 0.75rem; - padding: 1rem 0; + padding: 1rem 1.25rem; align-items: center; cursor: pointer; } diff --git a/src/v4/social/components/ReplyComment/ReplyComment.tsx b/src/v4/social/components/ReplyComment/ReplyComment.tsx index 2a1fcc812..62aa5c0cd 100644 --- a/src/v4/social/components/ReplyComment/ReplyComment.tsx +++ b/src/v4/social/components/ReplyComment/ReplyComment.tsx @@ -24,12 +24,11 @@ import styles from './ReplyComment.module.css'; type ReplyCommentProps = { pageId?: string; - targetId: string; - targetType: Amity.PostTargetType; + community?: Amity.Community; comment: Amity.Comment; }; -const PostReplyComment = ({ pageId = '*', targetId, targetType, comment }: ReplyCommentProps) => { +const PostReplyComment = ({ pageId = '*', community, comment }: ReplyCommentProps) => { const componentId = 'post_comment'; const { confirm } = useConfirmContext(); const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = @@ -108,8 +107,7 @@ const PostReplyComment = ({ pageId = '*', targetId, targetType, comment }: Reply <div className={styles.postReplyComment__edit__inputWrap}> <div className={styles.postReplyComment__edit__input}> <CommentInput - targetType={targetType} - targetId={targetId} + community={community} value={{ data: { text: (comment.data as Amity.ContentDataText).text, @@ -158,7 +156,6 @@ const PostReplyComment = ({ pageId = '*', targetId, targetType, comment }: Reply </Typography.BodyBold> <ModeratorBadge pageId={pageId} componentId={componentId} /> - <TextWithMention data={{ text: (comment.data as Amity.ContentDataText).text }} mentionees={comment.mentionees as Amity.UserMention[]} diff --git a/src/v4/social/components/ReplyCommentList/ReplyCommentList.tsx b/src/v4/social/components/ReplyCommentList/ReplyCommentList.tsx index 787343660..1ec452b73 100644 --- a/src/v4/social/components/ReplyCommentList/ReplyCommentList.tsx +++ b/src/v4/social/components/ReplyCommentList/ReplyCommentList.tsx @@ -7,22 +7,22 @@ import ReplyComment from '~/v4/social/components/ReplyComment/ReplyComment'; import styles from './ReplyCommentList.module.css'; interface ReplyCommentProps { - targetType: Amity.PostTargetType; - targetId: string; + community?: Amity.Community; referenceId: string; + referenceType: string; parentId: string; } export const ReplyCommentList = ({ referenceId, - targetId, - targetType, + referenceType, + community, parentId, }: ReplyCommentProps) => { const { comments, hasMore, isLoading, loadMore } = useCommentsCollection({ - referenceId: referenceId, - referenceType: targetType, - parentId: parentId, + referenceId, + referenceType: referenceType as Amity.CommentReferenceType, + parentId, limit: 10, shouldCall: true, includeDeleted: true, @@ -36,13 +36,7 @@ export const ReplyCommentList = ({ <div> {isLoading && <CommentSkeleton numberOfSkeletons={3} />} {comments.map((comment) => { - return ( - <ReplyComment - targetId={targetId} - targetType={targetType} - comment={comment as Amity.Comment} - /> - ); + return <ReplyComment community={community} comment={comment as Amity.Comment} />; })} {hasMore && ( <div diff --git a/src/v4/social/hooks/useMentionUser.ts b/src/v4/social/hooks/useMentionUser.ts index 9f400f153..c6f00d6e7 100644 --- a/src/v4/social/hooks/useMentionUser.ts +++ b/src/v4/social/hooks/useMentionUser.ts @@ -1,31 +1,26 @@ import { CommunityRepository, UserRepository } from '@amityco/ts-sdk'; import useLiveCollection from '~/v4/core/hooks/useLiveCollection'; -import useCommunity from '~/social/hooks/useCommunity'; export type MentionUser = Amity.Membership<'community'> | Amity.User; export const useMentionUsers = ({ displayName, limit = 10, - targetType, - targetId, + community, }: { displayName: string; limit?: number; - targetType: Amity.PostTargetType; - targetId: string; + community?: Amity.Community; }) => { - const community = useCommunity(targetType === 'community' ? targetId : undefined); - const fetcher = - targetType === 'community' && community && !community.isPublic + community && !community.isPublic ? CommunityRepository.Membership.getMembers : UserRepository.getUsers; const params = - targetType === 'community' && community && !community.isPublic + community && !community.isPublic ? ({ - communityId: targetId, + communityId: community.communityId, displayName, limit, } as Amity.SearchCommunityMemberLiveCollection) @@ -37,10 +32,6 @@ export const useMentionUsers = ({ >({ fetcher: fetcher as any, params, - shouldCall: - (targetType === 'community' && !!community) || - targetType === 'user' || - targetType === 'story', }); return { diff --git a/src/v4/social/hooks/useStory.ts b/src/v4/social/hooks/useStory.ts new file mode 100644 index 000000000..386bf3421 --- /dev/null +++ b/src/v4/social/hooks/useStory.ts @@ -0,0 +1,15 @@ +import { StoryRepository } from '@amityco/ts-sdk'; + +import useLiveObject from '~/v4/core/hooks/useLiveObject'; + +const useStory = ({ storyId, shouldCall }: { storyId?: string; shouldCall?: boolean }) => { + const { item, ...rest } = useLiveObject({ + fetcher: StoryRepository.getStoryByStoryId, + params: storyId, + shouldCall: shouldCall || storyId != null, + }); + + return { story: item, ...rest }; +}; + +export default useStory; diff --git a/src/v4/social/internal-components/TextWithMention/TextWithMention.tsx b/src/v4/social/internal-components/TextWithMention/TextWithMention.tsx index 2c6667fdf..abb21b98d 100644 --- a/src/v4/social/internal-components/TextWithMention/TextWithMention.tsx +++ b/src/v4/social/internal-components/TextWithMention/TextWithMention.tsx @@ -42,7 +42,7 @@ export const TextWithMention = ({ maxLines = 8, ...props }: TextWithMentionProps } setIsHidden(true); - }, [props.data.text]); + }, []); const renderText = (paragraph: SerializedParagraphNode) => { return paragraph.children.map((text) => { diff --git a/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx b/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx index 0f5087473..c4cd20502 100644 --- a/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx +++ b/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx @@ -45,7 +45,6 @@ export function PostDetailPage({ id }: PostDetailPageProps) { <CommentList referenceId={post.postId} referenceType="post" - post={post} onClickReply={(comment: Amity.Comment) => setReplyComment(comment)} /> )} From dbd8162ea7bd19a0fff518741a2e8b7b23226403 Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Tue, 23 Jul 2024 14:20:55 +0700 Subject: [PATCH 261/300] fix: onPress button (#550) --- .../pages/SelectPostTargetPage/SelectPostTargetPage.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.tsx b/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.tsx index 49f478a4c..7019a8f36 100644 --- a/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.tsx +++ b/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.tsx @@ -16,6 +16,7 @@ import { useUser } from '~/v4/core/hooks/objects/useUser'; import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; import useSDK from '~/v4/core/hooks/useSDK'; import { Mode } from '~/v4/social/pages/PostComposerPage/'; +import { Button } from '~/v4/core/natives/Button'; export function SelectPostTargetPage() { const pageId = 'select_post_target_page'; @@ -76,8 +77,8 @@ export function SelectPostTargetPage() { <Title pageId={pageId} titleClassName={styles.selectPostTargetPage__title} /> <div /> </div> - <div - onClick={() => { + <Button + onPress={() => { AmityPostTargetSelectionPage?.goToPostComposerPage?.({ mode: Mode.CREATE, targetId: null, @@ -89,7 +90,7 @@ export function SelectPostTargetPage() { > <MyTimelineAvatar pageId={pageId} userId={user?.userId} /> <MyTimelineText pageId={pageId} /> - </div> + </Button> <div className={styles.selectPostTargetPage__line} /> <div className={styles.selectPostTargetPage__myCommunities}>My Communities</div> {renderCommunity} From 2104f504a3d00d3c7b7d797a8266be561e29366f Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Wed, 24 Jul 2024 13:56:33 +0700 Subject: [PATCH 262/300] fix: ASC-24396 - comment list pagination (#553) * fix: update threshold * fix: remove height 100% * chore: typo --- src/v4/social/components/Comment/Comment.tsx | 6 +++--- src/v4/social/components/CommentList/CommentList.tsx | 10 ++++++---- .../pages/PostDetailPage/PostDetailPage.module.css | 1 - 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/v4/social/components/Comment/Comment.tsx b/src/v4/social/components/Comment/Comment.tsx index 9df6922f4..deca3ba46 100644 --- a/src/v4/social/components/Comment/Comment.tsx +++ b/src/v4/social/components/Comment/Comment.tsx @@ -71,7 +71,7 @@ interface CommentProps { comment: Amity.Comment; community?: Amity.Community; onClickReply: (comment: Amity.Comment) => void; - shoudAllowInteraction?: boolean; + shouldAllowInteraction?: boolean; } export const Comment = ({ @@ -80,7 +80,7 @@ export const Comment = ({ comment, community, onClickReply, - shoudAllowInteraction = true, + shouldAllowInteraction = true, }: CommentProps) => { const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = useAmityComponent({ @@ -220,7 +220,7 @@ export const Comment = ({ /> </div> <div className={styles.postComment__secondRow}> - {shoudAllowInteraction && ( + {shouldAllowInteraction && ( <div className={styles.postComment__secondRow__leftPane}> <Typography.Caption className={styles.postComment__secondRow__timestamp}> <Timestamp diff --git a/src/v4/social/components/CommentList/CommentList.tsx b/src/v4/social/components/CommentList/CommentList.tsx index 84f1ed9e6..1452ef087 100644 --- a/src/v4/social/components/CommentList/CommentList.tsx +++ b/src/v4/social/components/CommentList/CommentList.tsx @@ -74,6 +74,9 @@ export const CommentList = ({ useIntersectionObserver({ ref: intersectionRef, + options: { + threshold: 0.8, + }, onIntersect: () => { if (hasMore && isLoading === false) { loadMore(); @@ -121,7 +124,7 @@ export const CommentList = ({ community={community} // targetId={post?.targetId || story?.targetId} // targetType={post?.targetType || story?.targetType} - shoudAllowInteraction={shouldAllowInteraction} + shouldAllowInteraction={shouldAllowInteraction} /> ); })} @@ -132,9 +135,8 @@ export const CommentList = ({ {isLoading && ( <CommentSkeleton pageId={pageId} componentId={componentId} numberOfSkeletons={3} /> )} - {!isLoading && ( - <div ref={intersectionRef} className={styles.commentList__container_intersection} /> - )} + + <div ref={intersectionRef} className={styles.commentList__container_intersection} /> </div> ); }; diff --git a/src/v4/social/pages/PostDetailPage/PostDetailPage.module.css b/src/v4/social/pages/PostDetailPage/PostDetailPage.module.css index 15a271496..881e97d33 100644 --- a/src/v4/social/pages/PostDetailPage/PostDetailPage.module.css +++ b/src/v4/social/pages/PostDetailPage/PostDetailPage.module.css @@ -84,7 +84,6 @@ flex-direction: column; gap: 1rem; padding: 0.75rem 1rem; - height: 100%; } @keyframes skeleton-pulse { From ee60c55bfc0faa7d9a63c46ee7b84b46ce03eb57 Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Wed, 24 Jul 2024 15:51:16 +0700 Subject: [PATCH 263/300] fix: ASC-24024 - mention list and media attachment position (#551) * fix: position media attachment * style: adapt data attribute * fix: query params * fix: mention list position * fix: z-index * fix: mention list position * fix: position mention * fix: unit rem --- .../DetailedMediaAttachment.module.css | 3 + .../MediaAttachment.module.css | 3 + .../CameraButton/CameraButton.module.css | 4 +- .../elements/CameraButton/CameraButton.tsx | 2 +- .../elements/FileButton/FileButton.module.css | 4 +- .../ImageButton/ImageButton.module.css | 4 +- .../elements/PostTextField/PostTextField.tsx | 10 +++- .../VideoButton/VideoButton.module.css | 4 +- .../elements/VideoButton/VideoButton.tsx | 2 +- .../CommunityMember.module.css | 1 - .../MentionTextInput.module.css | 14 ++++- .../MentionTextInput/MentionTextInput.tsx | 32 ++++++---- .../PostComposerPage.module.css | 18 +++--- .../PostComposerPage/PostComposerPage.tsx | 58 ++++++++++++++++--- 14 files changed, 116 insertions(+), 43 deletions(-) diff --git a/src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.module.css b/src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.module.css index 11d9e5107..a93c9f236 100644 --- a/src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.module.css +++ b/src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.module.css @@ -7,6 +7,9 @@ box-shadow: var(--asc-box-shadow-04); padding-bottom: 2rem; padding-top: 0.75rem; + margin-top: -1rem; + position: absolute; + bottom: -0.9rem; } .detailedMediaAttachment__swipeDown { diff --git a/src/v4/social/components/MediaAttachment/MediaAttachment.module.css b/src/v4/social/components/MediaAttachment/MediaAttachment.module.css index 074bf56b4..a10cdf8c8 100644 --- a/src/v4/social/components/MediaAttachment/MediaAttachment.module.css +++ b/src/v4/social/components/MediaAttachment/MediaAttachment.module.css @@ -8,6 +8,9 @@ border-top-left-radius: 1.25rem; border-top-right-radius: 1.25rem; box-shadow: var(--asc-box-shadow-04); + margin-top: 0.75rem; + position: absolute; + bottom: -0.4rem; } .mediaAttachment__swipeDown { diff --git a/src/v4/social/elements/CameraButton/CameraButton.module.css b/src/v4/social/elements/CameraButton/CameraButton.module.css index 207f4ba28..fe89da925 100644 --- a/src/v4/social/elements/CameraButton/CameraButton.module.css +++ b/src/v4/social/elements/CameraButton/CameraButton.module.css @@ -7,8 +7,8 @@ } .cameraButton__icon { - width: 1.5rem; - height: 1.5rem; + width: 2rem; + height: 2rem; stroke: var(--asc-color-base-default); background: var(--asc-color-base-shade4); border-radius: 50%; diff --git a/src/v4/social/elements/CameraButton/CameraButton.tsx b/src/v4/social/elements/CameraButton/CameraButton.tsx index 1dca24b79..8784cc592 100644 --- a/src/v4/social/elements/CameraButton/CameraButton.tsx +++ b/src/v4/social/elements/CameraButton/CameraButton.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useState } from 'react'; +import React, { useCallback } from 'react'; import { IconComponent } from '~/v4/core/IconComponent'; import { Typography } from '~/v4/core/components'; import { useAmityElement } from '~/v4/core/hooks/uikit'; diff --git a/src/v4/social/elements/FileButton/FileButton.module.css b/src/v4/social/elements/FileButton/FileButton.module.css index bfb98b2b4..6db502777 100644 --- a/src/v4/social/elements/FileButton/FileButton.module.css +++ b/src/v4/social/elements/FileButton/FileButton.module.css @@ -6,8 +6,8 @@ } .fileButton__icon { - width: 1.5rem; - height: 1.5rem; + width: 2rem; + height: 2rem; stroke: var(--asc-color-base-default); background: var(--asc-color-base-shade4); border-radius: 50%; diff --git a/src/v4/social/elements/ImageButton/ImageButton.module.css b/src/v4/social/elements/ImageButton/ImageButton.module.css index 73f1e5b98..02dfac2a0 100644 --- a/src/v4/social/elements/ImageButton/ImageButton.module.css +++ b/src/v4/social/elements/ImageButton/ImageButton.module.css @@ -7,8 +7,8 @@ } .imageButton__icon { - width: 1.5rem; - height: 1.5rem; + width: 2rem; + height: 2rem; stroke: var(--asc-color-base-default); background: var(--asc-color-base-shade4); border-radius: 50%; diff --git a/src/v4/social/elements/PostTextField/PostTextField.tsx b/src/v4/social/elements/PostTextField/PostTextField.tsx index 026905b93..01c2168be 100644 --- a/src/v4/social/elements/PostTextField/PostTextField.tsx +++ b/src/v4/social/elements/PostTextField/PostTextField.tsx @@ -35,6 +35,8 @@ interface EditorStateJson extends SerializedLexicalNode { interface PostTextFieldProps { onChange: (data: CreatePostParams) => void; communityId?: string | null; + onChangeSnap: number; + mentionContainer: HTMLElement | null; } function editorStateToText(editor: LexicalEditor) { @@ -75,7 +77,7 @@ function editorStateToText(editor: LexicalEditor) { } export const PostTextField = forwardRef<LexicalEditor, PostTextFieldProps>( - ({ onChange, communityId }) => { + ({ onChange, communityId, onChangeSnap, mentionContainer }) => { return ( <> <LexicalComposer initialConfig={editorConfig}> @@ -92,7 +94,11 @@ export const PostTextField = forwardRef<LexicalEditor, PostTextFieldProps>( /> <HistoryPlugin /> <AutoFocusPlugin /> - <MentionTextInputPlugin communityId={communityId} /> + <MentionTextInputPlugin + communityId={communityId} + onChangeSnap={onChangeSnap} + mentionContainer={mentionContainer} + /> </div> </LexicalComposer> </> diff --git a/src/v4/social/elements/VideoButton/VideoButton.module.css b/src/v4/social/elements/VideoButton/VideoButton.module.css index 69be521a5..32ce0aa61 100644 --- a/src/v4/social/elements/VideoButton/VideoButton.module.css +++ b/src/v4/social/elements/VideoButton/VideoButton.module.css @@ -7,8 +7,8 @@ } .videoButton__icon { - width: 1.5rem; - height: 1.5rem; + width: 2rem; + height: 2rem; stroke: var(--asc-color-base-default); background: var(--asc-color-base-shade4); border-radius: 50%; diff --git a/src/v4/social/elements/VideoButton/VideoButton.tsx b/src/v4/social/elements/VideoButton/VideoButton.tsx index c52da7cc8..a6a7f56d4 100644 --- a/src/v4/social/elements/VideoButton/VideoButton.tsx +++ b/src/v4/social/elements/VideoButton/VideoButton.tsx @@ -1,4 +1,4 @@ -import React, { useCallback } from 'react'; +import React from 'react'; import { IconComponent } from '~/v4/core/IconComponent'; import { Typography } from '~/v4/core/components'; import { useAmityElement } from '~/v4/core/hooks/uikit'; diff --git a/src/v4/social/internal-components/CommunityMember/CommunityMember.module.css b/src/v4/social/internal-components/CommunityMember/CommunityMember.module.css index 9cfc4a71a..9425aebbf 100644 --- a/src/v4/social/internal-components/CommunityMember/CommunityMember.module.css +++ b/src/v4/social/internal-components/CommunityMember/CommunityMember.module.css @@ -19,7 +19,6 @@ } .communityMember__displayName { - margin-left: 0.5rem; font-size: 1rem; display: block; white-space: nowrap; diff --git a/src/v4/social/internal-components/MentionTextInput/MentionTextInput.module.css b/src/v4/social/internal-components/MentionTextInput/MentionTextInput.module.css index 2b4a017b7..1d8ed207b 100644 --- a/src/v4/social/internal-components/MentionTextInput/MentionTextInput.module.css +++ b/src/v4/social/internal-components/MentionTextInput/MentionTextInput.module.css @@ -1,10 +1,22 @@ .mentionTextInput_item { position: absolute; - bottom: 0; left: 0; width: 100%; height: 12.5rem; overflow-y: scroll; + background-color: var(--asc-color-background-default); +} + +.mentionTextInput_item[data-media-attachement='0'] { + bottom: 6rem; +} + +.mentionTextInput_item[data-media-attachement='1'] { + bottom: 13rem; +} + +.mentionTextInput_item[data-media-attachement='2'] { + bottom: 16rem; } .mentionTextInput_item::-webkit-scrollbar { diff --git a/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx b/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx index e9da47e1b..c8ec569f7 100644 --- a/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx +++ b/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx @@ -110,22 +110,34 @@ export class MentionTypeaheadOption extends MenuOption { } } -export const MentionTextInputPlugin = ({ communityId }: { communityId?: string | null }) => { - const mentionTextInputItemRef = useRef<HTMLDivElement>(null); +export const MentionTextInputPlugin = ({ + communityId, + onChangeSnap, + mentionContainer, +}: { + communityId?: string | null; + onChangeSnap: number; + mentionContainer: HTMLElement | null; +}) => { return ( <div> - <div ref={mentionTextInputItemRef}></div> - <Mention anchorRef={mentionTextInputItemRef} communityId={communityId} /> + <Mention + anchorElement={mentionContainer} + communityId={communityId} + onChangeSnap={onChangeSnap} + /> </div> ); }; function Mention({ - anchorRef, + anchorElement, communityId, + onChangeSnap, }: { - anchorRef: RefObject<HTMLDivElement>; + anchorElement: HTMLElement | null; communityId?: string | null; + onChangeSnap: number; }) { const [editor] = useLexicalComposerContext(); @@ -162,7 +174,7 @@ function Mention({ ref: intersectionRef, }); - const community = useCommunity({ communityId }).community; + const community = useCommunity({ communityId, shouldCall: !!communityId }).community; const isPublic = community?.isPublic; if (communityId && !isPublic) { @@ -213,10 +225,10 @@ function Mention({ anchorElementRef, { selectedIndex, selectOptionAndCleanUp, setHighlightedIndex }, ) => - anchorRef.current && options.length > 0 + anchorElement && options.length > 0 ? ReactDOM.createPortal( <> - <div className={styles.mentionTextInput_item}> + <div data-media-attachement={onChangeSnap} className={styles.mentionTextInput_item}> {options.map((option, i: number) => ( <CommunityMember isSelected={selectedIndex === i} @@ -234,7 +246,7 @@ function Mention({ </div> <div ref={intersectionRef} /> </>, - anchorRef.current, + anchorElement, ) : null } diff --git a/src/v4/social/pages/PostComposerPage/PostComposerPage.module.css b/src/v4/social/pages/PostComposerPage/PostComposerPage.module.css index cabae3895..2316f3ed1 100644 --- a/src/v4/social/pages/PostComposerPage/PostComposerPage.module.css +++ b/src/v4/social/pages/PostComposerPage/PostComposerPage.module.css @@ -41,19 +41,17 @@ top: 0; } -.postComposerPage__formLong { - max-height: calc(100% - 92px); - overflow: auto; +.postComposerPage__formMediaAttachment { + max-height: calc(100% - 6.75rem); /* for form scroll */ + overflow-y: scroll; } -.postComposerPage__formShort { - max-height: calc(100% - 236px); /* 290px including file button */ - overflow: auto; -} +.postComposerPage__formMediaAttachment[data-from-media='false'] { + max-height: calc( + 100% - 14.875rem + ); /* 290px including file button for open detail media attachment */ -.postComposerPage__formLong::-webkit-scrollbar, -.postComposerPage__formShort::-webkit-scrollbar { - display: none; + overflow-y: scroll; } /* vaul */ diff --git a/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx b/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx index 33803132f..295f466f4 100644 --- a/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx +++ b/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; import styles from './PostComposerPage.module.css'; import { useAmityPage } from '~/v4/core/hooks/uikit'; import { CloseButton } from '~/v4/social/elements/CloseButton/CloseButton'; @@ -90,9 +90,10 @@ const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCr }); // const HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU = '290px'; //Including file button - const HEIGHT_MEDIA_ATTACHMENT_MENU = '92px'; - const HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU = '236px'; //Not including file button - const HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_2 = '176px'; //Show 2 menus + const HEIGHT_MEDIA_ATTACHMENT_MENU = '6.75rem'; + const HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_1 = '8.5rem'; //Show 1 menus + const HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_2 = '11rem'; //Show 2 menus + const HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_3 = '14.5rem'; //Not including file button const editorRef = useRef<LexicalEditor | null>(null); const { AmityPostComposerPageBehavior } = usePageBehavior(); @@ -140,6 +141,8 @@ const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCr { file: File; videoUrl: string; thumbnail: string | undefined }[] >([]); + const mentionRef = useRef<HTMLDivElement | null>(null); + const useMutateCreatePost = () => useMutation({ mutationFn: async (params: Parameters<typeof PostRepository.createPost>[0]) => { @@ -228,6 +231,23 @@ const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCr } }, []); + useEffect(() => { + if ( + (!isVisibleCamera && isVisibleImage && isVisibleVideo) || + (isVisibleCamera && isVisibleImage && !isVisibleVideo) || + (isVisibleCamera && !isVisibleImage && isVisibleVideo) + ) { + setSnap(HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_2); + } else if ( + (!isVisibleCamera && isVisibleImage && !isVisibleVideo) || + (!isVisibleCamera && !isVisibleImage && isVisibleVideo) + ) { + setSnap(HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_1); + } else { + setSnap(HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_3); + } + }, [isVisibleCamera, isVisibleImage, isVisibleVideo]); + const handleRemoveThumbnail = (index: number) => { setVideoThumbnail((prev) => prev.filter((_, i) => i !== index)); }; @@ -289,7 +309,11 @@ const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCr return ( <div className={styles.postComposerPage} style={themeStyles}> - <form onSubmit={handleSubmit(onSubmit)}> + <form + onSubmit={handleSubmit(onSubmit)} + data-from-media={snap == HEIGHT_MEDIA_ATTACHMENT_MENU} + className={styles.postComposerPage__formMediaAttachment} + > <div className={styles.postComposerPage__topBar}> <CloseButton pageId={pageId} onPress={onClickClose} /> <CommunityDisplayName pageId={pageId} community={community} /> @@ -299,7 +323,19 @@ const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCr isValid={textValue.text.length > 0 || postImages.length > 0 || postVideos.length > 0} /> </div> - <PostTextField ref={editorRef} onChange={onChange} communityId={targetId} /> + <PostTextField + ref={editorRef} + onChange={onChange} + communityId={targetId} + onChangeSnap={ + snap == HEIGHT_MEDIA_ATTACHMENT_MENU + ? 0 + : snap == HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_3 + ? 2 + : 1 + } + mentionContainer={mentionRef.current} + /> <ImageThumbnail files={incomingImages} uploadedFiles={postImages} @@ -312,6 +348,7 @@ const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCr onError={setIsErrorUpload} isErrorUpload={isErrorUpload} /> + <div ref={mentionRef} /> <VideoThumbnail files={incomingVideos} uploadedFiles={postVideos} @@ -326,6 +363,7 @@ const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCr videoThumbnail={videoThumbnail} removeThumbnail={handleRemoveThumbnail} /> + <div ref={mentionRef} /> </form> <div ref={drawerRef}></div> {drawerRef.current @@ -333,8 +371,9 @@ const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCr <Drawer.Root snapPoints={[ HEIGHT_MEDIA_ATTACHMENT_MENU, + HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_1, HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_2, - HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU, + HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_3, ]} activeSnapPoint={snap} setActiveSnapPoint={handleSnapChange} @@ -360,8 +399,9 @@ const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCr /> )} </div> - {snap == HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_2 || - snap == HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU ? ( + {snap == HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_1 || + snap == HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_2 || + snap == HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_3 ? ( <DetailedMediaAttachment pageId={pageId} isVisibleCamera={isVisibleCamera} From 81d15318713f1a171a153bb6e39b60b99b9d0a11 Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Wed, 24 Jul 2024 15:51:38 +0700 Subject: [PATCH 264/300] fix: query community sort (#552) --- .../components/MyCommunities/MyCommunities.tsx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/v4/social/components/MyCommunities/MyCommunities.tsx b/src/v4/social/components/MyCommunities/MyCommunities.tsx index 093ef0e0d..d5338451c 100644 --- a/src/v4/social/components/MyCommunities/MyCommunities.tsx +++ b/src/v4/social/components/MyCommunities/MyCommunities.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { CommunitySearchResult } from '~/v4/social/components/CommunitySearchResult/'; -import useCommunitiesCollection from '~/v4/core/hooks/collections/useCommunitiesCollection'; +import useCommunitiesCollection from '~/v4/social/hooks/collections/useCommunitiesCollection'; import { useAmityComponent } from '~/v4/core/hooks/uikit'; import styles from './MyCommunities.module.css'; @@ -11,14 +11,13 @@ interface MyCommunitiesProps { export const MyCommunities = ({ pageId = '*' }: MyCommunitiesProps) => { const componentId = 'my_communities'; - const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = - useAmityComponent({ - pageId, - componentId, - }); + const { themeStyles } = useAmityComponent({ + pageId, + componentId, + }); - const { communities, isLoading, error, hasMore, loadMore } = useCommunitiesCollection({ - membership: 'member', + const { communities, hasMore, loadMore, isLoading } = useCommunitiesCollection({ + queryParams: { sortBy: 'displayName', limit: 20, membership: 'member' }, }); return ( From e24d56b84f1cba3e7e3687020a4069958924195c Mon Sep 17 00:00:00 2001 From: "Pitchaya T." <33589608+ptchayap@users.noreply.github.com> Date: Wed, 24 Jul 2024 17:28:32 +0700 Subject: [PATCH 265/300] docs: update example env (#554) --- .env.example | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index 41b9754e4..cec6eb559 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,5 @@ -STORYBOOK_API_REGION= +# Please use comma to separate userIds example User1,User2,User3 +STORYBOOK_USERS= STORYBOOK_API_KEY= -STORYBOOK_USER1= -STORYBOOK_USER2= \ No newline at end of file +# eu, sg, us +STORYBOOK_API_REGION= \ No newline at end of file From 93575650ec17791ea3175ca0117bab1bb9be9e59 Mon Sep 17 00:00:00 2001 From: "Pitchaya T." <33589608+ptchayap@users.noreply.github.com> Date: Wed, 24 Jul 2024 19:03:13 +0700 Subject: [PATCH 266/300] Release/v4.0.0 beta.10 (#555) * chore: v4.0.0-beta.9 * chore(release): 4.0.0-beta.10 --------- Co-authored-by: bmo-amity-bot <developers@amity.co> --- CHANGELOG.md | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c67baaa1..8b12931ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## 4.0.0-beta.10 (2024-07-24) + ## 4.0.0-beta.8 (2024-06-24) ## 4.0.0-beta.7 (2024-06-18) diff --git a/package.json b/package.json index 1a1840545..e77ea4b3d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@amityco/ui-kit", - "version": "4.0.0-beta.8", + "version": "4.0.0-beta.10", "engines": { "node": ">=20", "pnpm": "9" From c37d928c99dac32872ef6efdd1816ab7a78d9e87 Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Thu, 1 Aug 2024 17:47:02 +0700 Subject: [PATCH 267/300] feat: ASC-24349 - edit post (#557) * feat: edit menu * feat: default value edit post * refactor: remove log * feat: edit image * feat: edit video * refactor: thumbnail component * refactor: remove log * feat: update items * fix: combineItemsWithAds * fix: post data selector * fix: mention enter new line * refactor: button * fix: element button --------- Co-authored-by: Bonn <pittawat@amity.co> --- src/v4/core/hooks/usePaginator.ts | 12 +- src/v4/core/hooks/usePostedUserInformation.ts | 11 +- src/v4/core/providers/AmityUIKitProvider.tsx | 53 +-- src/v4/core/providers/NavigationProvider.tsx | 45 +- .../core/providers/PageBehaviorProvider.tsx | 21 +- .../components/PostContent/PostContent.tsx | 22 +- .../CreateNewPostButton.tsx | 2 +- .../EditPostButton/EditPostButton.module.css | 12 + .../EditPostButton/EditPostButton.tsx | 25 ++ .../social/elements/EditPostButton/index.tsx | 1 + .../EditPostTitle/EditPostTitle.module.css | 6 + .../elements/EditPostTitle/EditPostTitle.tsx | 24 ++ .../social/elements/EditPostTitle/index.tsx | 1 + .../elements/PostTextField/PostTextField.tsx | 39 +- .../CreatePost/CreatePost.module.css | 65 +++ .../CreatePost/CreatePost.tsx | 379 +++++++++++++++++ .../internal-components/CreatePost/index.tsx | 1 + .../EditPost/EditPost.module.css | 62 +++ .../internal-components/EditPost/EditPost.tsx | 190 +++++++++ .../EditPost/Thumbnail.module.css | 84 ++++ .../EditPost/Thumbnail.tsx | 70 ++++ .../internal-components/EditPost/index.tsx | 1 + .../MentionTextInput/MentionTextInput.tsx | 4 +- .../PostMenu/PostMenu.module.css | 9 +- .../internal-components/PostMenu/PostMenu.tsx | 24 +- .../VideoThumbnail/VideoThumbnail.module.css | 15 + .../VideoThumbnail/VideoThumbnail.tsx | 5 +- .../PostComposerPage/PostComposerPage.tsx | 388 +----------------- .../social/providers/GlobalFeedProvider.tsx | 21 +- src/v4/social/utils/textToEditorState.ts | 110 +++++ 30 files changed, 1228 insertions(+), 474 deletions(-) create mode 100644 src/v4/social/elements/EditPostButton/EditPostButton.module.css create mode 100644 src/v4/social/elements/EditPostButton/EditPostButton.tsx create mode 100644 src/v4/social/elements/EditPostButton/index.tsx create mode 100644 src/v4/social/elements/EditPostTitle/EditPostTitle.module.css create mode 100644 src/v4/social/elements/EditPostTitle/EditPostTitle.tsx create mode 100644 src/v4/social/elements/EditPostTitle/index.tsx create mode 100644 src/v4/social/internal-components/CreatePost/CreatePost.module.css create mode 100644 src/v4/social/internal-components/CreatePost/CreatePost.tsx create mode 100644 src/v4/social/internal-components/CreatePost/index.tsx create mode 100644 src/v4/social/internal-components/EditPost/EditPost.module.css create mode 100644 src/v4/social/internal-components/EditPost/EditPost.tsx create mode 100644 src/v4/social/internal-components/EditPost/Thumbnail.module.css create mode 100644 src/v4/social/internal-components/EditPost/Thumbnail.tsx create mode 100644 src/v4/social/internal-components/EditPost/index.tsx create mode 100644 src/v4/social/utils/textToEditorState.ts diff --git a/src/v4/core/hooks/usePaginator.ts b/src/v4/core/hooks/usePaginator.ts index c71844638..eef8eba8c 100644 --- a/src/v4/core/hooks/usePaginator.ts +++ b/src/v4/core/hooks/usePaginator.ts @@ -45,13 +45,23 @@ const usePaginatorCore = <T>({ if (frequency?.type === 'fixed') { const newItemIds = new Set(newItems.map((item) => getItemId(item))); - const prevItemWithAds = itemWithAds + const prevItemWithAds: Array<[T] | [T, Amity.Ad]> = itemWithAds .map((itemWithAd) => { const itemId = getItemId(itemWithAd[0]); if (!newItemIds.has(itemId)) { return null; } + + const updatedItem = newItems.find((newItem) => getItemId(newItem) === itemId); + + if (updatedItem) { + if (itemWithAd.length === 1) { + return [updatedItem] as [T]; + } + return [updatedItem, itemWithAd[1]] as [T, Amity.Ad]; + } + return itemWithAd; }) .filter(isNonNullable); diff --git a/src/v4/core/hooks/usePostedUserInformation.ts b/src/v4/core/hooks/usePostedUserInformation.ts index 1aab50495..e1abd1e5b 100644 --- a/src/v4/core/hooks/usePostedUserInformation.ts +++ b/src/v4/core/hooks/usePostedUserInformation.ts @@ -1,16 +1,19 @@ import { useMemo } from 'react'; import useCommunityModeratorsCollection from '~/v4/social/hooks/collections/useCommunityModeratorsCollection'; +import useSDK from './useSDK'; export const usePostedUserInformation = ({ post, community, }: { - post: Amity.Post; + post?: Amity.Post; community?: Amity.Community | null; }) => { + const { currentUserId } = useSDK(); + const isCommunityPost = useMemo( - () => post.targetType === 'community' && post.targetId === community?.communityId, - [post.targetType, community?.communityId], + () => post?.targetType === 'community' && post?.targetId === community?.communityId, + [post?.targetType, community?.communityId], ); const { moderators } = useCommunityModeratorsCollection({ @@ -25,7 +28,7 @@ export const usePostedUserInformation = ({ return false; }, [moderators, isCommunityPost, post?.postedUserId]); - const isOwner = post.postedUserId === post?.postedUserId; + const isOwner = post?.postedUserId === currentUserId; return { isCommunityModerator, diff --git a/src/v4/core/providers/AmityUIKitProvider.tsx b/src/v4/core/providers/AmityUIKitProvider.tsx index d82c3454c..795b15bc1 100644 --- a/src/v4/core/providers/AmityUIKitProvider.tsx +++ b/src/v4/core/providers/AmityUIKitProvider.tsx @@ -152,33 +152,34 @@ const AmityUIKitProvider: React.FC<AmityUIKitProviderProps> = ({ <SDKConnectorProviderV3> <SDKConnectorProvider> <NotificationProvider> - <DrawerProvider> - <LegacyNotificationProvider> - <ConfirmProvider> - <LegacyConfirmProvider> - <ConfigProvider - config={{ - socialCommunityCreationButtonVisible: - socialCommunityCreationButtonVisible || true, - }} - > - <PostRendererProvider config={postRendererConfig}> - <NavigationProvider> - <PageBehaviorProvider pageBehavior={pageBehavior}> + <LegacyNotificationProvider> + <ConfirmProvider> + <LegacyConfirmProvider> + <ConfigProvider + config={{ + socialCommunityCreationButtonVisible: + socialCommunityCreationButtonVisible || true, + }} + > + <PostRendererProvider config={postRendererConfig}> + <NavigationProvider> + <PageBehaviorProvider pageBehavior={pageBehavior}> + <DrawerProvider> <GlobalFeedProvider>{children}</GlobalFeedProvider> - </PageBehaviorProvider> - </NavigationProvider> - </PostRendererProvider> - </ConfigProvider> - <NotificationsContainer /> - <LegacyNotificationsContainer /> - <ConfirmComponent /> - <DrawerContainer /> - <LegacyConfirmComponent /> - </LegacyConfirmProvider> - </ConfirmProvider> - </LegacyNotificationProvider> - </DrawerProvider> + <DrawerContainer /> + </DrawerProvider> + </PageBehaviorProvider> + </NavigationProvider> + </PostRendererProvider> + </ConfigProvider> + <NotificationsContainer /> + <LegacyNotificationsContainer /> + <ConfirmComponent /> + + <LegacyConfirmComponent /> + </LegacyConfirmProvider> + </ConfirmProvider> + </LegacyNotificationProvider> </NotificationProvider> </SDKConnectorProvider> </SDKConnectorProviderV3> diff --git a/src/v4/core/providers/NavigationProvider.tsx b/src/v4/core/providers/NavigationProvider.tsx index c15aaa0f0..3dbe45f54 100644 --- a/src/v4/core/providers/NavigationProvider.tsx +++ b/src/v4/core/providers/NavigationProvider.tsx @@ -142,11 +142,14 @@ type ContextValue = { | undefined, ) => void; goToPostComposerPage: ( - mode: Mode, - targetId: string | null, - targetType: 'community' | 'user', - community?: Amity.Community, - post?: Amity.Post, + context: + | { + mode: Mode.CREATE; + targetId: string | null; + targetType: 'community' | 'user'; + community?: Amity.Community; + } + | { mode: Mode.EDIT; post: Amity.Post }, ) => void; goToStoryCreationPage: (context: { targetId: string; @@ -190,13 +193,7 @@ let defaultValue: ContextValue = { goToSocialGlobalSearchPage: (tab?: string) => {}, goToSelectPostTargetPage: () => {}, goToStoryTargetSelectionPage: () => {}, - goToPostComposerPage: ( - mode: Mode, - targetId: string | null, - targetType: 'community' | 'user', - community?: Amity.Community, - post?: Amity.Post, - ) => {}, + goToPostComposerPage: () => {}, goToStoryCreationPage: () => {}, goToSocialHomePage: () => {}, goToMyCommunitiesSearchPage: () => {}, @@ -239,10 +236,7 @@ if (process.env.NODE_ENV !== 'production') { goToStoryTargetSelectionPage: () => console.log('NavigationContext goToStoryTargetSelectionPage()'), goToDraftStoryPage: (data) => console.log(`NavigationContext goToDraftStoryPage()`), - goToPostComposerPage: (mode, targetId, targetType, community, post) => - console.log( - `NavigationContext goToPostComposerPage(${mode} ${targetId}) ${targetType} ${community} ${post}`, - ), + goToPostComposerPage: () => console.log(`NavigationContext goToPostComposerPage()`), goToStoryCreationPage: () => console.log('NavigationContext goToStoryCreationPage()'), goToSocialHomePage: () => console.log('NavigationContext goToSocialHomePage()'), goToMyCommunitiesSearchPage: () => @@ -611,16 +605,19 @@ export default function NavigationProvider({ }, [onChangePage, pushPage]); const goToPostComposerPage = useCallback( - (mode, targetId, targetType, community, post) => { + ( + context: + | { + mode: Mode.CREATE; + targetId: string | null; + targetType: 'community' | 'user'; + community?: Amity.Community; + } + | { mode: Mode.EDIT; post: Amity.Post }, + ) => { const next = { type: PageTypes.PostComposerPage, - context: { - mode, - targetId, - targetType, - community, - post, - }, + context, }; pushPage(next); diff --git a/src/v4/core/providers/PageBehaviorProvider.tsx b/src/v4/core/providers/PageBehaviorProvider.tsx index f0fdf5184..a8a3f1f81 100644 --- a/src/v4/core/providers/PageBehaviorProvider.tsx +++ b/src/v4/core/providers/PageBehaviorProvider.tsx @@ -25,6 +25,7 @@ export interface PageBehavior { AmityPostContentComponentBehavior?: { goToCommunityProfilePage?: (context: { communityId: string }) => void; goToUserProfilePage?: (context: { userId: string }) => void; + goToPostComposerPage?: (context: { mode: Mode.EDIT; post: Amity.Post }) => void; }; AmitySocialGlobalSearchPageBehavior?: Record<string, unknown>; AmityCommunitySearchResultComponentBehavior?: { @@ -36,11 +37,10 @@ export interface PageBehavior { }; AmityPostTargetSelectionPage?: { goToPostComposerPage?: (context: { - mode: Mode.CREATE | Mode.EDIT; + mode: Mode.CREATE; targetId: string | null; targetType: 'community' | 'user'; community?: Amity.Community; - post?: Amity.Post; }) => void; }; AmityStoryTargetSelectionPage?: { @@ -145,6 +145,12 @@ export const PageBehaviorProvider: React.FC<PageBehaviorProviderProps> = ({ } goToUserProfilePage(context.userId); }, + goToPostComposerPage: (context: { mode: Mode.EDIT; post: Amity.Post }) => { + if (pageBehavior?.AmityPostContentComponentBehavior?.goToPostComposerPage) { + return pageBehavior.AmityPostContentComponentBehavior.goToPostComposerPage(context); + } + goToPostComposerPage(context); + }, }, AmitySocialGlobalSearchPageBehavior: {}, @@ -174,22 +180,15 @@ export const PageBehaviorProvider: React.FC<PageBehaviorProviderProps> = ({ }, AmityPostTargetSelectionPage: { goToPostComposerPage: (context: { - mode: Mode.CREATE | Mode.EDIT; + mode: Mode.CREATE; targetId: string | null; targetType: 'community' | 'user'; community?: Amity.Community; - post?: Amity.Post; }) => { if (pageBehavior?.AmityPostTargetSelectionPage?.goToPostComposerPage) { return pageBehavior.AmityPostTargetSelectionPage.goToPostComposerPage(context); } - goToPostComposerPage( - context.mode, - context.targetId, - context.targetType, - context.community, - context.post, - ); + goToPostComposerPage(context); }, }, AmityStoryTargetSelectionPage: { diff --git a/src/v4/social/components/PostContent/PostContent.tsx b/src/v4/social/components/PostContent/PostContent.tsx index 3eb10490d..3b4adb1a4 100644 --- a/src/v4/social/components/PostContent/PostContent.tsx +++ b/src/v4/social/components/PostContent/PostContent.tsx @@ -36,6 +36,7 @@ import { usePostedUserInformation } from '~/v4/core/hooks/usePostedUserInformati import millify from 'millify'; import { Button } from '~/v4/core/natives/Button'; import { useNavigation } from '~/v4/core/providers/NavigationProvider'; +import dayjs from 'dayjs'; interface PostTitleProps { post: Amity.Post; @@ -46,7 +47,7 @@ const PostTitle = ({ pageId, post }: PostTitleProps) => { const shouldCall = useMemo(() => post?.targetType === 'community', [post?.targetType]); const { community: targetCommunity } = useCommunity({ - communityId: post.targetId, + communityId: post?.targetId, shouldCall, }); @@ -174,8 +175,6 @@ export const PostContent = ({ const { post: postData } = usePost(initialPost?.postId); const { setDrawerData, removeDrawerData } = useDrawer(); - const post = postData || initialPost; - const [shouldSubscribe, setShouldSubscribe] = useState(false); const [isImageViewerOpen, setIsImageViewerOpen] = useState(false); const [isVideoViewerOpen, setIsVideoViewerOpen] = useState(false); @@ -185,6 +184,21 @@ export const PostContent = ({ const [reactionByMe, setReactionByMe] = useState<string | null>(null); const [reactionsCount, setReactionsCount] = useState<number>(0); + const post = useMemo(() => { + if (initialPost != null && postData != null) { + if (dayjs(initialPost?.updatedAt).unix() > dayjs(postData?.updatedAt).unix()) { + return initialPost; + } + return postData; + } + if (postData != null) { + return postData; + } + if (initialPost != null) { + return initialPost; + } + }, [initialPost, postData]); + usePostSubscription({ postId: post?.postId, level: SubscriptionLevels.POST, @@ -212,7 +226,7 @@ export const PostContent = ({ useEffect(() => { if (post == null) return; setReactionByMe(post.myReactions?.[0] || null); - }, [post.myReactions]); + }, [post?.myReactions]); useEffect(() => { if (post == null) return; diff --git a/src/v4/social/elements/CreateNewPostButton/CreateNewPostButton.tsx b/src/v4/social/elements/CreateNewPostButton/CreateNewPostButton.tsx index cc77a28ae..0c2757c3a 100644 --- a/src/v4/social/elements/CreateNewPostButton/CreateNewPostButton.tsx +++ b/src/v4/social/elements/CreateNewPostButton/CreateNewPostButton.tsx @@ -1,4 +1,4 @@ -import React, { FormEventHandler } from 'react'; +import React from 'react'; import { useAmityElement } from '~/v4/core/hooks/uikit'; import styles from './CreateNewPostButton.module.css'; diff --git a/src/v4/social/elements/EditPostButton/EditPostButton.module.css b/src/v4/social/elements/EditPostButton/EditPostButton.module.css new file mode 100644 index 000000000..5a09cb1c9 --- /dev/null +++ b/src/v4/social/elements/EditPostButton/EditPostButton.module.css @@ -0,0 +1,12 @@ +.editPostButton { + display: flex; + font-weight: var(--asc-text-font-weight-normal); + color: var(--asc-color-primary-default); +} + +.editPostButton:disabled { + display: flex; + font-weight: var(--asc-text-font-weight-normal); + color: var(--asc-color-primary-shade2); + cursor: not-allowed; +} diff --git a/src/v4/social/elements/EditPostButton/EditPostButton.tsx b/src/v4/social/elements/EditPostButton/EditPostButton.tsx new file mode 100644 index 000000000..cc62b1f88 --- /dev/null +++ b/src/v4/social/elements/EditPostButton/EditPostButton.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import styles from './EditPostButton.module.css'; +import { Button, ButtonProps } from '~/v4/core/natives/Button'; + +type EditPostButtonProps = ButtonProps & { + pageId: string; + componentId?: string; +}; + +export function EditPostButton({ pageId = '*', componentId = '*', ...props }: EditPostButtonProps) { + const elementId = 'edit_post_button'; + const { config, isExcluded, themeStyles } = useAmityElement({ + pageId, + componentId, + elementId, + }); + if (isExcluded) return null; + + return ( + <Button style={themeStyles} className={styles.editPostButton} {...props}> + {config.text} + </Button> + ); +} diff --git a/src/v4/social/elements/EditPostButton/index.tsx b/src/v4/social/elements/EditPostButton/index.tsx new file mode 100644 index 000000000..97fac8e98 --- /dev/null +++ b/src/v4/social/elements/EditPostButton/index.tsx @@ -0,0 +1 @@ +export { EditPostButton } from './EditPostButton'; diff --git a/src/v4/social/elements/EditPostTitle/EditPostTitle.module.css b/src/v4/social/elements/EditPostTitle/EditPostTitle.module.css new file mode 100644 index 000000000..2074976cb --- /dev/null +++ b/src/v4/social/elements/EditPostTitle/EditPostTitle.module.css @@ -0,0 +1,6 @@ +.editPostTitle { + font-size: 1rem; + font-weight: 600; + line-height: 1.375rem; + color: var(--asc-color-base-default); +} diff --git a/src/v4/social/elements/EditPostTitle/EditPostTitle.tsx b/src/v4/social/elements/EditPostTitle/EditPostTitle.tsx new file mode 100644 index 000000000..732857dca --- /dev/null +++ b/src/v4/social/elements/EditPostTitle/EditPostTitle.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import styles from './EditPostTitle.module.css'; + +interface EditPostTitleProps { + pageId: string; + componentId?: string; +} + +export function EditPostTitle({ pageId = '*', componentId = '*' }: EditPostTitleProps) { + const elementId = 'edit_post_title'; + const { config, isExcluded, themeStyles } = useAmityElement({ + pageId, + componentId, + elementId, + }); + if (isExcluded) return null; + + return ( + <div style={themeStyles} className={styles.editPostTitle}> + {config.text} + </div> + ); +} diff --git a/src/v4/social/elements/EditPostTitle/index.tsx b/src/v4/social/elements/EditPostTitle/index.tsx new file mode 100644 index 000000000..8e177f40e --- /dev/null +++ b/src/v4/social/elements/EditPostTitle/index.tsx @@ -0,0 +1 @@ +export { EditPostTitle } from './EditPostTitle'; diff --git a/src/v4/social/elements/PostTextField/PostTextField.tsx b/src/v4/social/elements/PostTextField/PostTextField.tsx index 01c2168be..947204adc 100644 --- a/src/v4/social/elements/PostTextField/PostTextField.tsx +++ b/src/v4/social/elements/PostTextField/PostTextField.tsx @@ -11,6 +11,8 @@ import { MentionNode } from '~/v4/social/internal-components/MentionTextInput/Me import { MentionTextInputPlugin } from '~/v4/social/internal-components/MentionTextInput/MentionTextInput'; import { CreatePostParams, MetaData } from '~/v4/social/pages/PostComposerPage/PostComposerPage'; import styles from './PostTextField.module.css'; +import { Mentioned, Mentionees } from '~/v4/helpers/utils'; +import { textToEditorState } from '~/v4/social/utils/textToEditorState'; const theme = { ltr: 'ltr', @@ -19,15 +21,6 @@ const theme = { paragraph: styles.editorParagraph, }; -const editorConfig = { - namespace: 'PostTextField', - theme: theme, - onError(error: Error) { - throw error; - }, - nodes: [MentionNode], -}; - interface EditorStateJson extends SerializedLexicalNode { children: []; } @@ -35,8 +28,15 @@ interface EditorStateJson extends SerializedLexicalNode { interface PostTextFieldProps { onChange: (data: CreatePostParams) => void; communityId?: string | null; - onChangeSnap: number; + onChangeSnap?: number; mentionContainer: HTMLElement | null; + dataValue: { + data: { text: string }; + metadata?: { + mentioned?: Mentioned[]; + }; + mentionees?: Mentionees; + }; } function editorStateToText(editor: LexicalEditor) { @@ -77,10 +77,25 @@ function editorStateToText(editor: LexicalEditor) { } export const PostTextField = forwardRef<LexicalEditor, PostTextFieldProps>( - ({ onChange, communityId, onChangeSnap, mentionContainer }) => { + ({ onChange, communityId, onChangeSnap, mentionContainer, dataValue }) => { + const editorConfig = { + namespace: 'PostTextField', + theme: theme, + onError(error: Error) { + throw error; + }, + nodes: [MentionNode], + }; return ( <> - <LexicalComposer initialConfig={editorConfig}> + <LexicalComposer + initialConfig={{ + ...editorConfig, + ...(dataValue?.data.text + ? { editorState: JSON.stringify(textToEditorState(dataValue)) } + : {}), + }} + > <div className={styles.editorContainer}> <RichTextPlugin contentEditable={<ContentEditable />} diff --git a/src/v4/social/internal-components/CreatePost/CreatePost.module.css b/src/v4/social/internal-components/CreatePost/CreatePost.module.css new file mode 100644 index 000000000..93ea3856b --- /dev/null +++ b/src/v4/social/internal-components/CreatePost/CreatePost.module.css @@ -0,0 +1,65 @@ +.createPost { + position: relative; + display: block; + background-color: var(--asc-color-background-default); + height: 100%; +} + +.createPost__topBar { + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + padding: var(--asc-spacing-m1); +} + +.createPost__status { + width: calc(100% - 2rem); + position: absolute; + bottom: 0; + left: 1rem; + display: flex; + justify-content: start; + padding: unset; + font-size: var(--asc-text-font-size-md); + gap: 0.75rem; + margin: 0 auto 1rem; + border-radius: 0.5rem; + box-shadow: var(--asc-box-shadow-03); + background-color: var(--asc-color-secondary-default); + color: var(--asc-color-background-default); +} + +.createPost_infoIcon { + width: 1.5rem; + height: 1.5rem; + fill: var(--asc-color-background-default); +} + +.createPost__notiWrap { + position: relative; + top: 0; +} + +.createPost__formMediaAttachment { + max-height: calc(100% - 6.75rem); /* for form scroll */ + overflow-y: scroll; +} + +.createPost__formMediaAttachment[data-from-media='false'] { + max-height: calc( + 100% - 14.875rem + ); /* 290px including file button for open detail media attachment */ + + overflow-y: scroll; +} + +/* vaul */ + +.drawer__content { + position: absolute; + width: 100%; + top: 0; + left: 0; + outline: none; +} diff --git a/src/v4/social/internal-components/CreatePost/CreatePost.tsx b/src/v4/social/internal-components/CreatePost/CreatePost.tsx new file mode 100644 index 000000000..ea0cf8ef3 --- /dev/null +++ b/src/v4/social/internal-components/CreatePost/CreatePost.tsx @@ -0,0 +1,379 @@ +import React, { useEffect, useRef, useState } from 'react'; +import styles from './CreatePost.module.css'; +import { PostRepository } from '@amityco/ts-sdk'; +import { useMutation } from '@tanstack/react-query'; +import { LexicalEditor } from 'lexical'; +import { useForm } from 'react-hook-form'; +import { useAmityPage } from '~/v4/core/hooks/uikit'; +import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; +import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; +import { + AmityPostComposerCreateOptions, + CreatePostParams, +} from '~/v4/social/pages/PostComposerPage/PostComposerPage'; +import { useGlobalFeedContext } from '~/v4/social/providers/GlobalFeedProvider'; +import ExclamationCircle from '~/v4/icons/ExclamationCircle'; +import { isMobile } from '~/v4/social/utils/isMobile'; +import { generateThumbnailVideo } from '~/v4/social/utils/generateThumbnailVideo'; +import { CommunityDisplayName } from '~/v4/social/elements/CommunityDisplayName'; +import { CreateNewPostButton } from '~/v4/social/elements/CreateNewPostButton'; +import { PostTextField } from '~/v4/social/elements/PostTextField'; +import { ImageThumbnail } from '~/v4/social/internal-components/ImageThumbnail'; +import { VideoThumbnail } from '~/v4/social/internal-components/VideoThumbnail'; +import ReactDOM from 'react-dom'; +import { Drawer } from 'vaul'; +import { Spinner } from '~/v4/social/internal-components/Spinner'; +import { MediaAttachment } from '~/v4/social/components/MediaAttachment'; +import { DetailedMediaAttachment } from '~/v4/social/components/DetailedMediaAttachment'; +import { CloseButton } from '~/v4/social/elements/CloseButton/CloseButton'; +import { Notification } from '~/v4/core/components/Notification'; + +export function CreatePost({ community, targetType, targetId }: AmityPostComposerCreateOptions) { + const pageId = 'post_composer_page'; + const { themeStyles } = useAmityPage({ + pageId, + }); + + // const HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU = '290px'; //Including file button + const HEIGHT_MEDIA_ATTACHMENT_MENU = '6.75rem'; + const HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_1 = '8.5rem'; //Show 1 menus + const HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_2 = '11rem'; //Show 2 menus + const HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_3 = '14.5rem'; //Not including file button + + const editorRef = useRef<LexicalEditor | null>(null); + const { AmityPostComposerPageBehavior } = usePageBehavior(); + const { confirm } = useConfirmContext(); + const [snap, setSnap] = useState<number | string | null>(HEIGHT_MEDIA_ATTACHMENT_MENU); + const [isShowBottomMenu] = useState<boolean>(true); + const drawerRef = useRef<HTMLDivElement>(null); + const { prependItem } = useGlobalFeedContext(); + + const [textValue, setTextValue] = useState<CreatePostParams>({ + text: '', + mentioned: [], + mentionees: [ + { + type: 'user', + userIds: [''], + }, + ], + attachments: [ + { + fileId: '', + type: 'image', + }, + ], + }); + + //Upload media + const [postImages, setPostImages] = useState<Amity.File[]>([]); + const [postVideos, setPostVideos] = useState<Amity.File[]>([]); + + // Images/files incoming from uploads. + const [incomingImages, setIncomingImages] = useState<File[]>([]); + const [incomingVideos, setIncomingVideos] = useState<File[]>([]); + const [uploadLoading, setUploadLoading] = useState(false); + + const [uploadedImagesCount, setUploadedImagesCount] = useState<File[]>([]); + + // Visible menu attachment + const [isVisibleCamera, setIsVisibleCamera] = useState(false); + const [isVisibleImage, setIsVisibleImage] = useState(true); + const [isVisibleVideo, setIsVisibleVideo] = useState(true); + + const [isErrorUpload, setIsErrorUpload] = useState(false); + const [videoThumbnail, setVideoThumbnail] = useState< + { file: File; videoUrl: string; thumbnail: string | undefined }[] + >([]); + + const mentionRef = useRef<HTMLDivElement | null>(null); + + const useMutateCreatePost = () => + useMutation({ + mutationFn: async (params: Parameters<typeof PostRepository.createPost>[0]) => { + return PostRepository.createPost(params); + }, + onSuccess: (response) => { + AmityPostComposerPageBehavior?.goToSocialHomePage?.(); + prependItem(response.data); + }, + onError: (error) => { + console.error('Failed to create post', error); + }, + }); + + const { mutateAsync: mutateCreatePostAsync, isPending, isError } = useMutateCreatePost(); + + const { handleSubmit } = useForm(); + + const onSubmit = () => { + const attachmentsImage = postImages.map((file) => ({ + type: 'image', + fileId: file.fileId, + })); + const attachmentsVideo = postVideos.map((file) => ({ + type: 'video', + fileId: file.fileId, + })); + + const attachments = [...attachmentsImage, ...attachmentsVideo]; + + mutateCreatePostAsync({ + targetId: targetId, + targetType: targetType, + data: { text: textValue.text }, + dataType: 'text', + metadata: { mentioned: textValue.mentioned }, + mentionees: textValue.mentionees, + attachments: attachments, + }); + }; + + const onChange = (val: CreatePostParams) => { + setTextValue(val); + }; + + const handleSnapChange = (newSnap: string | number | null) => { + if (snap === HEIGHT_MEDIA_ATTACHMENT_MENU && newSnap === '0px') { + return; + } + setSnap(newSnap); + }; + + const onClickClose = () => { + confirm({ + pageId: pageId, + type: 'confirm', + title: 'Discard this post?', + content: 'The post will be permanently deleted. It cannot be undone.', + onOk: () => { + AmityPostComposerPageBehavior?.goToSocialHomePage?.(); + }, + okText: 'Discard', + cancelText: 'Keep editing', + }); + }; + + useEffect(() => { + if (postImages.length > 0) { + setIsVisibleImage(true); + setIsVisibleVideo(false); + } else if (postVideos.length > 0 && videoThumbnail.length > 0) { + setIsVisibleImage(false); + setIsVisibleVideo(true); + } else if (postImages.length == 0 || videoThumbnail.length == 0) { + setIsVisibleImage(true); + setIsVisibleVideo(true); + } + }, [postImages, postVideos, isVisibleImage, isVisibleVideo, videoThumbnail]); + + //check mobile device + useEffect(() => { + if (isMobile()) { + setIsVisibleCamera(true); + } else { + setIsVisibleCamera(false); + } + }, []); + + useEffect(() => { + if ( + (!isVisibleCamera && isVisibleImage && isVisibleVideo) || + (isVisibleCamera && isVisibleImage && !isVisibleVideo) || + (isVisibleCamera && !isVisibleImage && isVisibleVideo) + ) { + setSnap(HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_2); + } else if ( + (!isVisibleCamera && isVisibleImage && !isVisibleVideo) || + (!isVisibleCamera && !isVisibleImage && isVisibleVideo) + ) { + setSnap(HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_1); + } else { + setSnap(HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_3); + } + }, [isVisibleCamera, isVisibleImage, isVisibleVideo]); + + const handleRemoveThumbnail = (index: number) => { + setVideoThumbnail((prev) => prev.filter((_, i) => i !== index)); + }; + + const handleImageFileChange = (file: File[]) => { + if (file.length + uploadedImagesCount.length > 10) { + confirm({ + pageId: pageId, + type: 'info', + title: 'Maximum upload limit reached', + content: + 'You’ve reached the upload limit of 10 images. Any additional images will not be saved. ', + okText: 'Close', + }); + return; + } + + if (file.length > 0) { + setUploadedImagesCount((prevImages) => [...prevImages, ...file]); + setIncomingImages(file); + } + }; + + const handleVideoFileChange = async (file: File[]) => { + const existingVideosCount = videoThumbnail ? videoThumbnail.length : 0; + + if (file.length + existingVideosCount > 10) { + confirm({ + pageId: pageId, + type: 'info', + title: 'Maximum upload limit reached', + content: + 'You’ve reached the upload limit of 10 videos. Any additional videos will not be saved. ', + okText: 'Close', + }); + return; + } + + if (file.length > 0) { + setIncomingVideos?.(file); + const updatedVideos = file.map((file) => ({ + file, + videoUrl: URL.createObjectURL(file), + thumbnail: undefined, + })); + + const thumbnailVideo = await Promise.all( + updatedVideos.map(async (video) => { + const thumbnail = await generateThumbnailVideo(video.file); + return { + ...video, + thumbnail: thumbnail, + }; + }), + ); + setVideoThumbnail((prev) => [...prev, ...thumbnailVideo]); + } + }; + + return ( + <div className={styles.createPost} style={themeStyles}> + <form + onSubmit={handleSubmit(onSubmit)} + data-from-media={snap == HEIGHT_MEDIA_ATTACHMENT_MENU} + className={styles.createPost__formMediaAttachment} + > + <div className={styles.createPost__topBar}> + <CloseButton pageId={pageId} onPress={onClickClose} /> + <CommunityDisplayName pageId={pageId} community={community} /> + <CreateNewPostButton + pageId={pageId} + onSubmit={handleSubmit(onSubmit)} + isValid={textValue.text.length > 0 || postImages.length > 0 || postVideos.length > 0} + /> + </div> + <PostTextField + ref={editorRef} + onChange={onChange} + communityId={targetId} + onChangeSnap={ + snap == HEIGHT_MEDIA_ATTACHMENT_MENU + ? 0 + : snap == HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_3 + ? 2 + : 1 + } + mentionContainer={mentionRef.current} + dataValue={{ + data: { text: textValue.text }, + }} + /> + <ImageThumbnail + files={incomingImages} + uploadedFiles={postImages} + uploadLoading={uploadLoading} + onLoadingChange={setUploadLoading} + onChange={({ uploaded, uploading }) => { + setPostImages(uploaded); + setIncomingImages(uploading); + }} + onError={setIsErrorUpload} + isErrorUpload={isErrorUpload} + /> + <div ref={mentionRef} /> + <VideoThumbnail + files={incomingVideos} + uploadedFiles={postVideos} + uploadLoading={uploadLoading} + onLoadingChange={setUploadLoading} + onChange={({ uploaded, uploading }) => { + setPostVideos(uploaded); + setIncomingVideos(uploading); + }} + onError={setIsErrorUpload} + isErrorUpload={isErrorUpload} + videoThumbnail={videoThumbnail} + removeThumbnail={handleRemoveThumbnail} + /> + <div ref={mentionRef} /> + </form> + <div ref={drawerRef}></div> + {drawerRef.current + ? ReactDOM.createPortal( + <Drawer.Root + snapPoints={[ + HEIGHT_MEDIA_ATTACHMENT_MENU, + HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_1, + HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_2, + HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_3, + ]} + activeSnapPoint={snap} + setActiveSnapPoint={handleSnapChange} + open={isShowBottomMenu} + modal={false} + > + <Drawer.Portal container={drawerRef.current}> + <Drawer.Content className={styles.drawer__content}> + <div className={styles.createPost__notiWrap}> + {isPending && ( + <Notification + content="Posting..." + icon={<Spinner />} + className={styles.createPost__status} + /> + )} + {isError && ( + <Notification + content="Failed to create post" + icon={<ExclamationCircle className={styles.createPost_infoIcon} />} + className={styles.createPost__status} + duration={3000} + /> + )} + </div> + {snap == HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_1 || + snap == HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_2 || + snap == HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_3 ? ( + <DetailedMediaAttachment + pageId={pageId} + isVisibleCamera={isVisibleCamera} + isVisibleImage={isVisibleImage} + isVisibleVideo={isVisibleVideo} + onVideoFileChange={handleVideoFileChange} + onImageFileChange={handleImageFileChange} + /> + ) : ( + <MediaAttachment + pageId={pageId} + isVisibleCamera={isVisibleCamera} + isVisibleImage={isVisibleImage} + isVisibleVideo={isVisibleVideo} + onVideoFileChange={handleVideoFileChange} + onImageFileChange={handleImageFileChange} + /> + )} + </Drawer.Content> + </Drawer.Portal> + </Drawer.Root>, + drawerRef.current, + ) + : null} + </div> + ); +} diff --git a/src/v4/social/internal-components/CreatePost/index.tsx b/src/v4/social/internal-components/CreatePost/index.tsx new file mode 100644 index 000000000..3af79b2c0 --- /dev/null +++ b/src/v4/social/internal-components/CreatePost/index.tsx @@ -0,0 +1 @@ +export { CreatePost } from './CreatePost'; diff --git a/src/v4/social/internal-components/EditPost/EditPost.module.css b/src/v4/social/internal-components/EditPost/EditPost.module.css new file mode 100644 index 000000000..3e4a12fb9 --- /dev/null +++ b/src/v4/social/internal-components/EditPost/EditPost.module.css @@ -0,0 +1,62 @@ +.editPost { + position: relative; + display: block; + background-color: var(--asc-color-background-default); + height: 100%; +} + +.editPost__topBar { + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + padding: var(--asc-spacing-m1); +} + +.editPost__status { + width: calc(100% - 2rem); + position: absolute; + bottom: 0; + left: 1rem; + display: flex; + justify-content: start; + padding: unset; + font-size: var(--asc-text-font-size-md); + gap: 0.75rem; + margin: 0 auto 1rem; + border-radius: 0.5rem; + box-shadow: var(--asc-box-shadow-03); + background-color: var(--asc-color-secondary-default); + color: var(--asc-color-background-default); +} + +.editPost_infoIcon { + width: 1.5rem; + height: 1.5rem; + fill: var(--asc-color-background-default); +} + +.editPost__notiWrap { + position: absolute; + bottom: 0; + width: 100%; +} + +.editPost__formMediaAttachment { + max-height: 100%; /* for form scroll */ + overflow-y: scroll; +} + +.editPost__formMediaAttachment::-webkit-scrollbar { + width: 0.5rem; +} + +/* vaul */ + +.drawer__content { + position: absolute; + width: 100%; + top: 0; + left: 0; + outline: none; +} diff --git a/src/v4/social/internal-components/EditPost/EditPost.tsx b/src/v4/social/internal-components/EditPost/EditPost.tsx new file mode 100644 index 000000000..e4302a0e2 --- /dev/null +++ b/src/v4/social/internal-components/EditPost/EditPost.tsx @@ -0,0 +1,190 @@ +import React, { useEffect, useRef, useState } from 'react'; +import styles from './EditPost.module.css'; +import { PostContentType, PostRepository } from '@amityco/ts-sdk'; +import { useMutation } from '@tanstack/react-query'; +import { LexicalEditor } from 'lexical'; +import { useForm } from 'react-hook-form'; +import { useAmityPage } from '~/v4/core/hooks/uikit'; +import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; +import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; +import { + AmityPostComposerEditOptions, + CreatePostParams, +} from '~/v4/social/pages/PostComposerPage/PostComposerPage'; +import { PostTextField } from '~/v4/social/elements/PostTextField'; +import { Spinner } from '~/v4/social/internal-components/Spinner'; +import { CloseButton } from '~/v4/social/elements/CloseButton/CloseButton'; +import { Notification } from '~/v4/core/components/Notification'; +import { EditPostButton } from '~/v4/social/elements/EditPostButton'; +import { EditPostTitle } from '~/v4/social/elements/EditPostTitle'; +import usePostByIds from '~/v4/core/hooks/usePostByIds'; +import { ExclamationCircle } from '~/icons'; +import { Thumbnail } from './Thumbnail'; +import { useGlobalFeedContext } from '~/v4/social/providers/GlobalFeedProvider'; + +export function EditPost({ post }: AmityPostComposerEditOptions) { + const pageId = 'post_composer_page'; + const { themeStyles } = useAmityPage({ + pageId, + }); + + const editorRef = useRef<LexicalEditor | null>(null); + const { AmityPostComposerPageBehavior } = usePageBehavior(); + const { confirm } = useConfirmContext(); + const { updateItem } = useGlobalFeedContext(); + + const [textValue, setTextValue] = useState<CreatePostParams>({ + text: '', + mentioned: [], + mentionees: [ + { + type: 'user', + userIds: [''], + }, + ], + attachments: [ + { + fileId: '', + type: 'image', + }, + ], + }); + + const [postImages, setPostImages] = useState<Amity.File[]>([]); + const [postVideos, setPostVideos] = useState<Amity.File[]>([]); + + const mentionRef = useRef<HTMLDivElement | null>(null); + + const useMutateUpdatePost = () => + useMutation({ + mutationFn: async (params: Parameters<typeof PostRepository.editPost>[0]) => { + return await PostRepository.editPost(post.postId, params); + }, + onSuccess: (response) => { + AmityPostComposerPageBehavior?.goToSocialHomePage?.(); + updateItem(response.data); + }, + onError: (error) => { + console.error('Failed to create post', error); + }, + }); + + const { mutateAsync: mutateUpdatePostAsync, isPending, isError } = useMutateUpdatePost(); + + const { handleSubmit } = useForm(); + const posts = usePostByIds(post?.children || []); + + useEffect(() => { + const imagePosts = posts.filter((post) => post.dataType === 'image'); + setPostImages(imagePosts); + const videoPosts = posts.filter((post) => post.dataType === 'video'); + setPostVideos(videoPosts); + }, [posts]); + + const handleRemoveThumbnailImage = (fieldId: string) => { + setPostImages((prevImages) => + prevImages.filter((item: Amity.Post<'image'>) => item.data.fileId !== fieldId), + ); + }; + + const handleRemoveThumbnailVideo = (fieldId: string) => { + setPostVideos((prevVideos) => + prevVideos.filter((item: Amity.Post<'video'>) => item.data.videoFileId.original !== fieldId), + ); + }; + + const onSave = () => { + const attachmentsImage = postImages.map((item: Amity.Post<'image'>) => { + return { + fileId: item.data.fileId, + type: PostContentType.IMAGE, + }; + }); + + const attachmentsVideo = postVideos.map((item: Amity.Post<'video'>) => { + return { fileId: item.data.videoFileId.original, type: 'video' }; + }); + + const attachments = [...attachmentsImage, ...attachmentsVideo]; + + if (textValue) { + mutateUpdatePostAsync({ + data: { text: textValue.text }, + metadata: { mentioned: textValue.mentioned ?? [] }, + mentionees: textValue.mentionees ?? [], + attachments: attachments, + }); + } + }; + + const onChange = (val: CreatePostParams) => { + setTextValue(val); + }; + + const onClickClose = () => { + confirm({ + pageId: pageId, + type: 'confirm', + title: 'Discard this post?', + content: 'The post will be permanently deleted. It cannot be undone.', + onOk: () => { + AmityPostComposerPageBehavior?.goToSocialHomePage?.(); + }, + okText: 'Discard', + cancelText: 'Keep editing', + }); + }; + + return ( + <div className={styles.editPost} style={themeStyles}> + <form onSubmit={handleSubmit(onSave)} className={styles.editPost__formMediaAttachment}> + <div className={styles.editPost__topBar}> + <CloseButton pageId={pageId} onPress={onClickClose} /> + <EditPostTitle pageId={pageId} /> + <EditPostButton + pageId={pageId} + type="submit" + onPress={() => handleSubmit(onSave)} + isDisabled={ + !(textValue.text.length > 0 || postImages.length > 0 || postVideos.length > 0) + } + /> + </div> + <PostTextField + ref={editorRef} + onChange={onChange} + mentionContainer={mentionRef.current} + dataValue={{ + data: { text: post.data.text }, + metadata: { + mentioned: post.metadata.mentioned, + }, + mentionees: post.mentionees, + }} + /> + + <Thumbnail postMedia={postImages} onRemove={handleRemoveThumbnailImage} /> + <Thumbnail postMedia={postVideos} onRemove={handleRemoveThumbnailVideo} /> + + <div ref={mentionRef} /> + <div className={styles.editPost__notiWrap}> + {isPending && ( + <Notification + content="Posting..." + icon={<Spinner />} + className={styles.editPost__status} + /> + )} + {isError && ( + <Notification + content="Failed to create post" + icon={<ExclamationCircle className={styles.editPost_infoIcon} />} + className={styles.editPost__status} + duration={3000} + /> + )} + </div> + </form> + </div> + ); +} diff --git a/src/v4/social/internal-components/EditPost/Thumbnail.module.css b/src/v4/social/internal-components/EditPost/Thumbnail.module.css new file mode 100644 index 000000000..662b73a56 --- /dev/null +++ b/src/v4/social/internal-components/EditPost/Thumbnail.module.css @@ -0,0 +1,84 @@ +.thumbnail__container { + max-width: 100%; + display: grid; + gap: 0.5rem; + padding: 0 1rem; + width: 100%; +} + +.thumbnail__container[data-images-amount='1'] { + grid-template-columns: repeat(1, 1fr); + height: 21rem; +} + +.thumbnail__container[data-images-amount='2'] { + grid-template-columns: repeat(2, 1fr); + max-height: 21rem; +} + +.thumbnail__container[data-images-amount='3'] { + grid-template-columns: repeat(3, 1fr); +} + +.thumbnail__wrapper { + position: relative; + text-align: center; + width: 100%; + height: 21rem; +} + +.thumbnail__wrapper[data-images-height='true'] { + height: 6.8rem; +} + +.thumbnail { + border-radius: 0.25rem; + width: 100%; + height: 100%; + object-fit: cover; +} + +.thumbnail__closeButton { + position: absolute; + right: 0.5rem; + top: 0.5rem; + padding: 0.19rem; + background-color: rgb(0 0 0 / 50%); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + width: 1.5rem; + height: 1.5rem; +} + +.thumbnail__closeIcon { + fill: var(--asc-color-primary-shade4); + width: 1.25rem; + height: 1.25rem; +} + +.icon__status { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 1.75rem; + height: 1.75rem; + fill: var(--asc-color-primary-shade4); +} + +.playIcon { + position: absolute; + transform: translate(-50%, -50%); + top: 50%; + left: 50%; + width: 1.5rem; + height: 1.5rem; + padding: 0.32rem; + background-color: rgb(0 0 0 / 30%); + border-radius: 50%; + display: flex; + justify-content: center; + align-items: center; +} diff --git a/src/v4/social/internal-components/EditPost/Thumbnail.tsx b/src/v4/social/internal-components/EditPost/Thumbnail.tsx new file mode 100644 index 000000000..b3370ea38 --- /dev/null +++ b/src/v4/social/internal-components/EditPost/Thumbnail.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import styles from './Thumbnail.module.css'; +import { useImage } from '~/v4/core/hooks/useImage'; +import { CloseIcon, Play } from '~/icons'; +import { PostContentType } from '@amityco/ts-sdk'; +import { Button } from '~/v4/core/natives/Button'; + +const MediaComponent = ({ + fieldId, + onRemove, + type, +}: { + fieldId: string; + onRemove: () => void; + type: 'image' | 'video'; +}) => { + const thumbnailUrl = useImage({ fileId: fieldId }); + + if (!thumbnailUrl) return null; + return ( + <> + <img className={styles.thumbnail} src={thumbnailUrl} alt="thumbnail" /> + <Button type="reset" onPress={() => onRemove()} className={styles.thumbnail__closeButton}> + <CloseIcon className={styles.thumbnail__closeIcon} /> + </Button> + {type === PostContentType.VIDEO && ( + <div className={styles.playIcon} data-image="test"> + <Play /> + </div> + )} + </> + ); +}; + +export const Thumbnail = ({ + postMedia, + onRemove, +}: { + postMedia: Amity.Post<'image' | 'video'>[]; + onRemove: (fileId: string) => void; +}) => { + if (postMedia.length == 0) return null; + + return ( + <div data-images-amount={Math.min(postMedia.length, 3)} className={styles.thumbnail__container}> + {postMedia?.map((file, index: number) => ( + <div + key={index} + data-images-height={postMedia.length > 2} + className={styles.thumbnail__wrapper} + > + <MediaComponent + type={file.dataType} + key={index} + fieldId={ + file.dataType === PostContentType.IMAGE ? file.data.fileId : file.data.thumbnailFileId + } + onRemove={() => + onRemove( + file.dataType === PostContentType.IMAGE + ? file.data.fileId + : file.data.videoFileId.original, + ) + } + /> + </div> + ))} + </div> + ); +}; diff --git a/src/v4/social/internal-components/EditPost/index.tsx b/src/v4/social/internal-components/EditPost/index.tsx new file mode 100644 index 000000000..ce9c69907 --- /dev/null +++ b/src/v4/social/internal-components/EditPost/index.tsx @@ -0,0 +1 @@ +export { EditPost } from './EditPost'; diff --git a/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx b/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx index c8ec569f7..48c681b73 100644 --- a/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx +++ b/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx @@ -116,7 +116,7 @@ export const MentionTextInputPlugin = ({ mentionContainer, }: { communityId?: string | null; - onChangeSnap: number; + onChangeSnap?: number; mentionContainer: HTMLElement | null; }) => { return ( @@ -137,7 +137,7 @@ function Mention({ }: { anchorElement: HTMLElement | null; communityId?: string | null; - onChangeSnap: number; + onChangeSnap?: number; }) { const [editor] = useLexicalComposerContext(); diff --git a/src/v4/social/internal-components/PostMenu/PostMenu.module.css b/src/v4/social/internal-components/PostMenu/PostMenu.module.css index f57a05f4c..8939c1549 100644 --- a/src/v4/social/internal-components/PostMenu/PostMenu.module.css +++ b/src/v4/social/internal-components/PostMenu/PostMenu.module.css @@ -29,21 +29,22 @@ } .postMenu__editPost__text { - color: var(--asc-color-secondary-default); + color: var(--asc-color-base-default); + font-weight: 600; } .postMenu__editPost__icon { - fill: var(--asc-color-secondary-default); + fill: var(--asc-color-base-default); width: 1.5rem; height: 1.25rem; } .postMenu__reportPost__text { - color: var(--asc-color-secondary-default); + color: var(--asc-color-base-default); } .postMenu__reportPost__icon { - fill: var(--asc-color-secondary-default); + fill: var(--asc-color-base-default); width: 1.5rem; height: 1.25rem; } diff --git a/src/v4/social/internal-components/PostMenu/PostMenu.tsx b/src/v4/social/internal-components/PostMenu/PostMenu.tsx index ec8f584c5..034af1774 100644 --- a/src/v4/social/internal-components/PostMenu/PostMenu.tsx +++ b/src/v4/social/internal-components/PostMenu/PostMenu.tsx @@ -9,6 +9,9 @@ import { Button } from '~/v4/core/natives/Button'; import styles from './PostMenu.module.css'; import { usePostFlaggedByMe } from '~/v4/core/hooks/usePostFlaggedByMe'; import useCommunity from '~/v4/core/hooks/collections/useCommunity'; +import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; +import { Mode } from '~/v4/social/pages/PostComposerPage'; +import { useDrawer } from '~/v4/core/providers/DrawerProvider'; const PenSvg = (props: React.SVGProps<SVGSVGElement>) => ( <svg @@ -76,6 +79,8 @@ export const PostMenu = ({ }); const { isCommunityModerator, isOwner } = usePostPermissions({ post, community }); + const { AmityPostContentComponentBehavior } = usePageBehavior(); + const { removeDrawerData } = useDrawer(); const { showEditPostButton, showDeletePostButton, showReportPostButton } = useMemo(() => { if (isCommunityModerator) { @@ -157,10 +162,6 @@ export const PostMenu = ({ return ( <div className={styles.postMenu}> - {/* <button className={styles.postMenu__item} onClick={onEdit}> - <PenSvg className={styles.postMenu__editPost__icon} /> - <span>Edit post</span> - </button> */} {showReportPostButton && !isLoading ? ( <Button className={styles.postMenu__item} @@ -178,6 +179,21 @@ export const PostMenu = ({ </span> </Button> ) : null} + {showEditPostButton ? ( + <Button + className={styles.postMenu__item} + onPress={() => { + removeDrawerData(); + AmityPostContentComponentBehavior?.goToPostComposerPage?.({ + mode: Mode.EDIT, + post, + }); + }} + > + <PenSvg className={styles.postMenu__editPost__icon} /> + <span className={styles.postMenu__editPost__text}>Edit post</span> + </Button> + ) : null} {showDeletePostButton ? ( <Button className={styles.postMenu__item} onPress={() => onDeleteClick()}> <TrashSvg className={styles.postMenu__deletePost__icon} /> diff --git a/src/v4/social/internal-components/VideoThumbnail/VideoThumbnail.module.css b/src/v4/social/internal-components/VideoThumbnail/VideoThumbnail.module.css index fe946ebcc..d799bcead 100644 --- a/src/v4/social/internal-components/VideoThumbnail/VideoThumbnail.module.css +++ b/src/v4/social/internal-components/VideoThumbnail/VideoThumbnail.module.css @@ -59,3 +59,18 @@ height: 1.75rem; fill: var(--asc-color-primary-shade4); } + +.playIcon { + position: absolute; + transform: translate(-50%, -50%); + top: 50%; + left: 50%; + width: 1.5rem; + height: 1.5rem; + padding: 0.32rem; + background-color: rgb(0 0 0 / 30%); + border-radius: 50%; + display: flex; + justify-content: center; + align-items: center; +} diff --git a/src/v4/social/internal-components/VideoThumbnail/VideoThumbnail.tsx b/src/v4/social/internal-components/VideoThumbnail/VideoThumbnail.tsx index 70606d9fd..918a14915 100644 --- a/src/v4/social/internal-components/VideoThumbnail/VideoThumbnail.tsx +++ b/src/v4/social/internal-components/VideoThumbnail/VideoThumbnail.tsx @@ -2,7 +2,7 @@ import React from 'react'; import styles from './VideoThumbnail.module.css'; import useFileUpload from '~/v4/social/hooks/useFileUpload'; import clsx from 'clsx'; -import { CloseIcon, ExclamationCircle } from '~/icons'; +import { CloseIcon, ExclamationCircle, Play } from '~/icons'; import { Spinner } from '~/v4/social/internal-components/Spinner'; interface VideoThumbnailProps { @@ -78,6 +78,9 @@ export const VideoThumbnail = ({ > <CloseIcon className={styles.closeIcon} /> </button> + <div className={styles.playIcon}> + <Play /> + </div> </> )} </div> diff --git a/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx b/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx index 295f466f4..7d7b94109 100644 --- a/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx +++ b/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx @@ -1,40 +1,18 @@ -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import styles from './PostComposerPage.module.css'; -import { useAmityPage } from '~/v4/core/hooks/uikit'; -import { CloseButton } from '~/v4/social/elements/CloseButton/CloseButton'; -import { CommunityDisplayName } from '~/v4/social/elements/CommunityDisplayName'; -import { CreateNewPostButton } from '~/v4/social/elements/CreateNewPostButton'; -import { PostTextField } from '~/v4/social/elements/PostTextField'; -import { PostRepository } from '@amityco/ts-sdk'; -import { LexicalEditor } from 'lexical'; -import { useMutation } from '@tanstack/react-query'; -import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; -import { Notification } from '~/v4/core/components/Notification'; -import { Spinner } from '~/v4/social/internal-components/Spinner'; -import ExclamationCircle from '~/v4/icons/ExclamationCircle'; -import { useForm } from 'react-hook-form'; +import React from 'react'; import { Mentioned } from '~/v4/helpers/utils'; -import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; -import { Drawer } from 'vaul'; -import ReactDOM from 'react-dom'; -import { DetailedMediaAttachment } from '~/v4/social/components/DetailedMediaAttachment'; -import { ImageThumbnail } from '~/v4/social/internal-components/ImageThumbnail'; -import { MediaAttachment } from '~/v4/social/components/MediaAttachment'; -import { VideoThumbnail } from '~/v4/social/internal-components/VideoThumbnail'; -import { isMobile } from '~/v4/social/utils/isMobile'; -import { useGlobalFeedContext } from '~/v4/social/providers/GlobalFeedProvider'; -import { generateThumbnailVideo } from '~/v4/social/utils/generateThumbnailVideo'; +import { CreatePost } from '~/v4/social/internal-components/CreatePost'; +import { EditPost } from '~/v4/social/internal-components/EditPost'; export enum Mode { CREATE = 'create', EDIT = 'edit', } -interface AmityPostComposerEditOptions { +export interface AmityPostComposerEditOptions { mode: Mode.EDIT; - post?: Amity.Post; + post: Amity.Post; } -interface AmityPostComposerCreateOptions { +export interface AmityPostComposerCreateOptions { mode: Mode.CREATE; targetId: string | null; targetType: 'community' | 'user'; @@ -48,7 +26,7 @@ export interface MetaData { userId?: string; } -type PostComposerPageProps = AmityPostComposerCreateOptions | AmityPostComposerEditOptions; +export type PostComposerPageProps = AmityPostComposerCreateOptions | AmityPostComposerEditOptions; const isCreatePage = (props: PostComposerPageProps): props is AmityPostComposerCreateOptions => { return props.mode === Mode.CREATE; @@ -58,7 +36,7 @@ export function PostComposerPage(props: PostComposerPageProps) { if (isCreatePage(props)) { const { targetId, targetType, community } = props; return ( - <CreateInternal + <CreatePost mode={Mode.CREATE} targetId={targetId} targetType={targetType} @@ -66,7 +44,8 @@ export function PostComposerPage(props: PostComposerPageProps) { /> ); } else { - return null; + const { post } = props; + return <EditPost mode={Mode.EDIT} post={post} />; } } @@ -82,350 +61,3 @@ export type CreatePostParams = { type: string; }[]; }; - -const CreateInternal = ({ community, targetType, targetId }: AmityPostComposerCreateOptions) => { - const pageId = 'post_composer_page'; - const { themeStyles } = useAmityPage({ - pageId, - }); - - // const HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU = '290px'; //Including file button - const HEIGHT_MEDIA_ATTACHMENT_MENU = '6.75rem'; - const HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_1 = '8.5rem'; //Show 1 menus - const HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_2 = '11rem'; //Show 2 menus - const HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_3 = '14.5rem'; //Not including file button - - const editorRef = useRef<LexicalEditor | null>(null); - const { AmityPostComposerPageBehavior } = usePageBehavior(); - const { confirm } = useConfirmContext(); - const [snap, setSnap] = useState<number | string | null>(HEIGHT_MEDIA_ATTACHMENT_MENU); - const [isShowBottomMenu] = useState<boolean>(true); - const drawerRef = useRef<HTMLDivElement>(null); - const { prependItem } = useGlobalFeedContext(); - - const [textValue, setTextValue] = useState<CreatePostParams>({ - text: '', - mentioned: [], - mentionees: [ - { - type: 'user', - userIds: [''], - }, - ], - attachments: [ - { - fileId: '', - type: 'image', - }, - ], - }); - - //Upload media - const [postImages, setPostImages] = useState<Amity.File[]>([]); - const [postVideos, setPostVideos] = useState<Amity.File[]>([]); - - // Images/files incoming from uploads. - const [incomingImages, setIncomingImages] = useState<File[]>([]); - const [incomingVideos, setIncomingVideos] = useState<File[]>([]); - const [uploadLoading, setUploadLoading] = useState(false); - - const [uploadedImagesCount, setUploadedImagesCount] = useState<File[]>([]); - - // Visible menu attachment - const [isVisibleCamera, setIsVisibleCamera] = useState(false); - const [isVisibleImage, setIsVisibleImage] = useState(true); - const [isVisibleVideo, setIsVisibleVideo] = useState(true); - - const [isErrorUpload, setIsErrorUpload] = useState(false); - const [videoThumbnail, setVideoThumbnail] = useState< - { file: File; videoUrl: string; thumbnail: string | undefined }[] - >([]); - - const mentionRef = useRef<HTMLDivElement | null>(null); - - const useMutateCreatePost = () => - useMutation({ - mutationFn: async (params: Parameters<typeof PostRepository.createPost>[0]) => { - return PostRepository.createPost(params); - }, - onSuccess: (response) => { - AmityPostComposerPageBehavior?.goToSocialHomePage?.(); - prependItem(response.data); - }, - onError: (error) => { - console.error('Failed to create post', error); - }, - }); - - const { mutateAsync: mutateCreatePostAsync, isPending, isError } = useMutateCreatePost(); - - const { handleSubmit } = useForm(); - - const onSubmit = () => { - const attachmentsImage = postImages.map((file) => ({ - type: 'image', - fileId: file.fileId, - })); - const attachmentsVideo = postVideos.map((file) => ({ - type: 'video', - fileId: file.fileId, - })); - - const attachments = [...attachmentsImage, ...attachmentsVideo]; - - mutateCreatePostAsync({ - targetId: targetId, - targetType: targetType, - data: { text: textValue.text }, - dataType: 'text', - metadata: { mentioned: textValue.mentioned }, - mentionees: textValue.mentionees, - attachments: attachments, - }); - }; - - const onChange = (val: CreatePostParams) => { - setTextValue(val); - }; - - const handleSnapChange = (newSnap: string | number | null) => { - if (snap === HEIGHT_MEDIA_ATTACHMENT_MENU && newSnap === '0px') { - return; - } - setSnap(newSnap); - }; - - const onClickClose = () => { - confirm({ - pageId: pageId, - type: 'confirm', - title: 'Discard this post?', - content: 'The post will be permanently deleted. It cannot be undone.', - onOk: () => { - AmityPostComposerPageBehavior?.goToSocialHomePage?.(); - }, - okText: 'Discard', - cancelText: 'Keep editing', - }); - }; - - useEffect(() => { - if (postImages.length > 0) { - setIsVisibleImage(true); - setIsVisibleVideo(false); - } else if (postVideos.length > 0 && videoThumbnail.length > 0) { - setIsVisibleImage(false); - setIsVisibleVideo(true); - } else if (postImages.length == 0 || videoThumbnail.length == 0) { - setIsVisibleImage(true); - setIsVisibleVideo(true); - } - }, [postImages, postVideos, isVisibleImage, isVisibleVideo, videoThumbnail]); - - //check mobile device - useEffect(() => { - if (isMobile()) { - setIsVisibleCamera(true); - } else { - setIsVisibleCamera(false); - } - }, []); - - useEffect(() => { - if ( - (!isVisibleCamera && isVisibleImage && isVisibleVideo) || - (isVisibleCamera && isVisibleImage && !isVisibleVideo) || - (isVisibleCamera && !isVisibleImage && isVisibleVideo) - ) { - setSnap(HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_2); - } else if ( - (!isVisibleCamera && isVisibleImage && !isVisibleVideo) || - (!isVisibleCamera && !isVisibleImage && isVisibleVideo) - ) { - setSnap(HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_1); - } else { - setSnap(HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_3); - } - }, [isVisibleCamera, isVisibleImage, isVisibleVideo]); - - const handleRemoveThumbnail = (index: number) => { - setVideoThumbnail((prev) => prev.filter((_, i) => i !== index)); - }; - - const handleImageFileChange = (file: File[]) => { - if (file.length + uploadedImagesCount.length > 10) { - confirm({ - pageId: pageId, - type: 'info', - title: 'Maximum upload limit reached', - content: - 'You’ve reached the upload limit of 10 images. Any additional images will not be saved. ', - okText: 'Close', - }); - return; - } - - if (file.length > 0) { - setUploadedImagesCount((prevImages) => [...prevImages, ...file]); - setIncomingImages(file); - } - }; - - const handleVideoFileChange = async (file: File[]) => { - const existingVideosCount = videoThumbnail ? videoThumbnail.length : 0; - - if (file.length + existingVideosCount > 10) { - confirm({ - pageId: pageId, - type: 'info', - title: 'Maximum upload limit reached', - content: - 'You’ve reached the upload limit of 10 videos. Any additional videos will not be saved. ', - okText: 'Close', - }); - return; - } - - if (file.length > 0) { - setIncomingVideos?.(file); - const updatedVideos = file.map((file) => ({ - file, - videoUrl: URL.createObjectURL(file), - thumbnail: undefined, - })); - - const thumbnailVideo = await Promise.all( - updatedVideos.map(async (video) => { - const thumbnail = await generateThumbnailVideo(video.file); - return { - ...video, - thumbnail: thumbnail, - }; - }), - ); - setVideoThumbnail((prev) => [...prev, ...thumbnailVideo]); - } - }; - - return ( - <div className={styles.postComposerPage} style={themeStyles}> - <form - onSubmit={handleSubmit(onSubmit)} - data-from-media={snap == HEIGHT_MEDIA_ATTACHMENT_MENU} - className={styles.postComposerPage__formMediaAttachment} - > - <div className={styles.postComposerPage__topBar}> - <CloseButton pageId={pageId} onPress={onClickClose} /> - <CommunityDisplayName pageId={pageId} community={community} /> - <CreateNewPostButton - pageId={pageId} - onSubmit={handleSubmit(onSubmit)} - isValid={textValue.text.length > 0 || postImages.length > 0 || postVideos.length > 0} - /> - </div> - <PostTextField - ref={editorRef} - onChange={onChange} - communityId={targetId} - onChangeSnap={ - snap == HEIGHT_MEDIA_ATTACHMENT_MENU - ? 0 - : snap == HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_3 - ? 2 - : 1 - } - mentionContainer={mentionRef.current} - /> - <ImageThumbnail - files={incomingImages} - uploadedFiles={postImages} - uploadLoading={uploadLoading} - onLoadingChange={setUploadLoading} - onChange={({ uploaded, uploading }) => { - setPostImages(uploaded); - setIncomingImages(uploading); - }} - onError={setIsErrorUpload} - isErrorUpload={isErrorUpload} - /> - <div ref={mentionRef} /> - <VideoThumbnail - files={incomingVideos} - uploadedFiles={postVideos} - uploadLoading={uploadLoading} - onLoadingChange={setUploadLoading} - onChange={({ uploaded, uploading }) => { - setPostVideos(uploaded); - setIncomingVideos(uploading); - }} - onError={setIsErrorUpload} - isErrorUpload={isErrorUpload} - videoThumbnail={videoThumbnail} - removeThumbnail={handleRemoveThumbnail} - /> - <div ref={mentionRef} /> - </form> - <div ref={drawerRef}></div> - {drawerRef.current - ? ReactDOM.createPortal( - <Drawer.Root - snapPoints={[ - HEIGHT_MEDIA_ATTACHMENT_MENU, - HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_1, - HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_2, - HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_3, - ]} - activeSnapPoint={snap} - setActiveSnapPoint={handleSnapChange} - open={isShowBottomMenu} - modal={false} - > - <Drawer.Portal container={drawerRef.current}> - <Drawer.Content className={styles.drawer__content}> - <div className={styles.postComposerPage__notiWrap}> - {isPending && ( - <Notification - content="Posting..." - icon={<Spinner />} - className={styles.postComposerPage__status} - /> - )} - {isError && ( - <Notification - content="Failed to create post" - icon={<ExclamationCircle className={styles.selectPostTargetPag_infoIcon} />} - className={styles.postComposerPage__status} - duration={3000} - /> - )} - </div> - {snap == HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_1 || - snap == HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_2 || - snap == HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_3 ? ( - <DetailedMediaAttachment - pageId={pageId} - isVisibleCamera={isVisibleCamera} - isVisibleImage={isVisibleImage} - isVisibleVideo={isVisibleVideo} - onVideoFileChange={handleVideoFileChange} - onImageFileChange={handleImageFileChange} - /> - ) : ( - <MediaAttachment - pageId={pageId} - isVisibleCamera={isVisibleCamera} - isVisibleImage={isVisibleImage} - isVisibleVideo={isVisibleVideo} - onVideoFileChange={handleVideoFileChange} - onImageFileChange={handleImageFileChange} - /> - )} - </Drawer.Content> - </Drawer.Portal> - </Drawer.Root>, - drawerRef.current, - ) - : null} - </div> - ); -}; diff --git a/src/v4/social/providers/GlobalFeedProvider.tsx b/src/v4/social/providers/GlobalFeedProvider.tsx index 0fc3674ac..a27284351 100644 --- a/src/v4/social/providers/GlobalFeedProvider.tsx +++ b/src/v4/social/providers/GlobalFeedProvider.tsx @@ -1,6 +1,5 @@ import React, { createContext, useContext, useState, useMemo } from 'react'; import { FeedRepository, PostRepository } from '@amityco/ts-sdk'; - import { usePaginatorApi } from '~/v4/core/hooks/usePaginator'; import { isNonNullable } from '~/v4/helpers/utils'; @@ -67,7 +66,13 @@ const useGlobalFeed = () => { } const prependItem = (post: Amity.Post) => { - setItems((prevItems) => [post, ...prevItems]); + setItems((prevItems) => { + const currentItemIds = new Set([...prevItems.map((item) => item.postId)]); + if (!currentItemIds.has(post.postId)) { + return [post, ...prevItems]; + } + return prevItems; + }); }; const removeItem = (postId: string) => { @@ -75,6 +80,16 @@ const useGlobalFeed = () => { setItems(newItems); }; + const updateItem = (post: Amity.Post) => { + const newItems = items.map((item) => { + if (item.postId === post.postId) { + return { ...item, ...post }; + } + return item; + }); + setItems(newItems); + }; + const hasMore = useMemo(() => queryToken !== null, [queryToken]); const loadMore = () => { @@ -103,6 +118,7 @@ const useGlobalFeed = () => { isLoading, prependItem, removeItem, + updateItem, loadMore, hasMore, loadMoreHasBeenCalled, @@ -120,6 +136,7 @@ const GlobalFeedContext = createContext<GlobalFeedContextType>({ isLoading: false, prependItem: () => {}, removeItem: () => {}, + updateItem: () => {}, loadMore: () => {}, hasMore: false, loadMoreHasBeenCalled: false, diff --git a/src/v4/social/utils/textToEditorState.ts b/src/v4/social/utils/textToEditorState.ts new file mode 100644 index 000000000..93c3b3740 --- /dev/null +++ b/src/v4/social/utils/textToEditorState.ts @@ -0,0 +1,110 @@ +import { SerializedParagraphNode, SerializedRootNode, SerializedTextNode } from 'lexical'; +import { Mentioned, Mentionees } from '~/v4/helpers/utils'; +import { SerializedMentionNode } from '~/v4/social/internal-components/MentionTextInput/MentionNodes'; + +function createRootNode(): SerializedRootNode<SerializedParagraphNode> { + return { + children: [], + direction: 'ltr', + format: '', + indent: 0, + type: 'root', + version: 1, + }; +} +function createParagraphNode(): SerializedParagraphNode { + return { + children: [], + direction: 'ltr', + format: '', + indent: 0, + type: 'paragraph', + version: 1, + textFormat: 0, + }; +} + +function createSerializeTextNode(text: string): SerializedTextNode { + return { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text, + type: 'text', + version: 1, + }; +} + +function createSerializeMentionNode(mention: Mentioned): SerializedMentionNode { + return { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: ('@' + mention.userId) as string, + type: 'mention', + version: 1, + mentionName: mention.userId as string, + displayName: mention.userId as string, + userId: mention.userId as string, + userInternalId: mention.userId as string, + userPublicId: mention.userId as string, + }; +} + +export function textToEditorState(value: { + data: { text: string }; + metadata?: { + mentioned?: Mentioned[]; + }; + mentionees?: Mentionees; +}) { + const rootNode = createRootNode(); + const textArray = value.data.text.split('\n'); + const mentions = value.metadata?.mentioned || []; + let mentionIndex = 0; + let globalIndex = 0; + + for (let i = 0; i < textArray.length; i++) { + const paragraph = createParagraphNode(); + let runningIndex = 0; + + while (runningIndex < textArray[i].length) { + if (mentionIndex < mentions.length && mentions[mentionIndex].index === globalIndex) { + paragraph.children.push(createSerializeMentionNode(mentions[mentionIndex])); + runningIndex += mentions[mentionIndex].length; + globalIndex += mentions[mentionIndex].length; + mentionIndex++; + } else { + const nextMentionIndex = + mentionIndex < mentions.length + ? mentions[mentionIndex].index + : globalIndex + textArray[i].length; + const textSegment = textArray[i].slice( + runningIndex, + nextMentionIndex - globalIndex + runningIndex, + ); + if (textSegment) { + paragraph.children.push(createSerializeTextNode(textSegment)); + } + runningIndex += textSegment.length; + globalIndex += textSegment.length; + } + } + + if (runningIndex < textArray[i].length) { + const textSegment = textArray[i].slice(runningIndex); + if (textSegment) { + paragraph.children.push(createSerializeTextNode(textSegment)); + } + globalIndex += textSegment.length; + } + + globalIndex += 1; + + rootNode.children.push(paragraph); + } + + return { root: rootNode }; +} From abf6f137c9b3284d7bba9e327177f4902f955bce Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Thu, 1 Aug 2024 18:15:49 +0700 Subject: [PATCH 268/300] feat: ASC-24665 - check labels ci (#558) --- .github/workflows/check_do_not_merge.yaml | 21 +++++++++++ .github/workflows/check_release_label.yaml | 42 ++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 .github/workflows/check_do_not_merge.yaml create mode 100644 .github/workflows/check_release_label.yaml diff --git a/.github/workflows/check_do_not_merge.yaml b/.github/workflows/check_do_not_merge.yaml new file mode 100644 index 000000000..0f1d237b4 --- /dev/null +++ b/.github/workflows/check_do_not_merge.yaml @@ -0,0 +1,21 @@ +name: Check do not merge + +on: + pull_request: + types: + - opened + - reopened + - synchronize + - edited + - labeled + - unlabeled + +jobs: + ok-to-merge: + if: contains(github.event.pull_request.labels.*.name, 'DO NOT MERGE') == false + runs-on: ubuntu-latest + steps: + - name: This PR is not labeled with do not merge + run: | + echo "This PR can be merged" + exit 0 diff --git a/.github/workflows/check_release_label.yaml b/.github/workflows/check_release_label.yaml new file mode 100644 index 000000000..e742da83e --- /dev/null +++ b/.github/workflows/check_release_label.yaml @@ -0,0 +1,42 @@ +name: Check release label + +on: + pull_request: + types: + - opened + - reopened + - synchronize + - edited + - labeled + - unlabeled + +jobs: + it-has-a-release-label: + runs-on: ubuntu-latest + steps: + - name: Check for Release label + id: check_release_label + run: | + echo "Debugging: Contents of GITHUB_EVENT_PATH" + cat "$GITHUB_EVENT_PATH" + + echo "Debugging: Extracting labels" + labels=$(jq -r '.pull_request.labels[].name' "$GITHUB_EVENT_PATH") + echo "Extracted labels: $labels" + + echo "Debugging: Checking for Release label" + if echo "$labels" | grep -q "^Release/"; then + echo "Release label found" + echo "has_release_label=true" >> $GITHUB_OUTPUT + else + echo "No Release label found" + echo "has_release_label=false" >> $GITHUB_OUTPUT + fi + + echo "Debugging: Final GITHUB_OUTPUT" + cat $GITHUB_OUTPUT + - name: Fail if PR does not have a release label + if: steps.check_release_label.outputs.has_release_label == 'false' + run: | + echo "This PR must have a release label." + exit 1 From 4edd8edbd757ead16ca4c34bb5b1b8f2ac8c11c2 Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Mon, 5 Aug 2024 18:40:31 +0700 Subject: [PATCH 269/300] feat: ASC-24666 - create pull_request_template.md (#559) * Create pull_request_template.md Create pull request template * Update pull_request_template.md Add checklist --- .github/pull_request_template.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..86c3ef931 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,18 @@ +**Jira ticket :** + +- + +**Description :** + +- + +**Check lists :** + +- [ ] Test code +- [ ] Build local pass (optional) +- [ ] Code is the same level as origin/develop branch + +**Screen shot :** + + +**Note (optional) :** From 1136d0597513e0d482b0ef4fa4de874c351d5e02 Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Tue, 6 Aug 2024 13:31:34 +0700 Subject: [PATCH 270/300] feat: ASC-23982 - post impression (#561) * feat: add post impression * refactor: remove log * refactor: changed to hooks * refactor: hooks * refactor: ref div --- .../components/PostContent/PostContent.tsx | 28 ++++++++++----- src/v4/social/hooks/useVisibilitySensor.tsx | 34 +++++++++++++++++++ 2 files changed, 53 insertions(+), 9 deletions(-) create mode 100644 src/v4/social/hooks/useVisibilitySensor.tsx diff --git a/src/v4/social/components/PostContent/PostContent.tsx b/src/v4/social/components/PostContent/PostContent.tsx index 3b4adb1a4..ae26c6247 100644 --- a/src/v4/social/components/PostContent/PostContent.tsx +++ b/src/v4/social/components/PostContent/PostContent.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; import { Timestamp } from '~/v4/social/elements/Timestamp'; import { ReactionButton } from '~/v4/social/elements/ReactionButton'; @@ -35,8 +35,9 @@ import { ReactionList } from '~/v4/social/components/ReactionList/ReactionList'; import { usePostedUserInformation } from '~/v4/core/hooks/usePostedUserInformation'; import millify from 'millify'; import { Button } from '~/v4/core/natives/Button'; -import { useNavigation } from '~/v4/core/providers/NavigationProvider'; +import { PageTypes, useNavigation } from '~/v4/core/providers/NavigationProvider'; import dayjs from 'dayjs'; +import { useVisibilitySensor } from '~/v4/social/hooks/useVisibilitySensor'; interface PostTitleProps { post: Amity.Post; @@ -183,6 +184,9 @@ export const PostContent = ({ const [reactionByMe, setReactionByMe] = useState<string | null>(null); const [reactionsCount, setReactionsCount] = useState<number>(0); + const { page } = useNavigation(); + + const elementRef = useRef<HTMLDivElement>(null); const post = useMemo(() => { if (initialPost != null && postData != null) { @@ -205,12 +209,6 @@ export const PostContent = ({ shouldSubscribe: shouldSubscribe, }); - useEffect(() => { - if (post) { - post.analytics?.markAsViewed(); - } - }, [post]); - const shouldCall = useMemo(() => post?.targetType === 'community', [post?.targetType]); const { community: targetCommunity } = useCommunity({ @@ -298,8 +296,20 @@ export const PostContent = ({ const hasReaction = hasLike || hasLove || hasFire || hasHappy || hasCrying; + const { isVisible } = useVisibilitySensor({ + threshold: 0.6, + elementRef, + }); + + useEffect(() => { + if (page.type === PageTypes.PostDetailPage) return; + if (isVisible) { + post.analytics?.markAsViewed(); + } + }, [post, isVisible, page.type]); + return ( - <div className={styles.postContent} style={themeStyles}> + <div ref={elementRef} className={styles.postContent} style={themeStyles}> <div className={styles.postContent__bar} data-type={type}> <div className={styles.postContent__bar__userAvatar}> <UserAvatar userId={post?.postedUserId} /> diff --git a/src/v4/social/hooks/useVisibilitySensor.tsx b/src/v4/social/hooks/useVisibilitySensor.tsx new file mode 100644 index 000000000..14243139c --- /dev/null +++ b/src/v4/social/hooks/useVisibilitySensor.tsx @@ -0,0 +1,34 @@ +import { RefObject, useEffect, useState } from 'react'; + +interface UseVisibilitySensorProps { + threshold: number; + elementRef: RefObject<HTMLDivElement>; +} + +export const useVisibilitySensor = ({ threshold, elementRef }: UseVisibilitySensorProps) => { + const [isVisible, setIsVisible] = useState(false); + + useEffect(() => { + if (!elementRef.current) return; + + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + const isIntersecting = entry.isIntersecting; + setIsVisible(isIntersecting); + }); + }, + { + threshold: threshold, + }, + ); + + observer.observe(elementRef.current); + + return () => { + observer.disconnect(); + }; + }, [threshold, elementRef]); + + return { isVisible }; +}; From 568afd1b32561e35d79a62e5c9c5bc9678c2c06e Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Wed, 7 Aug 2024 11:23:23 +0700 Subject: [PATCH 271/300] fix: undefined metadata (#564) --- src/v4/social/internal-components/EditPost/EditPost.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v4/social/internal-components/EditPost/EditPost.tsx b/src/v4/social/internal-components/EditPost/EditPost.tsx index e4302a0e2..af8562c2f 100644 --- a/src/v4/social/internal-components/EditPost/EditPost.tsx +++ b/src/v4/social/internal-components/EditPost/EditPost.tsx @@ -157,7 +157,7 @@ export function EditPost({ post }: AmityPostComposerEditOptions) { dataValue={{ data: { text: post.data.text }, metadata: { - mentioned: post.metadata.mentioned, + mentioned: post.metadata?.mentioned || [], }, mentionees: post.mentionees, }} From f30d7ae992f76e04cc223801fd904ed3ebece4f1 Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Thu, 8 Aug 2024 14:56:42 +0700 Subject: [PATCH 272/300] fix: ASC-24780 - handle no change edit post (#562) * fix: handle no change edit * fix: remove img change save * fix: compare array function * fix: type params Co-authored-by: Bonn <pittawat@amity.co> * fix: error text --------- Co-authored-by: Bonn <pittawat@amity.co> --- .../social/internal-components/EditPost/EditPost.tsx | 10 ++++++++++ src/v4/social/utils/arraysContainSameElements.ts | 9 +++++++++ 2 files changed, 19 insertions(+) create mode 100644 src/v4/social/utils/arraysContainSameElements.ts diff --git a/src/v4/social/internal-components/EditPost/EditPost.tsx b/src/v4/social/internal-components/EditPost/EditPost.tsx index af8562c2f..ea7393cc7 100644 --- a/src/v4/social/internal-components/EditPost/EditPost.tsx +++ b/src/v4/social/internal-components/EditPost/EditPost.tsx @@ -21,6 +21,7 @@ import usePostByIds from '~/v4/core/hooks/usePostByIds'; import { ExclamationCircle } from '~/icons'; import { Thumbnail } from './Thumbnail'; import { useGlobalFeedContext } from '~/v4/social/providers/GlobalFeedProvider'; +import { arraysContainSameElements } from '~/v4/social/utils/arraysContainSameElements'; export function EditPost({ post }: AmityPostComposerEditOptions) { const pageId = 'post_composer_page'; @@ -53,6 +54,9 @@ export function EditPost({ post }: AmityPostComposerEditOptions) { const [postImages, setPostImages] = useState<Amity.File[]>([]); const [postVideos, setPostVideos] = useState<Amity.File[]>([]); + const [defaultPostImages, setDefaultPostImages] = useState<Amity.File[]>([]); + const [defaultPostVideo, setDefaultPostVideo] = useState<Amity.File[]>([]); + const mentionRef = useRef<HTMLDivElement | null>(null); const useMutateUpdatePost = () => @@ -76,8 +80,10 @@ export function EditPost({ post }: AmityPostComposerEditOptions) { useEffect(() => { const imagePosts = posts.filter((post) => post.dataType === 'image'); + setDefaultPostImages(imagePosts); setPostImages(imagePosts); const videoPosts = posts.filter((post) => post.dataType === 'video'); + setDefaultPostVideo(videoPosts); setPostVideos(videoPosts); }, [posts]); @@ -135,6 +141,9 @@ export function EditPost({ post }: AmityPostComposerEditOptions) { }); }; + const isImageChanged = !arraysContainSameElements(defaultPostImages, postImages); + const isVideoChanged = !arraysContainSameElements(defaultPostVideo, postVideos); + return ( <div className={styles.editPost} style={themeStyles}> <form onSubmit={handleSubmit(onSave)} className={styles.editPost__formMediaAttachment}> @@ -146,6 +155,7 @@ export function EditPost({ post }: AmityPostComposerEditOptions) { type="submit" onPress={() => handleSubmit(onSave)} isDisabled={ + (post.data.text == textValue.text && !isImageChanged && !isVideoChanged) || !(textValue.text.length > 0 || postImages.length > 0 || postVideos.length > 0) } /> diff --git a/src/v4/social/utils/arraysContainSameElements.ts b/src/v4/social/utils/arraysContainSameElements.ts new file mode 100644 index 000000000..b748f0de1 --- /dev/null +++ b/src/v4/social/utils/arraysContainSameElements.ts @@ -0,0 +1,9 @@ +export const arraysContainSameElements = <T>(arr1: T[], arr2: T[]): boolean => { + if (arr1.length !== arr2.length) return false; + const sortedArr1 = [...arr1].sort(); + const sortedArr2 = [...arr2].sort(); + for (let i = 0; i < sortedArr1.length; i++) { + if (sortedArr1[i] !== sortedArr2[i]) return false; + } + return true; +}; From 4853c2f52f17a6078982ed9bdc051e195abe72bb Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Thu, 8 Aug 2024 14:58:07 +0700 Subject: [PATCH 273/300] fix: ASC-24778 - add edit tag (#563) * fix: edit tag * style: reduce spacing --- src/v4/social/components/PostContent/PostContent.module.css | 5 +++++ src/v4/social/components/PostContent/PostContent.tsx | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/v4/social/components/PostContent/PostContent.module.css b/src/v4/social/components/PostContent/PostContent.module.css index 34380c8f7..5cabd922b 100644 --- a/src/v4/social/components/PostContent/PostContent.module.css +++ b/src/v4/social/components/PostContent/PostContent.module.css @@ -163,3 +163,8 @@ .postContent__reactionsBar__reactions__count { color: var(--asc-color-base-shade2); } + +.postContent__bar__information__editedTag { + margin-left: 0.125rem; + color: var(--asc-color-base-shade2); +} diff --git a/src/v4/social/components/PostContent/PostContent.tsx b/src/v4/social/components/PostContent/PostContent.tsx index ae26c6247..3a5bcac3e 100644 --- a/src/v4/social/components/PostContent/PostContent.tsx +++ b/src/v4/social/components/PostContent/PostContent.tsx @@ -326,6 +326,11 @@ export const PostContent = ({ </div> ) : null} <Timestamp timestamp={post.createdAt} /> + {post.createdAt !== post.editedAt && ( + <Typography.Caption className={styles.postContent__bar__information__editedTag}> + (edited) + </Typography.Caption> + )} </div> </div> <div className={styles.postContent__bar__actionButton}> From 8e5faf1371aaff5f6eab7539d85652a13d068b89 Mon Sep 17 00:00:00 2001 From: "Pitchaya T." <33589608+ptchayap@users.noreply.github.com> Date: Fri, 9 Aug 2024 15:06:02 +0700 Subject: [PATCH 274/300] fix: add getAuthToken param (#567) --- src/v4/core/AmityUIKitManager.ts | 12 ++++++++++-- src/v4/core/providers/AmityUIKitProvider.tsx | 19 ++++++++++++++----- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/v4/core/AmityUIKitManager.ts b/src/v4/core/AmityUIKitManager.ts index 760a506e8..af7c82cbb 100644 --- a/src/v4/core/AmityUIKitManager.ts +++ b/src/v4/core/AmityUIKitManager.ts @@ -58,6 +58,7 @@ export class AmityUIKitManager { * @param userId - The user ID to be used for login. * @param displayName - The display name of the user. * @param sessionHandler - The session handler for access token renewal. + * @param authToken - The authentication token to be used for login. * @param onConnectionStatusChange - The callback function for connection status changes. * @param onConnected - The callback function to be called when connected. * @param onDisconnected - The callback function to be called when disconnected. @@ -66,6 +67,7 @@ export class AmityUIKitManager { userId: string, displayName: string, sessionHandler: SessionHandler, + authToken?: string, onConnectionStatusChange?: (state: Amity.SessionStates) => void, onConnected?: () => void, onDisconnected?: () => void, @@ -78,7 +80,12 @@ export class AmityUIKitManager { AmityUIKitManager.instance.onConnected = onConnected; AmityUIKitManager.instance.onDisconnected = onDisconnected; - await AmityUIKitManager.instance.connectAndLogin(userId, displayName, sessionHandler); + await AmityUIKitManager.instance.connectAndLogin( + userId, + displayName, + sessionHandler, + authToken, + ); } /** @@ -105,9 +112,10 @@ export class AmityUIKitManager { userId: string, displayName: string, sessionHandler: SessionHandler, + authToken?: string, ): Promise<void> { await ASCClient.login( - { userId, displayName }, + { userId, displayName, authToken }, { sessionWillRenewAccessToken: sessionHandler.sessionWillRenewAccessToken.bind(sessionHandler), diff --git a/src/v4/core/providers/AmityUIKitProvider.tsx b/src/v4/core/providers/AmityUIKitProvider.tsx index 795b15bc1..4fb254718 100644 --- a/src/v4/core/providers/AmityUIKitProvider.tsx +++ b/src/v4/core/providers/AmityUIKitProvider.tsx @@ -47,7 +47,6 @@ interface AmityUIKitProviderProps { http?: string; mqtt?: string; }; - authToken?: string; userId: string; displayName: string; postRendererConfig?: any; @@ -68,6 +67,7 @@ interface AmityUIKitProviderProps { onConnectionStatusChange?: (state: Amity.SessionStates) => void; onConnected?: () => void; onDisconnected?: () => void; + getAuthToken?: () => Promise<string>; configs?: AmityUIKitConfig; } @@ -75,7 +75,6 @@ const AmityUIKitProvider: React.FC<AmityUIKitProviderProps> = ({ apiKey, apiRegion, apiEndpoint, - authToken, userId, displayName, postRendererConfig, @@ -85,6 +84,7 @@ const AmityUIKitProvider: React.FC<AmityUIKitProviderProps> = ({ pageBehavior, onConnectionStatusChange, onDisconnected, + getAuthToken, configs, }) => { const queryClient = new QueryClient(); @@ -102,6 +102,12 @@ const AmityUIKitProvider: React.FC<AmityUIKitProviderProps> = ({ useEffect(() => { const setup = async () => { + let authToken; + + if (getAuthToken) { + authToken = await getAuthToken(); + } + try { // Set up the AmityUIKitManager AmityUIKitManager.setup({ apiKey, apiRegion, apiEndpoint }); @@ -115,13 +121,16 @@ const AmityUIKitProvider: React.FC<AmityUIKitProviderProps> = ({ { sessionWillRenewAccessToken: (renewal) => { // Handle access token renewal - if (authToken) { - renewal.renewWithAuthToken(authToken); + if (getAuthToken) { + getAuthToken().then((newToken) => { + renewal.renewWithAuthToken(newToken); + }); } else { renewal.renew(); } }, }, + authToken, onConnectionStatusChange, onDisconnected, ); @@ -134,7 +143,7 @@ const AmityUIKitProvider: React.FC<AmityUIKitProviderProps> = ({ }; setup(); - }, [userId, displayName, authToken, onConnectionStatusChange, onDisconnected]); + }, [userId, displayName, onConnectionStatusChange, onDisconnected]); if (!client) return null; From 16c0ac857999b0e2a3d8a268929e72a93b67934a Mon Sep 17 00:00:00 2001 From: "Pitchaya T." <33589608+ptchayap@users.noreply.github.com> Date: Fri, 9 Aug 2024 17:17:49 +0700 Subject: [PATCH 275/300] fix: ASC-24831 - profile blinking (#569) * chore: v3.6.0 * fix: remove expose v4 component * chore: v3.6.0 * fix: remove expose v4 component * chore(release): 3.7.0 * chore: update pnpm-lock.yaml * chore(release): 3.8.0 * chore: update pnpmplock * fix: ASC-24831 - profile blinking * fix: ASC-24831 - profile blinking * fix: avoid channel object change affect to avatarUrl from calling reading api while reading the channel * fix: button size * fix: lint --------- Co-authored-by: bmo-amity-bot <developers@amity.co> Co-authored-by: Bonn <pittawat@amity.co> --- src/chat/components/Chat/index.tsx | 18 +------ src/chat/components/Message/styles.tsx | 20 ++++---- src/chat/components/RecentChat/index.tsx | 9 ++++ src/chat/hooks/useChatInfo.ts | 56 +++++++++++++++------- src/core/providers/UiKitProvider/index.tsx | 31 +++++++----- 5 files changed, 77 insertions(+), 57 deletions(-) diff --git a/src/chat/components/Chat/index.tsx b/src/chat/components/Chat/index.tsx index f3258bab7..917502530 100644 --- a/src/chat/components/Chat/index.tsx +++ b/src/chat/components/Chat/index.tsx @@ -13,8 +13,6 @@ import ChatHeader from '~/chat/components/ChatHeader'; import { ChannelContainer } from './styles'; import { useCustomComponent } from '~/core/providers/CustomComponentsProvider'; -import useChannel from '~/chat/hooks/useChannel'; -import useChannelMembers from '~/chat/hooks/useChannelMembers'; interface ChatProps { channelId: string; @@ -23,23 +21,11 @@ interface ChatProps { } const Chat = ({ channelId, onChatDetailsClick, shouldShowChatDetails }: ChatProps) => { - const channel = useChannel(channelId); useEffect(() => { - async function run() { - if (channel == null) return; - - if (channel.type !== 'conversation') { - await ChannelRepository.joinChannel(channel?.channelId); - } - - await SubChannelRepository.startReading(channel?.channelId); - } - run(); return () => { - if (channel == null) return; - SubChannelRepository.stopReading(channel?.channelId); + SubChannelRepository.stopReading(channelId); }; - }, [channel]); + }, [channelId]); const sendMessage = async (text: string) => { return MessageRepository.createMessage({ diff --git a/src/chat/components/Message/styles.tsx b/src/chat/components/Message/styles.tsx index b5a5dfff7..771e01b0b 100644 --- a/src/chat/components/Message/styles.tsx +++ b/src/chat/components/Message/styles.tsx @@ -7,6 +7,8 @@ import { Close, EllipsisV, Save, TrashIcon } from '~/icons'; export const EditingContainer = styled.div` display: flex; align-items: center; + gap: 10px; + padding: 0 10px; `; export const EditingInput = styled.input` @@ -18,21 +20,19 @@ export const EditingInput = styled.input` border-radius: 4px; `; -export const SaveIcon = styled(Save)<{ icon?: ReactNode }>` - opacity: 0.7; - padding: 0 10px; - cursor: pointer; -`; - -export const DeleteIcon = styled(TrashIcon)` +export const SaveIcon = styled(Save).attrs<{ icon?: ReactNode }>({ + width: 14, + height: 14, +})` opacity: 0.7; - padding: 0 10px; cursor: pointer; `; -export const CloseIcon = styled(Close)<{ icon?: ReactNode }>` +export const CloseIcon = styled(Close).attrs<{ icon?: ReactNode }>({ + width: 14, + height: 14, +})` opacity: 0.7; - padding: 0 10px; cursor: pointer; `; diff --git a/src/chat/components/RecentChat/index.tsx b/src/chat/components/RecentChat/index.tsx index 3621e19a0..c567a3c16 100644 --- a/src/chat/components/RecentChat/index.tsx +++ b/src/chat/components/RecentChat/index.tsx @@ -13,6 +13,7 @@ import { } from './styles'; import { useCustomComponent } from '~/core/providers/CustomComponentsProvider'; import useChannelsCollection from '~/chat/hooks/collections/useChannelsCollection'; +import { ChannelRepository, SubChannelRepository } from '@amityco/ts-sdk'; interface RecentChatProps { onChannelSelect?: (data: { channelId: string; type: string }) => void; @@ -34,6 +35,13 @@ const RecentChat = ({ }); const containerRef = useRef<HTMLDivElement | null>(null); + const onClickChannel = async ({ channelId, type }: { channelId: string; type: string }) => { + if (type !== 'conversation') { + await ChannelRepository.joinChannel(channelId); + } + await SubChannelRepository.startReading(channelId); + }; + return ( <RecentContainer> <RecentHeader> @@ -66,6 +74,7 @@ const RecentChat = ({ channelId={channel.channelId} isSelected={selectedChannelId === channel.channelId} onSelect={(data) => { + onClickChannel(data); onChannelSelect?.(data); }} /> diff --git a/src/chat/hooks/useChatInfo.ts b/src/chat/hooks/useChatInfo.ts index 270444724..c241c9c8c 100644 --- a/src/chat/hooks/useChatInfo.ts +++ b/src/chat/hooks/useChatInfo.ts @@ -53,23 +53,33 @@ async function getAvatarUrl({ return null; } -async function getChatAvatar( - channel?: Amity.Channel | null, - otherUser?: { avatarUrl?: string } | null, - currentUser?: { avatarUrl?: string } | null, -) { +async function getChatAvatar({ + channelAvatarFileId, + channelAvatarCustomUrl, + otherUserAvatarUrl, + currentUserAvatarUrl, + isDirectChat, + memberCount = 0, +}: { + channelAvatarFileId?: string; + channelAvatarCustomUrl?: string; + otherUserAvatarUrl?: string; + currentUserAvatarUrl?: string; + isDirectChat?: boolean; + memberCount?: number; +}) { // It is direct chat but only one user - show current user instead - if (channel?.memberCount === MEMBER_COUNT_PER_CASE.ONLY_ME_CHAT) { - return getAvatarUrl({ avatarCustomUrl: currentUser?.avatarUrl }); + if (memberCount === MEMBER_COUNT_PER_CASE.ONLY_ME_CHAT) { + return getAvatarUrl({ avatarCustomUrl: currentUserAvatarUrl }); } - if (channel?.metadata?.isDirectChat && otherUser?.avatarUrl) { - return getAvatarUrl({ avatarCustomUrl: otherUser.avatarUrl }); + if (isDirectChat && otherUserAvatarUrl) { + return getAvatarUrl({ avatarCustomUrl: otherUserAvatarUrl }); } return getAvatarUrl({ - avatarFileId: channel?.avatarFileId, - avatarCustomUrl: channel?.metadata?.avatarCustomUrl, + avatarFileId: channelAvatarFileId, + avatarCustomUrl: channelAvatarCustomUrl, }); } @@ -89,17 +99,27 @@ function useChatInfo({ channel }: { channel: Amity.Channel | null }) { useEffect(() => { async function run() { - setChatAvatar(null); - const url = await getChatAvatar( - channel, - { avatarUrl: otherUser?.avatarCustomUrl || otherUserAvatarUrl }, - { avatarUrl: currentUser?.avatarCustomUrl || currentUserAvatarUrl }, - ); + const url = await getChatAvatar({ + channelAvatarFileId: channel?.avatarFileId, + channelAvatarCustomUrl: channel?.metadata?.avatarCustomUrl, + otherUserAvatarUrl: otherUser?.avatarCustomUrl || otherUserAvatarUrl, + currentUserAvatarUrl: currentUser?.avatarCustomUrl || currentUserAvatarUrl, + isDirectChat: channel?.metadata?.isDirectChat, + memberCount: channel?.memberCount, + }); setChatAvatar(url); } run(); - }, [otherUser?.avatarCustomUrl, channel, currentUserAvatarUrl, currentUser?.avatarCustomUrl]); + }, [ + otherUser?.avatarCustomUrl, + channel?.metadata?.isDirectChat, + channel?.memberCount, + channel?.avatarFileId, + channel?.metadata?.avatarCustomUrl, + currentUserAvatarUrl, + currentUser?.avatarCustomUrl, + ]); const chatName = useMemo(() => { if (channel == null) return; diff --git a/src/core/providers/UiKitProvider/index.tsx b/src/core/providers/UiKitProvider/index.tsx index 7b050bdfd..00bcb24b6 100644 --- a/src/core/providers/UiKitProvider/index.tsx +++ b/src/core/providers/UiKitProvider/index.tsx @@ -36,7 +36,6 @@ interface UiKitProviderProps { http?: string; mqtt?: string; }; - authToken?: string; userId: string; displayName: string; customComponents?: CustomComponentType; @@ -59,13 +58,13 @@ interface UiKitProviderProps { onConnected?: () => void; onDisconnected?: () => void; pageBehavior?: Record<string, unknown>; + getAuthToken?: () => Promise<string>; } const UiKitProvider = ({ apiKey, apiRegion, apiEndpoint, - authToken, userId, displayName, customComponents = {}, @@ -76,6 +75,7 @@ const UiKitProvider = ({ actionHandlers, onConnectionStatusChange, onDisconnected, + getAuthToken, }: UiKitProviderProps) => { const queryClient = new QueryClient(); const [isConnected, setIsConnected] = useState(false); @@ -105,20 +105,25 @@ const UiKitProvider = ({ setClient(ascClient); } - await ASCClient.login( - { userId, displayName, authToken }, - { - sessionWillRenewAccessToken(renewal) { - // secure mode - if (authToken) { - renewal.renewWithAuthToken(authToken); - return; - } + let params: Amity.ConnectClientParams = { userId, displayName }; + + if (getAuthToken) { + const authToken = await getAuthToken(); + params = { ...params, authToken }; + } + await ASCClient.login(params, { + sessionWillRenewAccessToken(renewal) { + // secure mode + if (getAuthToken) { + getAuthToken().then((authToken) => { + renewal.renewWithAuthToken(authToken); + }); + } else { renewal.renew(); - }, + } }, - ); + }); setIsConnected(true); if (stateChangeRef.current == null) { From 0fd1d3bd59cbffbe9b5c07300807d18785df44fc Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Tue, 13 Aug 2024 09:51:06 +0700 Subject: [PATCH 276/300] fix: change text fail edit (#571) --- src/v4/social/internal-components/EditPost/EditPost.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/v4/social/internal-components/EditPost/EditPost.tsx b/src/v4/social/internal-components/EditPost/EditPost.tsx index ea7393cc7..6825a0839 100644 --- a/src/v4/social/internal-components/EditPost/EditPost.tsx +++ b/src/v4/social/internal-components/EditPost/EditPost.tsx @@ -69,7 +69,7 @@ export function EditPost({ post }: AmityPostComposerEditOptions) { updateItem(response.data); }, onError: (error) => { - console.error('Failed to create post', error); + console.error('Failed to edit post', error); }, }); @@ -187,7 +187,7 @@ export function EditPost({ post }: AmityPostComposerEditOptions) { )} {isError && ( <Notification - content="Failed to create post" + content="Failed to edit post" icon={<ExclamationCircle className={styles.editPost_infoIcon} />} className={styles.editPost__status} duration={3000} From e7f086869dfcaacffc711a94607eef7b16905e13 Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Tue, 13 Aug 2024 21:46:59 +0700 Subject: [PATCH 277/300] fix: ASC-23280 - showing long post (#580) * fix: truncate * style: truncate --- .../components/post/TextContent/index.tsx | 87 +++++++++++-------- 1 file changed, 52 insertions(+), 35 deletions(-) diff --git a/src/social/components/post/TextContent/index.tsx b/src/social/components/post/TextContent/index.tsx index 3beab50b7..db62e2756 100644 --- a/src/social/components/post/TextContent/index.tsx +++ b/src/social/components/post/TextContent/index.tsx @@ -1,6 +1,5 @@ -import React, { useState, useMemo } from 'react'; +import React, { useState, useMemo, useEffect, useRef } from 'react'; import { FormattedMessage } from 'react-intl'; -import Truncate from 'react-truncate-markup'; import styled from 'styled-components'; import { processChunks } from '~/core/components/ChunkHighlighter'; @@ -10,11 +9,20 @@ import MentionHighlightTag from '~/core/components/MentionHighlightTag'; import { Mentioned, findChunks } from '~/helpers/utils'; import { useCustomComponent } from '~/core/providers/CustomComponentsProvider'; -export const PostContent = styled.div` +export const PostContent = styled.div<{ isExpanded: boolean; postMaxLines: number }>` overflow-wrap: break-word; color: ${({ theme }) => theme.palette.neutral.main}; white-space: pre-wrap; - ${({ theme }) => theme.typography.body} + ${({ theme }) => theme.typography.body}; + max-height: ${({ isExpanded, postMaxLines }) => + isExpanded ? 'none' : `calc(${postMaxLines} * 1.5em)`}; /* Adjust height based on line count */ + overflow: hidden; + position: relative; + + /* Hide the ellipsis */ + &::after { + content: ''; + } `; export const ReadMoreButton = styled(Button).attrs({ variant: 'secondary' })` @@ -29,53 +37,62 @@ interface TextContentProps { mentionees?: Mentioned[]; } -const TextContent = ({ text, postMaxLines, mentionees }: TextContentProps) => { +const TextContent = ({ text, postMaxLines = 8, mentionees }: TextContentProps) => { const chunks = useMemo( () => processChunks(text || '', findChunks(mentionees)), [mentionees, text], ); + const contentRef = useRef<HTMLDivElement>(null); + const [isExpanded, setIsExpanded] = useState(false); + const [isTruncated, setIsTruncated] = useState(false); + + useEffect(() => { + if (contentRef.current) { + const { scrollHeight, clientHeight } = contentRef.current; + setIsTruncated(scrollHeight > clientHeight); + } + }, [text, postMaxLines]); + + const onExpand = () => setIsExpanded(true); + const textContent = text ? ( - <PostContent data-qa-anchor="post-text-content"> - <Truncate.Atom> - {chunks.map((chunk) => { - const key = `${text}-${chunk.start}-${chunk.end}`; - const sub = text.substring(chunk.start, chunk.end); - if (chunk.highlight) { - const mentionee = mentionees?.find((m) => m.index === chunk.start); - if (mentionee) { - return ( - <MentionHighlightTag key={key} mentionee={mentionee}> - {sub} - </MentionHighlightTag> - ); - } - return <span key={key}>{sub}</span>; + <PostContent + ref={contentRef} + isExpanded={isExpanded} + postMaxLines={postMaxLines} + data-qa-anchor="post-text-content" + > + {chunks.map((chunk) => { + const key = `${text}-${chunk.start}-${chunk.end}`; + const sub = text.substring(chunk.start, chunk.end); + if (chunk.highlight) { + const mentionee = mentionees?.find((m) => m.index === chunk.start); + if (mentionee) { + return ( + <MentionHighlightTag key={key} mentionee={mentionee}> + {sub} + </MentionHighlightTag> + ); } - return <Linkify key={key}>{sub}</Linkify>; - })} - </Truncate.Atom> + return <span key={key}>{sub}</span>; + } + return <Linkify key={key}>{sub}</Linkify>; + })} </PostContent> ) : null; - const [isExpanded, setIsExpanded] = useState(false); - const onExpand = () => setIsExpanded(true); - if (!textContent) return null; - if (isExpanded) return textContent; - return ( - <Truncate - lines={postMaxLines} - ellipsis={ + <> + {textContent} + {!isExpanded && isTruncated && ( <ReadMoreButton onClick={onExpand}> <FormattedMessage id="post.readMore" /> </ReadMoreButton> - } - > - {textContent} - </Truncate> + )} + </> ); }; From 9b5cdb8a48f827f54870fb1a6fdb449e2707e2dd Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Tue, 13 Aug 2024 22:10:31 +0700 Subject: [PATCH 278/300] style: add spacing (#581) --- src/social/components/post/Creator/styles.tsx | 1 + src/social/components/post/Post/styles.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/src/social/components/post/Creator/styles.tsx b/src/social/components/post/Creator/styles.tsx index a4eeaea01..8385b2dcd 100644 --- a/src/social/components/post/Creator/styles.tsx +++ b/src/social/components/post/Creator/styles.tsx @@ -15,6 +15,7 @@ export const PostCreatorContainer = styled.div` display: flex; background: ${({ theme }) => theme.palette.system.background}; border-radius: 4px; + margin-bottom: 12px; `; export const Footer = styled.div` diff --git a/src/social/components/post/Post/styles.tsx b/src/social/components/post/Post/styles.tsx index f56d302eb..4835bedb5 100644 --- a/src/social/components/post/Post/styles.tsx +++ b/src/social/components/post/Post/styles.tsx @@ -33,6 +33,7 @@ export const PostContainer = styled(PlainPostContainer)` background: ${({ theme }) => theme.palette.system.background}; border: 1px solid #edeef2; border-radius: 4px; + margin-bottom: 12px; `; export const PostHeadContainer = styled.div` From 10d37bbefbe70ebf96d54c2594c7e28f8acb7731 Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Wed, 14 Aug 2024 16:12:10 +0700 Subject: [PATCH 279/300] fix: ASC-24779 - onClick go to post detail (#584) * fix: add onClick redirect * fix: use Button --- .../social/components/GlobalFeed/GlobalFeed.module.css | 1 + src/v4/social/components/GlobalFeed/GlobalFeed.tsx | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/v4/social/components/GlobalFeed/GlobalFeed.module.css b/src/v4/social/components/GlobalFeed/GlobalFeed.module.css index 70cefdc60..e5b6c7677 100644 --- a/src/v4/social/components/GlobalFeed/GlobalFeed.module.css +++ b/src/v4/social/components/GlobalFeed/GlobalFeed.module.css @@ -5,6 +5,7 @@ .global_feed__postContainer { padding: 0.25rem 1rem 0.75rem; + width: 94%; } .global_feed__postSkeletonContainer { diff --git a/src/v4/social/components/GlobalFeed/GlobalFeed.tsx b/src/v4/social/components/GlobalFeed/GlobalFeed.tsx index b4d1c6d25..2e15cc94a 100644 --- a/src/v4/social/components/GlobalFeed/GlobalFeed.tsx +++ b/src/v4/social/components/GlobalFeed/GlobalFeed.tsx @@ -7,6 +7,7 @@ import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; import styles from './GlobalFeed.module.css'; import { useAmityComponent } from '~/v4/core/hooks/uikit'; import { PostAd } from '~/v4/social/internal-components/PostAd/PostAd'; +import { Button } from '~/v4/core/natives/Button'; interface GlobalFeedProps { pageId?: string; @@ -68,7 +69,12 @@ export const GlobalFeed = ({ {isAmityAd(item) ? ( <PostAd ad={item} /> ) : ( - <div className={styles.global_feed__postContainer}> + <Button + className={styles.global_feed__postContainer} + onPress={() => + AmityGlobalFeedComponentBehavior?.goToPostDetailPage?.({ postId: item.postId }) + } + > <PostContent pageId={pageId} post={item} @@ -78,7 +84,7 @@ export const GlobalFeed = ({ }} onPostDeleted={onPostDeleted} /> - </div> + </Button> )} </div> ))} From 3fc11013cc5a4f4cae60024d906a69160a62f279 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Thu, 15 Aug 2024 11:58:13 +0700 Subject: [PATCH 280/300] fix: fix ui (#589) --- src/social/pages/UserFeed/Followers/FollowersList.tsx | 2 +- src/social/pages/UserFeed/Followers/PendingList.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/social/pages/UserFeed/Followers/FollowersList.tsx b/src/social/pages/UserFeed/Followers/FollowersList.tsx index 19109365d..ede472813 100644 --- a/src/social/pages/UserFeed/Followers/FollowersList.tsx +++ b/src/social/pages/UserFeed/Followers/FollowersList.tsx @@ -120,7 +120,7 @@ function useFollowerListItems({ userId }: { userId?: string | null }) { const { followers, isLoading, loadMore, hasMore, loadMoreHasBeenCalled } = useFollowersCollection( { userId, - status: 'all', + status: 'accepted', }, ); diff --git a/src/social/pages/UserFeed/Followers/PendingList.tsx b/src/social/pages/UserFeed/Followers/PendingList.tsx index 2da713f4e..288317db2 100644 --- a/src/social/pages/UserFeed/Followers/PendingList.tsx +++ b/src/social/pages/UserFeed/Followers/PendingList.tsx @@ -90,7 +90,7 @@ const PendingList = ({ userId }: { userId?: string | null }) => { isLoadingItem(item) ? ( <Skeleton style={{ fontSize: 8 }} /> ) : ( - <PendingItem key={`${item.from}-${item.to}`} userId={item.to} /> + <PendingItem key={`${item.from}-${item.to}`} userId={item.from} /> ), )} </Grid> From 5a91c314201f53c5eb28fea95d471b208359a474 Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Fri, 16 Aug 2024 13:33:57 +0700 Subject: [PATCH 281/300] fix: ASC-24857 - link preview button (#570) * fix: use button instead div * refactor: onBack * fix: add button Ad link --- .../components/PostContent/LinkPreview/LinkPreview.tsx | 9 +++++---- src/v4/social/internal-components/EditPost/EditPost.tsx | 8 ++++---- src/v4/social/internal-components/PostAd/UIPostAd.tsx | 6 +++--- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/v4/social/components/PostContent/LinkPreview/LinkPreview.tsx b/src/v4/social/components/PostContent/LinkPreview/LinkPreview.tsx index ff2ecbaef..46bd6f219 100644 --- a/src/v4/social/components/PostContent/LinkPreview/LinkPreview.tsx +++ b/src/v4/social/components/PostContent/LinkPreview/LinkPreview.tsx @@ -6,6 +6,7 @@ import { useQuery } from '@tanstack/react-query'; import useSDK from '~/v4/core/hooks/useSDK'; import { LinkPreviewSkeleton } from './LinkPreviewSkeleton'; import styles from './LinkPreview.module.css'; +import { Button } from '~/v4/core/natives/Button'; const UnableToPreviewSvg = (props: React.SVGProps<SVGSVGElement>) => ( <svg @@ -69,19 +70,19 @@ export function LinkPreview({ url }: LinkPreviewProps) { if (previewData.isError) { return ( - <div onClick={handleClick} className={styles.linkPreview}> + <Button onPress={handleClick} className={styles.linkPreview}> <div className={styles.linkPreview__top}> <UnableToPreview /> </div> <div className={styles.linkPreview__bottom}> <Typography.Caption>{urlObject.hostname}</Typography.Caption> </div> - </div> + </Button> ); } return ( - <div onClick={handleClick} className={styles.linkPreview}> + <Button onPress={handleClick} className={styles.linkPreview}> <div className={styles.linkPreview__top}> {previewData.data?.image ? ( <object data={previewData.data.image} className={styles.linkPreview__object}> @@ -97,6 +98,6 @@ export function LinkPreview({ url }: LinkPreviewProps) { <Typography.Caption>{urlObject.hostname}</Typography.Caption> <Typography.BodyBold>{previewData.data?.title || ''}</Typography.BodyBold> </div> - </div> + </Button> ); } diff --git a/src/v4/social/internal-components/EditPost/EditPost.tsx b/src/v4/social/internal-components/EditPost/EditPost.tsx index 6825a0839..87824c29c 100644 --- a/src/v4/social/internal-components/EditPost/EditPost.tsx +++ b/src/v4/social/internal-components/EditPost/EditPost.tsx @@ -6,7 +6,6 @@ import { LexicalEditor } from 'lexical'; import { useForm } from 'react-hook-form'; import { useAmityPage } from '~/v4/core/hooks/uikit'; import { useConfirmContext } from '~/v4/core/providers/ConfirmProvider'; -import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; import { AmityPostComposerEditOptions, CreatePostParams, @@ -22,6 +21,7 @@ import { ExclamationCircle } from '~/icons'; import { Thumbnail } from './Thumbnail'; import { useGlobalFeedContext } from '~/v4/social/providers/GlobalFeedProvider'; import { arraysContainSameElements } from '~/v4/social/utils/arraysContainSameElements'; +import { useNavigation } from '~/v4/core/providers/NavigationProvider'; export function EditPost({ post }: AmityPostComposerEditOptions) { const pageId = 'post_composer_page'; @@ -30,7 +30,7 @@ export function EditPost({ post }: AmityPostComposerEditOptions) { }); const editorRef = useRef<LexicalEditor | null>(null); - const { AmityPostComposerPageBehavior } = usePageBehavior(); + const { onBack } = useNavigation(); const { confirm } = useConfirmContext(); const { updateItem } = useGlobalFeedContext(); @@ -65,7 +65,7 @@ export function EditPost({ post }: AmityPostComposerEditOptions) { return await PostRepository.editPost(post.postId, params); }, onSuccess: (response) => { - AmityPostComposerPageBehavior?.goToSocialHomePage?.(); + onBack(); updateItem(response.data); }, onError: (error) => { @@ -134,7 +134,7 @@ export function EditPost({ post }: AmityPostComposerEditOptions) { title: 'Discard this post?', content: 'The post will be permanently deleted. It cannot be undone.', onOk: () => { - AmityPostComposerPageBehavior?.goToSocialHomePage?.(); + onBack(); }, okText: 'Discard', cancelText: 'Keep editing', diff --git a/src/v4/social/internal-components/PostAd/UIPostAd.tsx b/src/v4/social/internal-components/PostAd/UIPostAd.tsx index b4d7e70b8..6dfb7c943 100644 --- a/src/v4/social/internal-components/PostAd/UIPostAd.tsx +++ b/src/v4/social/internal-components/PostAd/UIPostAd.tsx @@ -54,10 +54,10 @@ export const UIPostAd = ({ <InfoCircle className={styles.infoIcon} /> </Button> - <div + <Button className={styles.footer} data-has-url={!!ad.callToActionUrl} - onClick={handleCallToActionClick} + onPress={handleCallToActionClick} > <div className={styles.footer__left}> <Typography.Body className={styles.footer__content__description}> @@ -76,7 +76,7 @@ export const UIPostAd = ({ </Button> </div> ) : null} - </div> + </Button> <AdInformation ad={ad} isOpen={isAdvertisementInfoOpen} From e16b6d2dd025aaa656da814ae4847fcfadce0d30 Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Fri, 16 Aug 2024 18:15:11 +0700 Subject: [PATCH 282/300] chore(release): 4.0.0-beta.11 (#601) Co-authored-by: bmo-amity-bot <developers@amity.co> --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b12931ca..e9a0af1b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## 4.0.0-beta.11 (2024-08-16) + + +### Bug Fixes + +* ASC-24857 - link preview button ([#570](https://github.com/AmityCo/Amity-Social-Cloud-UIKit-Web/issues/570)) ([5a91c31](https://github.com/AmityCo/Amity-Social-Cloud-UIKit-Web/commit/5a91c314201f53c5eb28fea95d471b208359a474)) + ## 4.0.0-beta.10 (2024-07-24) ## 4.0.0-beta.8 (2024-06-24) diff --git a/package.json b/package.json index e77ea4b3d..cb41b6e97 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@amityco/ui-kit", - "version": "4.0.0-beta.10", + "version": "4.0.0-beta.11", "engines": { "node": ">=20", "pnpm": "9" From 1b9c9454d1ae00b3e0bc1837d3b22b09af329c16 Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Tue, 20 Aug 2024 11:21:14 +0700 Subject: [PATCH 283/300] fix: ASC-00000 - layout global feed (#603) * fix: scroll horizontal * style: remove padding --- src/v4/social/components/GlobalFeed/GlobalFeed.module.css | 3 +-- src/v4/social/components/PostContent/PostContent.module.css | 1 + src/v4/social/pages/PostDetailPage/PostDetailPage.module.css | 4 ---- src/v4/social/pages/PostDetailPage/PostDetailPage.tsx | 2 +- src/v4/social/pages/SocialHomePage/SocialHomePage.module.css | 2 +- 5 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/v4/social/components/GlobalFeed/GlobalFeed.module.css b/src/v4/social/components/GlobalFeed/GlobalFeed.module.css index e5b6c7677..ddaa89ca6 100644 --- a/src/v4/social/components/GlobalFeed/GlobalFeed.module.css +++ b/src/v4/social/components/GlobalFeed/GlobalFeed.module.css @@ -4,8 +4,7 @@ } .global_feed__postContainer { - padding: 0.25rem 1rem 0.75rem; - width: 94%; + width: 100%; } .global_feed__postSkeletonContainer { diff --git a/src/v4/social/components/PostContent/PostContent.module.css b/src/v4/social/components/PostContent/PostContent.module.css index 5cabd922b..93fe49961 100644 --- a/src/v4/social/components/PostContent/PostContent.module.css +++ b/src/v4/social/components/PostContent/PostContent.module.css @@ -1,5 +1,6 @@ .postContent { background-color: var(--asc-color-background-default); + padding: 0.25rem 1rem 0.75rem; } .postContent__bar { diff --git a/src/v4/social/pages/PostDetailPage/PostDetailPage.module.css b/src/v4/social/pages/PostDetailPage/PostDetailPage.module.css index 881e97d33..814dc72bc 100644 --- a/src/v4/social/pages/PostDetailPage/PostDetailPage.module.css +++ b/src/v4/social/pages/PostDetailPage/PostDetailPage.module.css @@ -40,10 +40,6 @@ cursor: pointer; } -.postDetailPage__postContent { - padding: 0.5rem 1rem; -} - .postDetailPage__backIcon { fill: var(--asc-color-base-default); cursor: pointer; diff --git a/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx b/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx index c4cd20502..9e159c741 100644 --- a/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx +++ b/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx @@ -32,7 +32,7 @@ export function PostDetailPage({ id }: PostDetailPageProps) { return ( <div className={styles.postDetailPage} style={themeStyles}> <div className={styles.postDetailPage__container}> - <div className={styles.postDetailPage__postContent}> + <div> {isPostLoading ? ( <PostContentSkeleton pageId={pageId} /> ) : post ? ( diff --git a/src/v4/social/pages/SocialHomePage/SocialHomePage.module.css b/src/v4/social/pages/SocialHomePage/SocialHomePage.module.css index 604ab0c51..adb0cc55c 100644 --- a/src/v4/social/pages/SocialHomePage/SocialHomePage.module.css +++ b/src/v4/social/pages/SocialHomePage/SocialHomePage.module.css @@ -38,7 +38,7 @@ top: 7.5rem; width: 100%; height: calc(100% - 7.5rem); - overflow-y: scroll; + overflow: hidden scroll; -ms-overflow-style: none; /* IE and Edge */ scrollbar-width: none; /* Firefox */ } From 8495866c4873d0108e2dfd47a3306232280b7f82 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Fri, 23 Aug 2024 11:13:30 +0700 Subject: [PATCH 284/300] chore: change default screen to fullscreen (#598) --- .storybook/decorators/FluidControl.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.storybook/decorators/FluidControl.tsx b/.storybook/decorators/FluidControl.tsx index 263b0a973..b2aa92b04 100644 --- a/.storybook/decorators/FluidControl.tsx +++ b/.storybook/decorators/FluidControl.tsx @@ -143,7 +143,7 @@ const decorator: NonNullable<Preview['decorators']>[number] = ( Story, { globals: { [GLOBAL_NAME]: val } }, ) => { - if (val === 'none') return <Centered>{Story()}</Centered>; + if (val === 'none') return <FullScreen>{Story()}</FullScreen>; else if (val === 'fullscreen') return <FullScreen>{Story()}</FullScreen>; else if (val === 'framed') return <Framed>{Story()}</Framed>; else if (val === 'boundingbox') From ff79fd0f0fca6efb6703d515a0947922a3233ceb Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Fri, 23 Aug 2024 11:13:47 +0700 Subject: [PATCH 285/300] chore: ASC-00000 - configurable storybook (#595) * chore: receive apiKey, apiRegion, userId and displayName as a free text * chore: add submit toggle --- .storybook/decorators/UiKitDecorator.tsx | 56 ++++++++++------------ .storybook/decorators/UiKitV4Decorator.tsx | 49 +++++++++---------- .storybook/preview.ts | 16 ++++++- 3 files changed, 61 insertions(+), 60 deletions(-) diff --git a/.storybook/decorators/UiKitDecorator.tsx b/.storybook/decorators/UiKitDecorator.tsx index b030a36da..d21f85bf3 100644 --- a/.storybook/decorators/UiKitDecorator.tsx +++ b/.storybook/decorators/UiKitDecorator.tsx @@ -1,36 +1,30 @@ import React, { useCallback } from 'react'; import UiKitProvider from '../../src/core/providers/UiKitProvider'; import { Preview } from '@storybook/react'; -import amityConfig from '../../amity-uikit.config.json'; - -const users = import.meta.env.STORYBOOK_USERS.split(','); - -const GLOBAL_NAME = 'user'; -const global = { - [GLOBAL_NAME]: { - name: 'User selector', - description: 'User switcher for SDK', - defaultValue: 'Web-Test', - toolbar: { - icon: 'user', - items: [ - { value: 'Web-Test,Web-test', title: 'Web-Test' }, - ...users.map((user) => { - return { value: `${user},${user}`, title: user }; - }), - ], - }, - }, -}; +import { useState } from 'react'; +import { useEffect } from 'react'; + +const FALLBACK_USER = 'Web-Test'; + +const decorator: NonNullable<Preview['decorators']>[number] = (Story, context) => { + const { args } = context; + + const [userId, setUserId] = useState<string>(args.userId || FALLBACK_USER); + const [displayNameState, setDisplayNameState] = useState<string | undefined>( + args.displayName || args.userId || userId, + ); -const FALLBACK_USER = 'Web-Test,Web-Test'; + useEffect(() => { + if (!args.submit) return; + if (args.userId) { + setUserId(args.userId); + } + if (args.displayName) { + setDisplayNameState(args.displayName); + } + }, [args.submit]); -const decorator: NonNullable<Preview['decorators']>[number] = ( - Story, - { globals: { [GLOBAL_NAME]: val, theme } }, -) => { - const user = val || FALLBACK_USER; - const [userId, displayName] = user.split(','); + const displayName = displayNameState || userId; const handleConnectionStatusChange = useCallback((...args) => { console.log(`[UiKitProvider.handleConnectionStatusChange]`, ...args); @@ -46,8 +40,8 @@ const decorator: NonNullable<Preview['decorators']>[number] = ( return ( <UiKitProvider - apiKey={import.meta.env.STORYBOOK_API_KEY} - apiRegion={import.meta.env.STORYBOOK_API_REGION} + apiKey={args.apiKey || import.meta.env.STORYBOOK_API_KEY} + apiRegion={args.apiRegion || import.meta.env.STORYBOOK_API_REGION} key={userId} userId={userId} displayName={displayName || userId} @@ -60,4 +54,4 @@ const decorator: NonNullable<Preview['decorators']>[number] = ( ); }; -export default { global, decorator }; +export default { decorator }; diff --git a/.storybook/decorators/UiKitV4Decorator.tsx b/.storybook/decorators/UiKitV4Decorator.tsx index 0bc9887e5..802529925 100644 --- a/.storybook/decorators/UiKitV4Decorator.tsx +++ b/.storybook/decorators/UiKitV4Decorator.tsx @@ -3,35 +3,30 @@ import { AmityUIKitProvider } from '../../src/v4/core/providers'; import { Preview } from '@storybook/react'; import amityConfig from '../../amity-uikit.config.json'; import { Config } from '../../src/v4/core/providers/CustomizationProvider'; +import { useState } from 'react'; +import { useEffect } from 'react'; -const users = import.meta.env.STORYBOOK_USERS.split(','); +const FALLBACK_USER = 'Web-Test'; -const GLOBAL_NAME = 'user'; -const global = { - [GLOBAL_NAME]: { - name: 'User selector', - description: 'User switcher for SDK', - defaultValue: 'Web-Test', - toolbar: { - icon: 'user', - items: [ - { value: 'Web-Test,Web-test', title: 'Web-Test' }, - ...users.map((user) => { - return { value: `${user},${user}`, title: user }; - }), - ], - }, - }, -}; +const decorator: NonNullable<Preview['decorators']>[number] = (Story, context) => { + const { args } = context; + + const [userId, setUserId] = useState<string>(args.userId || FALLBACK_USER); + const [displayNameState, setDisplayNameState] = useState<string | undefined>( + args.displayName || args.userId || userId, + ); -const FALLBACK_USER = 'Web-Test,Web-Test'; + useEffect(() => { + if (!args.submit) return; + if (args.userId) { + setUserId(args.userId); + } + if (args.displayName) { + setDisplayNameState(args.displayName); + } + }, [args.submit]); -const decorator: NonNullable<Preview['decorators']>[number] = ( - Story, - { globals: { [GLOBAL_NAME]: val, theme } }, -) => { - const user = val || FALLBACK_USER; - const [userId, displayName] = user.split(','); + const displayName = displayNameState || userId; const handleConnectionStatusChange = useCallback((...args) => { console.log(`[UiKitProvider.handleConnectionStatusChange]`, ...args); @@ -47,8 +42,8 @@ const decorator: NonNullable<Preview['decorators']>[number] = ( return ( <AmityUIKitProvider - apiKey={import.meta.env.STORYBOOK_API_KEY} - apiRegion={import.meta.env.STORYBOOK_API_REGION} + apiKey={args.apiKey || import.meta.env.STORYBOOK_API_KEY} + apiRegion={args.apiRegion || import.meta.env.STORYBOOK_API_REGION} key={userId} userId={userId} displayName={displayName || userId} diff --git a/.storybook/preview.ts b/.storybook/preview.ts index 53cf53fb1..dc1f1e500 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -2,6 +2,20 @@ import { Preview } from '@storybook/react'; import { FluidControl, UiKitDecorator, UiKitV4Decorator } from './decorators'; const preview: Preview = { + args: { + apiKey: undefined, + apiRegion: undefined, + userId: undefined, + displayName: undefined, + submit: false, + }, + argTypes: { + apiKey: { control: { type: 'text' } }, + apiRegion: { control: { type: 'text' } }, + userId: { control: { type: 'text' } }, + displayName: { control: { type: 'text' } }, + submit: { control: { type: 'boolean' } }, + }, decorators: [ FluidControl.decorator, (Story, ctx) => { @@ -29,8 +43,6 @@ const preview: Preview = { }, globalTypes: { ...FluidControl.global, - ...UiKitDecorator.global, - ...UiKitV4Decorator.global, }, }; From 6bc4363ae358a41575db6d122f53e2b400694a7b Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Fri, 23 Aug 2024 12:15:59 +0700 Subject: [PATCH 286/300] feat: ASC-24796 - fix community profile (#568) * fix: merge from develop * fix: remove unused * fix: community profile component * fix: post content to use bottom sheet * fix: community tab control state * feat: add community pin * fix: community profile style * feat: navigate back in community cover * fix: use millify for count value * fix: coomunity profile post gap * fix: verify badge * fix: community name description * fix: remove unused * fix: community description max lines * fix: community categories * fix: comunity cover to use bem convention * fix: community cover add max-height * fix: export page and components * fix: update navigation and page behavior * fix: add community info * feat: community feed post skeleton * fix: remove unused * fix: community tab active color state * fix: page behavior comment unused * fix: css * fix: pin * fix: type * fix: css * Revert "fix: css" This reverts commit d10c48f94a79d0ab4dcd876a35553457ad6abbc3. * fix: css * style: z index * fix: category UI * fix: handle long description * fix: max length * fix: cover icon color * fix: private commu hide join btn * feat: no post UI * feat: empty pinpost * feat: lock private content * fix: condition show lock private content * fix: condition lock content * feat: edit page v3 * fix: export error * fix: context type * fix: redirect edit post * style: pass fill default icon as props * style: remove props * fix: skeleton feed * refactor: icons and conditions * style: lock icon size * fix: locate files * fix: import error * fix: duplicated code * fix: edit update post * fix: review comments * refactor: spinner pull to refresh * fix: navigate post detail * fix: hide post impression when not member * style: spacing * fix: prevent comment for non member * style: color text empty post * fix: UI post title * fix: post detail click and non member access * fix: threshold * style: menu button size --------- Co-authored-by: Chaiwat Trisuwan <chaiwattsw@gmail.com> --- src/index.ts | 10 +- .../core/providers/CommunityTabProvider.tsx | 29 +++ .../core/providers/CustomizationProvider.tsx | 44 +++++ src/v4/core/providers/NavigationProvider.tsx | 15 +- .../core/providers/PageBehaviorProvider.tsx | 58 ++++++ src/v4/icons/CommunityCreatePost.tsx | 57 ++++++ src/v4/icons/Ellipsis.tsx | 14 ++ src/v4/icons/EmptyPost.tsx | 16 ++ src/v4/icons/Feed.tsx | 12 ++ src/v4/icons/Lock.tsx | 16 ++ src/v4/icons/Pin.tsx | 12 ++ src/v4/icons/PinBadge.tsx | 19 ++ src/v4/icons/Plus.tsx | 18 ++ src/v4/icons/RefreshSpinner.tsx | 41 ++++ src/v4/icons/VerifyBadge.tsx | 13 ++ .../CommentComposer/CommentInput.tsx | 2 + .../CommunityCreatePostButton.tsx | 0 .../CommunityCreatePostButton/index.ts | 0 .../CommunityFeed/CommunityFeed.module.css | 146 ++++++++++++++ .../CommunityFeed/CommunityFeed.tsx | 144 ++++++++++++++ .../social/components/CommunityFeed/index.ts | 1 + .../CommunityHeader.module.css | 134 +++++++++++++ .../CommunityHeader/CommunityHeader.tsx | 91 +++++++++ .../components/CommunityHeader/index.ts | 1 + .../CommunityPin/CommunityPin.module.css | 5 + .../components/CommunityPin/CommunityPin.tsx | 42 ++++ .../social/components/CommunityPin/index.ts | 1 + .../CommunityPinnedPost.tsx | 9 + .../components/CommunityPinnedPost/index.ts | 1 + .../EmptyPinnedPost.module.css | 18 ++ .../EmptyPinnedPost/EmptyPinnedPost.tsx | 30 +++ .../components/EmptyPinnedPost/index.ts | 1 + .../components/GlobalFeed/GlobalFeed.tsx | 7 +- .../PostContent/PostContent.module.css | 133 ++++++++++++- .../components/PostContent/PostContent.tsx | 179 +++++++++++------- src/v4/social/components/index.ts | 3 + .../social/elements/BackButton/BackButton.tsx | 2 +- .../CommunityCategory.module.css | 11 ++ .../CommunityCategory/CommunityCategory.tsx | 37 ++++ .../elements/CommunityCategory/index.ts | 1 + .../CommunityCategoryName.module.css | 5 +- .../CommunityCategoryName.tsx | 15 +- .../CommunityCover/CommunityCover.module.css | 45 +++++ .../CommunityCover/CommunityCover.tsx | 58 ++++++ .../social/elements/CommunityCover/index.ts | 1 + .../CommunityCreatePostButton.tsx | 42 ++++ .../CommunityCreatePostButton/index.ts | 1 + .../CommunityDescription.module.css | 26 +++ .../CommunityDescription.tsx | 62 ++++++ .../elements/CommunityDescription/index.ts | 1 + .../CommunityInfo/CommunityInfo.module.css | 20 ++ .../elements/CommunityInfo/CommunityInfo.tsx | 40 ++++ src/v4/social/elements/CommunityInfo/index.ts | 1 + .../CommunityJoinButton.module.css | 16 ++ .../CommunityJoinButton.tsx | 50 +++++ .../elements/CommunityJoinButton/index.ts | 1 + .../CommunityName/CommunityName.module.css | 8 + .../elements/CommunityName/CommunityName.tsx | 31 +++ src/v4/social/elements/CommunityName/index.ts | 1 + .../CommunityPendingPost.module.css | 41 ++++ .../CommunityPendingPost.tsx | 47 +++++ .../elements/CommunityPendingPost/index.ts | 1 + .../CommunityPinTabButton.tsx | 27 +++ .../elements/CommunityPinTabButton/index.ts | 1 + .../CommunityProfileMenuButton.module.css | 6 + .../CommunityProfileMenuButton.tsx | 46 +++++ .../CommunityProfileMenuButton/index.tsx | 1 + .../CommunityProfileTab.module.css | 25 +++ .../CommunityProfileTab.tsx | 54 ++++++ .../elements/CommunityProfileTab/index.ts | 1 + .../CommunityVerifyBadge.tsx | 34 ++++ .../elements/CommunityVerifyBadge/index.ts | 1 + .../elements/MenuButton/MenuButton.module.css | 11 ++ .../social/elements/MenuButton/MenuButton.tsx | 20 +- .../NonMemberSection/NonMemberSection.tsx | 0 .../social/elements/NonMemberSection/index.ts | 0 src/v4/social/elements/PinBadge/PinBadge.tsx | 31 +++ src/v4/social/elements/PinBadge/index.ts | 1 + .../hooks/collections/usePostsCollection.ts | 27 +++ src/v4/social/hooks/useCommunityInfo.ts | 1 + .../LockPrivateContent.module.css | 24 +++ .../LockPrivateContent/LockPrivateContent.tsx | 18 ++ .../LockPrivateContent/index.tsx | 1 + src/v4/social/pages/Application/index.tsx | 11 +- .../CommunityProfilePage.module.css | 59 ++++++ .../CommunityProfilePage.tsx | 87 +++++++++ .../CommunityProfilePageSkeleton.tsx | 26 +++ .../CommunityProfileSkeleton.module.css | 98 ++++++++++ .../pages/CommunityProfilePage/index.ts | 1 + .../pages/CommunityProfilePage/ui.stories.tsx | 77 ++++++++ .../PostDetailPage/PostDetailPage.module.css | 16 ++ .../pages/PostDetailPage/PostDetailPage.tsx | 52 +++-- src/v4/social/pages/index.ts | 1 + 93 files changed, 2523 insertions(+), 121 deletions(-) create mode 100644 src/v4/core/providers/CommunityTabProvider.tsx create mode 100644 src/v4/icons/CommunityCreatePost.tsx create mode 100644 src/v4/icons/Ellipsis.tsx create mode 100644 src/v4/icons/EmptyPost.tsx create mode 100644 src/v4/icons/Feed.tsx create mode 100644 src/v4/icons/Lock.tsx create mode 100644 src/v4/icons/Pin.tsx create mode 100644 src/v4/icons/PinBadge.tsx create mode 100644 src/v4/icons/Plus.tsx create mode 100644 src/v4/icons/RefreshSpinner.tsx create mode 100644 src/v4/icons/VerifyBadge.tsx create mode 100644 src/v4/social/components/CommunityCreatePostButton/CommunityCreatePostButton.tsx create mode 100644 src/v4/social/components/CommunityCreatePostButton/index.ts create mode 100644 src/v4/social/components/CommunityFeed/CommunityFeed.module.css create mode 100644 src/v4/social/components/CommunityFeed/CommunityFeed.tsx create mode 100644 src/v4/social/components/CommunityFeed/index.ts create mode 100644 src/v4/social/components/CommunityHeader/CommunityHeader.module.css create mode 100644 src/v4/social/components/CommunityHeader/CommunityHeader.tsx create mode 100644 src/v4/social/components/CommunityHeader/index.ts create mode 100644 src/v4/social/components/CommunityPin/CommunityPin.module.css create mode 100644 src/v4/social/components/CommunityPin/CommunityPin.tsx create mode 100644 src/v4/social/components/CommunityPin/index.ts create mode 100644 src/v4/social/components/CommunityPinnedPost/CommunityPinnedPost.tsx create mode 100644 src/v4/social/components/CommunityPinnedPost/index.ts create mode 100644 src/v4/social/components/EmptyPinnedPost/EmptyPinnedPost.module.css create mode 100644 src/v4/social/components/EmptyPinnedPost/EmptyPinnedPost.tsx create mode 100644 src/v4/social/components/EmptyPinnedPost/index.ts create mode 100644 src/v4/social/elements/CommunityCategory/CommunityCategory.module.css create mode 100644 src/v4/social/elements/CommunityCategory/CommunityCategory.tsx create mode 100644 src/v4/social/elements/CommunityCategory/index.ts create mode 100644 src/v4/social/elements/CommunityCover/CommunityCover.module.css create mode 100644 src/v4/social/elements/CommunityCover/CommunityCover.tsx create mode 100644 src/v4/social/elements/CommunityCover/index.ts create mode 100644 src/v4/social/elements/CommunityCreatePostButton/CommunityCreatePostButton.tsx create mode 100644 src/v4/social/elements/CommunityCreatePostButton/index.ts create mode 100644 src/v4/social/elements/CommunityDescription/CommunityDescription.module.css create mode 100644 src/v4/social/elements/CommunityDescription/CommunityDescription.tsx create mode 100644 src/v4/social/elements/CommunityDescription/index.ts create mode 100644 src/v4/social/elements/CommunityInfo/CommunityInfo.module.css create mode 100644 src/v4/social/elements/CommunityInfo/CommunityInfo.tsx create mode 100644 src/v4/social/elements/CommunityInfo/index.ts create mode 100644 src/v4/social/elements/CommunityJoinButton/CommunityJoinButton.module.css create mode 100644 src/v4/social/elements/CommunityJoinButton/CommunityJoinButton.tsx create mode 100644 src/v4/social/elements/CommunityJoinButton/index.ts create mode 100644 src/v4/social/elements/CommunityName/CommunityName.module.css create mode 100644 src/v4/social/elements/CommunityName/CommunityName.tsx create mode 100644 src/v4/social/elements/CommunityName/index.ts create mode 100644 src/v4/social/elements/CommunityPendingPost/CommunityPendingPost.module.css create mode 100644 src/v4/social/elements/CommunityPendingPost/CommunityPendingPost.tsx create mode 100644 src/v4/social/elements/CommunityPendingPost/index.ts create mode 100644 src/v4/social/elements/CommunityPinTabButton/CommunityPinTabButton.tsx create mode 100644 src/v4/social/elements/CommunityPinTabButton/index.ts create mode 100644 src/v4/social/elements/CommunityProfileMenuButton/CommunityProfileMenuButton.module.css create mode 100644 src/v4/social/elements/CommunityProfileMenuButton/CommunityProfileMenuButton.tsx create mode 100644 src/v4/social/elements/CommunityProfileMenuButton/index.tsx create mode 100644 src/v4/social/elements/CommunityProfileTab/CommunityProfileTab.module.css create mode 100644 src/v4/social/elements/CommunityProfileTab/CommunityProfileTab.tsx create mode 100644 src/v4/social/elements/CommunityProfileTab/index.ts create mode 100644 src/v4/social/elements/CommunityVerifyBadge/CommunityVerifyBadge.tsx create mode 100644 src/v4/social/elements/CommunityVerifyBadge/index.ts create mode 100644 src/v4/social/elements/NonMemberSection/NonMemberSection.tsx create mode 100644 src/v4/social/elements/NonMemberSection/index.ts create mode 100644 src/v4/social/elements/PinBadge/PinBadge.tsx create mode 100644 src/v4/social/elements/PinBadge/index.ts create mode 100644 src/v4/social/hooks/collections/usePostsCollection.ts create mode 100644 src/v4/social/internal-components/LockPrivateContent/LockPrivateContent.module.css create mode 100644 src/v4/social/internal-components/LockPrivateContent/LockPrivateContent.tsx create mode 100644 src/v4/social/internal-components/LockPrivateContent/index.tsx create mode 100644 src/v4/social/pages/CommunityProfilePage/CommunityProfilePage.module.css create mode 100644 src/v4/social/pages/CommunityProfilePage/CommunityProfilePage.tsx create mode 100644 src/v4/social/pages/CommunityProfilePage/CommunityProfilePageSkeleton.tsx create mode 100644 src/v4/social/pages/CommunityProfilePage/CommunityProfileSkeleton.module.css create mode 100644 src/v4/social/pages/CommunityProfilePage/index.ts create mode 100644 src/v4/social/pages/CommunityProfilePage/ui.stories.tsx diff --git a/src/index.ts b/src/index.ts index eaacd94d9..0987ab370 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,10 +20,6 @@ export { useSDK as useAmitySDK } from '~/core/hooks/useSDK'; // v4 export { AmityUIKitManager } from '~/v4/core/AmityUIKitManager'; -export { - CommentTray as AmityCommentTrayComponent, - StoryTab as AmityStoryTabComponent, -} from '~/v4/social/components'; // Chat v4 @@ -60,6 +56,7 @@ export { MyCommunitiesSearchPage as AmityMyCommunitiesSearchPage, SelectPostTargetPage as AmityPostTargetSelectionPage, PostComposerPage as AmityPostComposerPage, + CommunityProfilePage as AmityCommunityProfilePage, } from '~/v4/social/pages'; export { @@ -76,6 +73,11 @@ export { CreatePostMenu as AmityCreatePostMenuComponent, ReactionList as AmityReactionListComponent, TopNavigation as AmitySocialHomeTopNavigationComponent, + CommentTray as AmityCommentTrayComponent, + StoryTab as AmityStoryTabComponent, + CommunityHeader as AmityCommunityHeaderComponent, + CommunityFeed as AmityCommunityFeedComponent, + CommunityPinnedPost as AmityCommunityPinnedPostComponent, } from '~/v4/social/components/'; export { HomePageTab as AmitySocialHomePageTab } from '~/v4/social/pages/SocialHomePage'; diff --git a/src/v4/core/providers/CommunityTabProvider.tsx b/src/v4/core/providers/CommunityTabProvider.tsx new file mode 100644 index 000000000..76eb71e2e --- /dev/null +++ b/src/v4/core/providers/CommunityTabProvider.tsx @@ -0,0 +1,29 @@ +import React, { createContext, useContext, useState } from 'react'; + +type CommunityTabContextType = { + activeTab: 'community_feed' | 'community_pin'; + setActiveTab: (tab: 'community_feed' | 'community_pin') => void; +}; + +const CommunityTabContext = createContext<CommunityTabContextType>({ + activeTab: 'community_feed', + setActiveTab: () => {}, +}); + +export const useCommunityTabContext = () => useContext(CommunityTabContext); + +type CommunityTabProviderProps = { + children: React.ReactNode; +}; + +export const CommunityTabProvider: React.FC<CommunityTabProviderProps> = ({ children }) => { + const [activeTab, setActiveTab] = + useState<CommunityTabContextType['activeTab']>('community_feed'); + + const value: CommunityTabContextType = { + activeTab, + setActiveTab, + }; + + return <CommunityTabContext.Provider value={value}>{children}</CommunityTabContext.Provider>; +}; diff --git a/src/v4/core/providers/CustomizationProvider.tsx b/src/v4/core/providers/CustomizationProvider.tsx index 9f11687c2..2c59cf297 100644 --- a/src/v4/core/providers/CustomizationProvider.tsx +++ b/src/v4/core/providers/CustomizationProvider.tsx @@ -490,6 +490,50 @@ export const defaultConfig: DefaultConfig = { 'my_communities_search_page/top_search_bar/cancel_button': { text: 'Cancel', }, + 'community_profile_page/*/*': {}, + 'community_profile_page/community_feed/*': {}, + '*/post_content/announcement_badge': { + image: 'value', + }, + '*/post_content/pin_badge': { + image: 'value', + }, + '*/post_content/non_member_section': { + image: 'value', + }, + 'community_profile_page/community_header/*': {}, + 'community_profile_page/community_header/community_cover': {}, + 'community_profile_page/community_header/community_name': {}, + 'community_profile_page/community_header/community_verify_badge': { + image: 'value', + }, + 'community_profile_page/community_header/community_category': {}, + 'community_profile_page/community_header/community_description': {}, + 'community_profile_page/community_header/community_info': {}, + 'community_profile_page/community_header/community_join_button': { + image: 'value', + }, + 'community_profile_page/community_header/community_pending_post': { + image: 'value', + }, + 'community_profile_page/community_header/back_button': { + image: 'value', + }, + 'community_profile_page/community_header/menu_button': { + image: 'value', + }, + 'community_profile_page/community_profile_tab/*': {}, + 'community_profile_page/community_profile_tab/community_feed_tab_button': { + image: 'value', + }, + 'community_profile_page/community_profile_tab/community_pin_tab_button': { + image: 'value', + }, + 'community_profile_page/community_pin/*': {}, + 'community_profile_page/community_pin/community_create_post_button': { + image: 'value', + }, + 'community_profile_page/post_content/*': {}, }, }; diff --git a/src/v4/core/providers/NavigationProvider.tsx b/src/v4/core/providers/NavigationProvider.tsx index 3dbe45f54..188313b25 100644 --- a/src/v4/core/providers/NavigationProvider.tsx +++ b/src/v4/core/providers/NavigationProvider.tsx @@ -70,6 +70,7 @@ type Page = context: { postId: string; communityId?: string; + hideTarget?: boolean; }; } | { type: PageTypes.CommunityProfilePage; context: { communityId: string } } @@ -114,7 +115,7 @@ type ContextValue = { onMessageUser: (userId: string) => void; onBack: () => void; goToUserProfilePage: (userId: string) => void; - goToPostDetailPage: (postId: string) => void; + goToPostDetailPage: (postId: string, hideTarget?: boolean) => void; goToCommunityProfilePage: (communityId: string) => void; goToSocialGlobalSearchPage: (tab?: string) => void; goToMyCommunitiesSearchPage: () => void; @@ -177,7 +178,7 @@ let defaultValue: ContextValue = { onEditUser: (userId: string) => {}, onMessageUser: (userId: string) => {}, goToUserProfilePage: (userId: string) => {}, - goToPostDetailPage: (postId: string) => {}, + goToPostDetailPage: (postId: string, hideTarget?: boolean) => {}, goToViewStoryPage: (context: { targetId: string; targetType: Amity.StoryTargetType; @@ -227,7 +228,8 @@ if (process.env.NODE_ENV !== 'production') { onBack: () => console.log('NavigationContext onBack()'), goToUserProfilePage: (userId) => console.log(`NavigationContext goToUserProfilePage(${userId})`), - goToPostDetailPage: (postId) => console.log(`NavigationContext goToPostDetailPage(${postId})`), + goToPostDetailPage: (postId, hideTarget) => + console.log(`NavigationContext goToPostDetailPage(${postId} ${hideTarget})`), goToCommunityProfilePage: (communityId) => console.log(`NavigationContext goToCommunityProfilePage(${communityId})`), goToSocialGlobalSearchPage: (tab) => @@ -360,7 +362,7 @@ export default function NavigationProvider({ const handleClickCommunity = useCallback( (communityId) => { const next = { - type: PageTypes.CommunityFeed, + type: PageTypes.CommunityProfilePage, context: { communityId, }, @@ -508,11 +510,12 @@ export default function NavigationProvider({ ); const goToPostDetailPage = useCallback( - (postId) => { + (postId, hideTarget) => { const next = { type: PageTypes.PostDetailPage, context: { postId, + hideTarget, }, }; @@ -524,7 +527,7 @@ export default function NavigationProvider({ const goToCommunityProfilePage = useCallback( (communityId) => { const next = { - type: PageTypes.CommunityFeed, + type: PageTypes.CommunityProfilePage, context: { communityId, }, diff --git a/src/v4/core/providers/PageBehaviorProvider.tsx b/src/v4/core/providers/PageBehaviorProvider.tsx index a8a3f1f81..01d293b91 100644 --- a/src/v4/core/providers/PageBehaviorProvider.tsx +++ b/src/v4/core/providers/PageBehaviorProvider.tsx @@ -54,6 +54,20 @@ export interface PageBehavior { AmityPostComposerPageBehavior?: { goToSocialHomePage?(): void; }; + AmityCommunityProfilePageBehavior?: { + goToPostComposerPage?(context: { + mode: Mode.CREATE | Mode.EDIT; + targetId: string | null; + targetType: 'community' | 'user'; + community?: Amity.Community; + post?: Amity.Post; + }): void; + goToPostDetailPage?(context: { postId: string; hideTarget?: boolean }): void; + goToCreateStoryPage?(context: { communityId: string }): void; + // goToCommunitySettingPage?(context: { communityId: string }): void; + // goToPendingPostPage?(context: { communityId: string }): void; + // goToMemberListPage?(context: { communityId: string }): void; + }; } const PageBehaviorContext = React.createContext<PageBehavior | undefined>(undefined); @@ -212,6 +226,50 @@ export const PageBehaviorProvider: React.FC<PageBehaviorProviderProps> = ({ goToSocialHomePage(); }, }, + AmityCommunityProfilePageBehavior: { + goToPostComposerPage(context: { + mode: Mode.CREATE; + targetId: string | null; + targetType: 'community' | 'user'; + community?: Amity.Community; + post?: Amity.Post; + }) { + if (pageBehavior?.AmityCommunityProfilePageBehavior?.goToPostComposerPage) { + return pageBehavior.AmityCommunityProfilePageBehavior.goToPostComposerPage(context); + } + goToPostComposerPage(context); + }, + goToPostDetailPage(context: { postId: string; hideTarget?: boolean }) { + if (pageBehavior?.AmityCommunityProfilePageBehavior?.goToPostDetailPage) { + return pageBehavior.AmityCommunityProfilePageBehavior.goToPostDetailPage(context); + } + goToPostDetailPage(context.postId, context.hideTarget); + }, + // goToPendingPostPage(context) { + // if (pageBehavior?.AmityCommunityProfilePageBehavior?.goToPendingPostPage) { + // return pageBehavior.AmityCommunityProfilePageBehavior.goToPendingPostPage(context); + // } + // goToPostDetailPage(context); + // }, + // goToCommunitySettingPage(context) { + // if (pageBehavior?.AmityCommunityProfilePageBehavior?.goToCommunitySettingPage) { + // return pageBehavior.AmityCommunityProfilePageBehavior.goToCommunitySettingPage(context); + // } + // goToPostDetailPage(context); + // }, + // goToCreateStoryPage(context) { + // if (pageBehavior?.AmityCommunityProfilePageBehavior?.goToCreateStoryPage) { + // return pageBehavior.AmityCommunityProfilePageBehavior.goToCreateStoryPage(context); + // } + // goToPostDetailPage(context); + // }, + // goToMemberListPage(context) { + // if (pageBehavior?.AmityCommunityProfilePageBehavior?.goToMemberListPage) { + // return pageBehavior.AmityCommunityProfilePageBehavior.goToMemberListPage(context); + // } + // goToPostDetailPage(context); + // }, + }, }; return ( diff --git a/src/v4/icons/CommunityCreatePost.tsx b/src/v4/icons/CommunityCreatePost.tsx new file mode 100644 index 000000000..0729b7846 --- /dev/null +++ b/src/v4/icons/CommunityCreatePost.tsx @@ -0,0 +1,57 @@ +import React from 'react'; + +export const CommunityCreatePostButtonIcon = (props: React.SVGProps<SVGSVGElement>) => { + return ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="81" + height="80" + fill="none" + viewBox="0 0 81 80" + {...props} + > + <g filter="url(#filter0_dd_483_21580)"> + <circle cx="40.5" cy="36" r="32" fill="#1054DE"></circle> + </g> + <path + fill="#fff" + d="M50.125 35.036c.438 0 .875.437.875.875v1.75c0 .492-.438.875-.875.875H42.25v7.875c0 .492-.438.875-.875.875h-1.75a.864.864 0 01-.875-.875v-7.875h-7.875A.864.864 0 0130 37.66v-1.75c0-.438.383-.875.875-.875h7.875V27.16c0-.438.383-.875.875-.875h1.75c.438 0 .875.437.875.875v7.875h7.875z" + ></path> + <defs> + <filter + id="filter0_dd_483_21580" + width="80" + height="80" + x="0.5" + y="0" + colorInterpolationFilters="sRGB" + filterUnits="userSpaceOnUse" + > + <feFlood floodOpacity="0" result="BackgroundImageFix"></feFlood> + <feColorMatrix + in="SourceAlpha" + result="hardAlpha" + values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" + ></feColorMatrix> + <feOffset dy="4"></feOffset> + <feGaussianBlur stdDeviation="4"></feGaussianBlur> + <feColorMatrix values="0 0 0 0 0.376471 0 0 0 0 0.380392 0 0 0 0 0.439216 0 0 0 0.2 0"></feColorMatrix> + <feBlend in2="BackgroundImageFix" result="effect1_dropShadow_483_21580"></feBlend> + <feColorMatrix + in="SourceAlpha" + result="hardAlpha" + values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" + ></feColorMatrix> + <feOffset></feOffset> + <feGaussianBlur stdDeviation="1"></feGaussianBlur> + <feColorMatrix values="0 0 0 0 0.156863 0 0 0 0 0.160784 0 0 0 0 0.239216 0 0 0 0.1 0"></feColorMatrix> + <feBlend + in2="effect1_dropShadow_483_21580" + result="effect2_dropShadow_483_21580" + ></feBlend> + <feBlend in="SourceGraphic" in2="effect2_dropShadow_483_21580" result="shape"></feBlend> + </filter> + </defs> + </svg> + ); +}; diff --git a/src/v4/icons/Ellipsis.tsx b/src/v4/icons/Ellipsis.tsx new file mode 100644 index 000000000..1c6405a98 --- /dev/null +++ b/src/v4/icons/Ellipsis.tsx @@ -0,0 +1,14 @@ +import React from 'react'; + +export const EllipsisH = ({ ...props }: React.SVGProps<SVGSVGElement>) => ( + <svg + width="16" + height="4" + viewBox="0 0 16 4" + fill="none" + xmlns="http://www.w3.org/2000/svg" + {...props} + > + <path d="M9.6875 2.25C9.6875 3.19922 8.91406 3.9375 8 3.9375C7.05078 3.9375 6.3125 3.19922 6.3125 2.25C6.3125 1.33594 7.05078 0.5625 8 0.5625C8.91406 0.5625 9.6875 1.33594 9.6875 2.25ZM13.9062 0.5625C14.8203 0.5625 15.5938 1.33594 15.5938 2.25C15.5938 3.19922 14.8203 3.9375 13.9062 3.9375C12.957 3.9375 12.2188 3.19922 12.2188 2.25C12.2188 1.33594 12.957 0.5625 13.9062 0.5625ZM2.09375 0.5625C3.00781 0.5625 3.78125 1.33594 3.78125 2.25C3.78125 3.19922 3.00781 3.9375 2.09375 3.9375C1.14453 3.9375 0.40625 3.19922 0.40625 2.25C0.40625 1.33594 1.14453 0.5625 2.09375 0.5625Z" /> + </svg> +); diff --git a/src/v4/icons/EmptyPost.tsx b/src/v4/icons/EmptyPost.tsx new file mode 100644 index 000000000..b9a9e6b8f --- /dev/null +++ b/src/v4/icons/EmptyPost.tsx @@ -0,0 +1,16 @@ +import React from 'react'; + +const EmptyPost = ({ ...props }: React.SVGProps<SVGSVGElement>) => ( + <svg + width="61" + height="41" + viewBox="0 0 61 41" + fill="none" + xmlns="http://www.w3.org/2000/svg" + {...props} + > + <path d="M58.3438 0.5C59.7148 0.5 60.875 1.66016 60.875 3.03125V38.4688C60.875 39.9453 59.7148 41 58.3438 41H5.1875C2.33984 41 0.125 38.7852 0.125 35.9375V6.40625C0.125 5.03516 1.17969 3.875 2.65625 3.875H6.875V3.03125C6.875 1.66016 7.92969 0.5 9.40625 0.5H58.3438ZM3.5 35.9375C3.5 36.8867 4.23828 37.625 5.1875 37.625C6.03125 37.625 6.875 36.8867 6.875 35.9375V7.25H3.5V35.9375ZM57.5 37.625V3.875H10.25V35.9375V36.043C10.25 36.4648 10.0391 37.2031 9.93359 37.625H57.5ZM14.8906 27.5C14.1523 27.5 13.625 26.9727 13.625 26.2344V8.51562C13.625 7.88281 14.1523 7.25 14.8906 7.25H32.6094C33.2422 7.25 33.875 7.88281 33.875 8.51562V26.2344C33.875 26.9727 33.2422 27.5 32.6094 27.5H14.8906ZM17 10.625V24.125H30.5V10.625H17ZM13.625 32.9844V32.1406C13.625 31.5078 14.1523 30.875 14.8906 30.875H32.6094C33.2422 30.875 33.875 31.5078 33.875 32.1406V32.9844C33.875 33.7227 33.2422 34.25 32.6094 34.25H14.8906C14.1523 34.25 13.625 33.7227 13.625 32.9844ZM37.25 32.9844V32.1406C37.25 31.5078 37.7773 30.875 38.5156 30.875H52.8594C53.4922 30.875 54.125 31.5078 54.125 32.1406V32.9844C54.125 33.7227 53.4922 34.25 52.8594 34.25H38.5156C37.7773 34.25 37.25 33.7227 37.25 32.9844ZM37.25 26.2344V25.3906C37.25 24.7578 37.7773 24.125 38.5156 24.125H52.8594C53.4922 24.125 54.125 24.7578 54.125 25.3906V26.2344C54.125 26.9727 53.4922 27.5 52.8594 27.5H38.5156C37.7773 27.5 37.25 26.9727 37.25 26.2344ZM37.25 12.7344V11.8906C37.25 11.2578 37.7773 10.625 38.5156 10.625H52.8594C53.4922 10.625 54.125 11.2578 54.125 11.8906V12.7344C54.125 13.4727 53.4922 14 52.8594 14H38.5156C37.7773 14 37.25 13.4727 37.25 12.7344ZM37.25 19.4844V18.6406C37.25 18.0078 37.7773 17.375 38.5156 17.375H52.8594C53.4922 17.375 54.125 18.0078 54.125 18.6406V19.4844C54.125 20.2227 53.4922 20.75 52.8594 20.75H38.5156C37.7773 20.75 37.25 20.2227 37.25 19.4844Z" /> + </svg> +); + +export default EmptyPost; diff --git a/src/v4/icons/Feed.tsx b/src/v4/icons/Feed.tsx new file mode 100644 index 000000000..cabe072fc --- /dev/null +++ b/src/v4/icons/Feed.tsx @@ -0,0 +1,12 @@ +import React from 'react'; + +export const Feed = () => { + return ( + <svg xmlns="http://www.w3.org/2000/svg" width="25" height="24" fill="none" viewBox="0 0 25 24"> + <path + fill="currentColor" + d="M9.219 14.5c-.492 0-.844-.352-.844-.844 0-.457.352-.844.844-.844h7.312c.457 0 .844.387.844.844a.833.833 0 01-.844.844H9.22zm0 3.375c-.492 0-.844-.352-.844-.844 0-.457.352-.843.844-.843h3.937c.457 0 .844.386.844.843a.833.833 0 01-.844.844H9.22zm0-6.75c-.492 0-.844-.352-.844-.844 0-.457.352-.844.844-.844h7.312c.457 0 .844.387.844.844a.833.833 0 01-.844.844H9.22zM18.5 3.25c1.23 0 2.25 1.02 2.25 2.25V19c0 1.266-1.02 2.25-2.25 2.25H7.25A2.221 2.221 0 015 19V5.5c0-1.23.984-2.25 2.25-2.25H18.5zM19.063 19V7.75H6.688V19c0 .316.246.563.562.563H18.5a.578.578 0 00.563-.563z" + ></path> + </svg> + ); +}; diff --git a/src/v4/icons/Lock.tsx b/src/v4/icons/Lock.tsx new file mode 100644 index 000000000..d597b5928 --- /dev/null +++ b/src/v4/icons/Lock.tsx @@ -0,0 +1,16 @@ +import React from 'react'; + +const Lock = ({ ...props }: React.SVGProps<SVGSVGElement>) => ( + <svg + width="61" + height="61" + viewBox="0 0 61 61" + fill="none" + xmlns="http://www.w3.org/2000/svg" + {...props} + > + <path d="M33.0312 44.25C33.0312 45.7266 31.8711 46.7812 30.5 46.7812C29.0234 46.7812 27.9688 45.7266 27.9688 44.25V37.5C27.9688 36.1289 29.0234 34.9688 30.5 34.9688C31.8711 34.9688 33.0312 36.1289 33.0312 37.5V44.25ZM30.5 3.75C37.8828 3.75 44 9.86719 44 17.25V24H47.375C51.0664 24 54.125 27.0586 54.125 30.75V51C54.125 54.7969 51.0664 57.75 47.375 57.75H13.625C9.82812 57.75 6.875 54.7969 6.875 51V30.75C6.875 27.0586 9.82812 24 13.625 24H17V17.25C17 9.86719 23.0117 3.75 30.5 3.75ZM30.5 8.8125C25.7539 8.8125 22.0625 12.6094 22.0625 17.25V24H38.9375V17.25C38.9375 12.6094 35.1406 8.8125 30.5 8.8125ZM13.625 29.0625C12.6758 29.0625 11.9375 29.9062 11.9375 30.75V51C11.9375 51.9492 12.6758 52.6875 13.625 52.6875H47.375C48.2188 52.6875 49.0625 51.9492 49.0625 51V30.75C49.0625 29.9062 48.2188 29.0625 47.375 29.0625H13.625Z" /> + </svg> +); + +export default Lock; diff --git a/src/v4/icons/Pin.tsx b/src/v4/icons/Pin.tsx new file mode 100644 index 000000000..18d3cd815 --- /dev/null +++ b/src/v4/icons/Pin.tsx @@ -0,0 +1,12 @@ +import React from 'react'; + +export const Pin = () => { + return ( + <svg xmlns="http://www.w3.org/2000/svg" width="35" height="34" fill="none" viewBox="0 0 35 34"> + <path + fill="currentColor" + d="M21.998 18.295c.372 2.014-.15 4.027-1.567 5.444a.833.833 0 01-1.193 0l-3.58-3.58-2.585 2.586c-.025.025-.074.025-.1.05l-1.59.795c-.224.125-.497-.149-.373-.373l.795-1.59.075-.075 2.585-2.585-3.58-3.58c-.348-.348-.348-.845 0-1.194 1.418-1.416 3.406-1.963 5.42-1.59l1.218-.92-.671-.671c-.348-.348-.348-.846 0-1.194l2.386-2.386a.833.833 0 011.194 0l6.761 6.761a.856.856 0 010 1.194l-2.386 2.386a.833.833 0 01-1.194 0l-.696-.696-.92 1.218zm-9.273-3.455l7.035 7.035c.746-1.143.845-2.635.398-3.928l2.61-3.405 1.442 1.441 1.193-1.193-5.568-5.568-1.193 1.193 1.442 1.442-3.406 2.61c-1.318-.472-2.81-.373-3.953.373z" + ></path> + </svg> + ); +}; diff --git a/src/v4/icons/PinBadge.tsx b/src/v4/icons/PinBadge.tsx new file mode 100644 index 000000000..836bb3346 --- /dev/null +++ b/src/v4/icons/PinBadge.tsx @@ -0,0 +1,19 @@ +import React from 'react'; + +export const PinBadgeIcon = (props: React.SVGProps<SVGSVGElement>) => { + return ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="29" + height="30" + fill="none" + viewBox="0 0 29 30" + {...props} + > + <path + fill="currentColor" + d="M18.106 16.436c.575 1.547.53 3.27-.596 4.398a.74.74 0 01-1.06 0l-3.006-3.005-2.32 2.32c-.022.022-.067.022-.089.044l-1.59.53c-.2.067-.398-.132-.332-.331l.53-1.591.066-.066 2.32-2.32-3.005-3.006a.722.722 0 010-1.06c1.105-1.105 2.829-1.194 4.376-.62l2.917-2.342-.929-.928a.722.722 0 010-1.06l1.061-1.06a.74.74 0 011.06 0l6.011 6.01a.76.76 0 010 1.06l-1.06 1.06a.74.74 0 01-1.061 0l-.95-.95-2.343 2.917z" + ></path> + </svg> + ); +}; diff --git a/src/v4/icons/Plus.tsx b/src/v4/icons/Plus.tsx new file mode 100644 index 000000000..f3cd3c3db --- /dev/null +++ b/src/v4/icons/Plus.tsx @@ -0,0 +1,18 @@ +import React from 'react'; + +export const Plus = (props: React.SVGProps<SVGSVGElement>) => ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="100%" + height="100%" + viewBox="0 0 384 512" + fill="currentColor" + {...props} + > + <path + d="M368 224H224V80c0-8.84-7.16-16-16-16h-32c-8.84 0-16 7.16-16 16v144H16c-8.84 0-16 7.16-16 + 16v32c0 8.84 7.16 16 16 16h144v144c0 8.84 7.16 16 16 16h32c8.84 0 16-7.16 16-16V288h144c8.84 + 0 16-7.16 16-16v-32c0-8.84-7.16-16-16-16z" + /> + </svg> +); diff --git a/src/v4/icons/RefreshSpinner.tsx b/src/v4/icons/RefreshSpinner.tsx new file mode 100644 index 000000000..32b194914 --- /dev/null +++ b/src/v4/icons/RefreshSpinner.tsx @@ -0,0 +1,41 @@ +import React from 'react'; + +type RefreshSpinnerProps = { + props?: React.SVGProps<SVGSVGElement>; + className?: string; +}; + +export const RefreshSpinner = ({ props, className }: RefreshSpinnerProps) => { + return ( + <div className={className}> + <svg + xmlns="http://www.w3.org/2000/svg" + width="21" + height="20" + viewBox="0 0 21 20" + fill="none" + {...props} + > + <path + fillRule="evenodd" + clipRule="evenodd" + d="M11.1122 5C11.1122 5.39449 10.7924 5.71429 10.3979 5.71429C10.0035 5.71429 9.68366 5.39449 9.68366 5V0.714286C9.68366 0.319797 10.0035 0 10.3979 0C10.7924 0 11.1122 0.319797 11.1122 0.714286V5ZM8.25509 6.28846C8.59673 6.09122 8.71378 5.65437 8.51654 5.31273L6.37368 1.60119C6.17644 1.25955 5.73959 1.1425 5.39795 1.33975C5.05631 1.53699 4.93926 1.97384 5.1365 2.31548L7.27936 6.02702C7.4766 6.36865 7.91346 6.48571 8.25509 6.28846ZM6.42496 6.88141C6.7666 7.07865 6.88366 7.51551 6.68641 7.85714C6.48917 8.19878 6.05232 8.31583 5.71068 8.11859L1.99914 5.97573C1.6575 5.77849 1.54045 5.34164 1.7377 5C1.93494 4.65836 2.37179 4.54131 2.71343 4.73855L6.42496 6.88141ZM6.11224 10C6.11224 9.60551 5.79244 9.28571 5.39795 9.28571H1.11223C0.717746 9.28571 0.397949 9.60551 0.397949 10C0.397949 10.3945 0.717746 10.7143 1.11224 10.7143H5.39795C5.79244 10.7143 6.11224 10.3945 6.11224 10ZM5.71068 11.8814C6.05232 11.6842 6.48917 11.8012 6.68641 12.1429C6.88366 12.4845 6.7666 12.9213 6.42497 13.1186L2.71343 15.2614C2.37179 15.4587 1.93494 15.3416 1.7377 15C1.54045 14.6584 1.6575 14.2215 1.99914 14.0243L5.71068 11.8814ZM8.25509 13.7115C7.91345 13.5143 7.4766 13.6313 7.27936 13.973L5.1365 17.6845C4.93926 18.0262 5.05631 18.463 5.39795 18.6603C5.73959 18.8575 6.17644 18.7404 6.37368 18.3988L8.51654 14.6873C8.71378 14.3456 8.59673 13.9088 8.25509 13.7115ZM10.3979 14.2857C10.0035 14.2857 9.68366 14.6055 9.68366 15V19.2857C9.68366 19.6802 10.0035 20 10.3979 20C10.7924 20 11.1122 19.6802 11.1122 19.2857V15C11.1122 14.6055 10.7924 14.2857 10.3979 14.2857ZM12.5408 6.28846C12.8824 6.48571 13.3193 6.36865 13.5165 6.02702L15.6594 2.31548C15.8566 1.97384 15.7396 1.53699 15.3979 1.33975C15.0563 1.1425 14.6195 1.25956 14.4222 1.60119L12.2794 5.31273C12.0821 5.65437 12.1992 6.09122 12.5408 6.28846ZM15.0852 8.11859C14.7436 8.31583 14.3067 8.19878 14.1095 7.85714C13.9122 7.51551 14.0293 7.07866 14.3709 6.88141L18.0825 4.73855C18.4241 4.54131 18.861 4.65836 19.0582 5C19.2554 5.34164 19.1384 5.77849 18.7968 5.97573L15.0852 8.11859ZM14.6837 10C14.6837 10.3945 15.0035 10.7143 15.3979 10.7143H19.6837C20.0782 10.7143 20.3979 10.3945 20.3979 10C20.3979 9.60551 20.0782 9.28571 19.6837 9.28571H15.3979C15.0035 9.28571 14.6837 9.60551 14.6837 10ZM14.3709 13.1186C14.0293 12.9213 13.9122 12.4845 14.1095 12.1429C14.3067 11.8012 14.7436 11.6842 15.0852 11.8814L18.7968 14.0243C19.1384 14.2215 19.2554 14.6584 19.0582 15C18.861 15.3416 18.4241 15.4587 18.0825 15.2614L14.3709 13.1186ZM12.5408 13.7115C12.1992 13.9088 12.0821 14.3456 12.2794 14.6873L14.4222 18.3988C14.6195 18.7404 15.0563 18.8575 15.3979 18.6603C15.7396 18.463 15.8566 18.0262 15.6594 17.6845L13.5165 13.973C13.3193 13.6313 12.8824 13.5143 12.5408 13.7115Z" + fill="url(#paint0_angular_1709_10374)" + /> + <defs> + <radialGradient + id="paint0_angular_1709_10374" + cx="0" + cy="0" + r="1" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(10.3979 10) scale(10)" + > + <stop offset="0.669733" stopColor="#595F67" /> + <stop offset="0.716307" stopColor="#262626" stopOpacity="0.01" /> + </radialGradient> + </defs> + </svg> + </div> + ); +}; diff --git a/src/v4/icons/VerifyBadge.tsx b/src/v4/icons/VerifyBadge.tsx new file mode 100644 index 000000000..888379a75 --- /dev/null +++ b/src/v4/icons/VerifyBadge.tsx @@ -0,0 +1,13 @@ +import React from 'react'; + +export const VerifyBadgeIcon = () => { + return ( + <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none"> + <circle cx="9.88525" cy="10" r="5.5" fill="white" /> + <path + d="M8.62899 17.3036C9.55401 18.2356 10.4373 18.2286 11.3693 17.3036L12.4125 16.2604C12.5099 16.163 12.5934 16.1352 12.7185 16.1352H14.193C15.5075 16.1352 16.1334 15.5092 16.1334 14.1947V12.7203C16.1334 12.5951 16.1682 12.5047 16.2586 12.4143L17.3019 11.3641C18.2339 10.439 18.2269 9.55575 17.3019 8.63073L16.2586 7.58748C16.1682 7.49011 16.1334 7.40665 16.1334 7.28146V5.807C16.1334 4.4925 15.5075 3.86655 14.193 3.86655H12.7185C12.5934 3.86655 12.5029 3.83178 12.4125 3.74136L11.3693 2.69811C10.4373 1.76614 9.55401 1.76614 8.62899 2.70507L7.58574 3.74136C7.49533 3.83178 7.40491 3.86655 7.27972 3.86655H5.80526C4.49076 3.86655 3.86481 4.47859 3.86481 5.807V7.28146C3.86481 7.40665 3.83004 7.49707 3.73962 7.58748L2.69637 8.63073C1.7644 9.55575 1.77136 10.439 2.69637 11.3641L3.73962 12.4143C3.83004 12.5047 3.86481 12.5951 3.86481 12.7203V14.1947C3.86481 15.5092 4.49076 16.1352 5.80526 16.1352H7.27972C7.40491 16.1352 7.48837 16.163 7.58574 16.2604L8.62899 17.3036ZM8.8446 13.5618C8.60117 13.5618 8.40643 13.4714 8.25342 13.3115L6.02782 10.8216C5.90959 10.6964 5.84699 10.5225 5.84699 10.3486C5.84699 9.93132 6.14606 9.63225 6.57727 9.63225C6.81374 9.63225 6.98761 9.70876 7.14062 9.87568L8.81678 11.7466L12.1691 6.96153C12.336 6.72506 12.5168 6.62769 12.795 6.62769C13.2263 6.62769 13.5323 6.92676 13.5323 7.33015C13.5323 7.46925 13.4766 7.64312 13.3723 7.78918L9.47055 13.2628C9.31059 13.4714 9.10194 13.5618 8.8446 13.5618Z" + fill="#1054DE" + /> + </svg> + ); +}; diff --git a/src/v4/social/components/CommentComposer/CommentInput.tsx b/src/v4/social/components/CommentComposer/CommentInput.tsx index ac13d1abe..a7f5d0b10 100644 --- a/src/v4/social/components/CommentComposer/CommentInput.tsx +++ b/src/v4/social/components/CommentComposer/CommentInput.tsx @@ -51,6 +51,8 @@ interface CommentInputProps { mentionOffsetBottom?: number; maxLines?: number; placehoder?: string; + targetType?: string; + targetId?: string; ref: MutableRefObject<LexicalEditor | null | undefined>; onChange: (data: CreateCommentParams) => void; } diff --git a/src/v4/social/components/CommunityCreatePostButton/CommunityCreatePostButton.tsx b/src/v4/social/components/CommunityCreatePostButton/CommunityCreatePostButton.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/src/v4/social/components/CommunityCreatePostButton/index.ts b/src/v4/social/components/CommunityCreatePostButton/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/v4/social/components/CommunityFeed/CommunityFeed.module.css b/src/v4/social/components/CommunityFeed/CommunityFeed.module.css new file mode 100644 index 000000000..de4f60faa --- /dev/null +++ b/src/v4/social/components/CommunityFeed/CommunityFeed.module.css @@ -0,0 +1,146 @@ +.communityFeed__container { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.communityFeed__loadingIndicator { + display: flex; + justify-content: center; + padding: var(--asc-spacing-m1) 0; +} + +.communityFeed__observerTarget { + height: var(--asc-spacing-m1); + width: 100%; +} + +.communityFeed__post { + margin-bottom: var(--asc-spacing-m1); + border-radius: var(--asc-border-radius-md); + overflow: hidden; +} + +.communityFeed__spinner { + animation: spin 1s linear infinite; +} + +.communityFeed__postContent { + padding-bottom: 0.75rem; + background-color: var(--asc-color-base-shade4); +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } +} + +.communityFeed__postSkeleton { + background-color: var(--asc-color-background-default); + padding: 1rem; + box-shadow: 0 0.125rem 0.25rem rgb(0 0 0 / 10%); +} + +.communityFeed__postSkeletonHeader { + display: flex; + align-items: center; + margin-bottom: 1rem; +} + +.communityFeed__postSkeletonAvatar { + width: 2.5rem; + height: 2.5rem; + border-radius: 50%; + background-color: var(--asc-color-base-shade4); + margin-right: 0.75rem; +} + +.communityFeed__postSkeletonUserInfo { + display: flex; + flex-direction: column; +} + +.communityFeed__postSkeletonUsername { + width: 6rem; + height: 1rem; + background-color: var(--asc-color-base-shade4); + margin-bottom: 0.5rem; + border-radius: 0.25rem; +} + +.communityFeed__postSkeletonSubtitle { + width: 4rem; + height: 0.75rem; + background-color: var(--asc-color-base-shade4); + border-radius: 0.25rem; +} + +.communityFeed__postSkeletonContent { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.communityFeed__postSkeletonLine { + height: 0.75rem; + background-color: var(--asc-color-base-shade4); + border-radius: 0.25rem; + width: 100%; +} + +.communityFeed__postSkeletonLine:first-child { + width: 75%; +} + +.communityFeed__postSkeletonLine:nth-child(2) { + width: 100%; +} + +.communityFeed__postSkeletonLine:last-child { + width: 50%; +} + +@keyframes pulse { + 0% { + opacity: 1; + } + + 50% { + opacity: 0.5; + } + + 100% { + opacity: 1; + } +} + +.communityFeed__postSkeletonAvatar, +.communityFeed__postSkeletonUsername, +.communityFeed__postSkeletonSubtitle, +.communityFeed__postSkeletonLine { + animation: pulse 1.5s ease-in-out infinite; +} + +.communityFeed__emptyPost { + margin: 5rem auto 3rem; + text-align: center; +} + +.communityFeed__emptyPostIcon { + fill: var(--asc-color-base-shade4); + width: 3.75rem; + height: 2.8125rem; +} + +.communityFeed__emptyPostText { + color: var(--asc-color-base-shade3); + font-size: 1rem; + font-weight: 600; + line-height: 1.375rem; + margin-top: 0.5rem; +} diff --git a/src/v4/social/components/CommunityFeed/CommunityFeed.tsx b/src/v4/social/components/CommunityFeed/CommunityFeed.tsx new file mode 100644 index 000000000..62cfdb202 --- /dev/null +++ b/src/v4/social/components/CommunityFeed/CommunityFeed.tsx @@ -0,0 +1,144 @@ +import React, { useEffect, useRef } from 'react'; +import { useAmityComponent } from '~/v4/core/hooks/uikit'; +import { PostContent } from '~/v4/social/components/PostContent'; +import { + AmityPostCategory, + AmityPostContentComponentStyle, +} from '~/v4/social/components/PostContent/PostContent'; +import usePostsCollection from '~/v4/social/hooks/collections/usePostsCollection'; +import styles from './CommunityFeed.module.css'; +import EmptyPost from '~/v4/icons/EmptyPost'; +import useCommunity from '~/v4/core/hooks/collections/useCommunity'; +import LockPrivateContent from '~/v4/social/internal-components/LockPrivateContent'; +import { SubscriptionLevels } from '@amityco/ts-sdk'; +import useCommunitySubscription from '~/v4/core/hooks/subscriptions/useCommunitySubscription'; +import { Button } from '~/v4/core/natives/Button'; +import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; + +const CommunityFeedPostContentSkeleton = () => { + return ( + <div className={styles.communityFeed__postSkeleton}> + <div className={styles.communityFeed__postSkeletonHeader}> + <div className={styles.communityFeed__postSkeletonAvatar}></div> + <div className={styles.communityFeed__postSkeletonUserInfo}> + <div className={styles.communityFeed__postSkeletonUsername}></div> + <div className={styles.communityFeed__postSkeletonSubtitle}></div> + </div> + </div> + <div className={styles.communityFeed__postSkeletonContent}> + <div className={styles.communityFeed__postSkeletonLine}></div> + <div className={styles.communityFeed__postSkeletonLine}></div> + <div className={styles.communityFeed__postSkeletonLine}></div> + </div> + </div> + ); +}; + +interface CommunityFeedProps { + communityId: string; + pageId?: string; +} + +export const CommunityFeed = ({ pageId = '*', communityId }: CommunityFeedProps) => { + const componentId = 'community_feed_component'; + const { isExcluded, accessibilityId, themeStyles } = useAmityComponent({ + pageId, + componentId, + }); + + const { community } = useCommunity({ communityId, shouldCall: !!communityId }); + + const isMemberPrivateCommunity = community?.isJoined && !community?.isPublic; + + const { posts, hasMore, loadMore, isLoading } = usePostsCollection({ + feedType: 'published', + targetId: communityId, + targetType: 'community', + limit: 10, + }); + + const { AmityCommunityProfilePageBehavior } = usePageBehavior(); + + useCommunitySubscription({ communityId, level: SubscriptionLevels.POST }); + + const observerTarget = useRef(null); + + useEffect(() => { + const observer = new IntersectionObserver( + async (entries) => { + if (entries[0].isIntersecting && hasMore) { + loadMore(); + } + }, + { threshold: 0.5 }, + ); + + if (observerTarget.current) { + observer.observe(observerTarget.current); + } + + return () => { + if (observerTarget.current) { + observer.unobserve(observerTarget.current); + } + }; + }, [hasMore, loadMore]); + + if (isExcluded) return null; + + const renderPublicCommunityFeed = () => ( + <> + {posts && + posts.map((post) => ( + <Button + className={styles.communityFeed__postContent} + onPress={() => + AmityCommunityProfilePageBehavior?.goToPostDetailPage?.({ + postId: post.postId, + hideTarget: true, + }) + } + > + <PostContent + key={post.postId} + post={post} + category={AmityPostCategory.GENERAL} + style={AmityPostContentComponentStyle.FEED} + hideTarget + onClick={() => + AmityCommunityProfilePageBehavior?.goToPostDetailPage?.({ + postId: post.postId, + hideTarget: true, + }) + } + /> + </Button> + ))} + {isLoading && + Array.from({ length: 2 }).map((_, index) => ( + <CommunityFeedPostContentSkeleton key={index} /> + ))} + {posts?.length === 0 && !isLoading && ( + <div className={styles.communityFeed__emptyPost}> + <EmptyPost className={styles.communityFeed__emptyPostIcon} /> + <p className={styles.communityFeed__emptyPostText}>No post yet</p> + </div> + )} + <div ref={observerTarget} className={styles.communityFeed__observerTarget} /> + </> + ); + + return ( + <div + data-qa-anchor={accessibilityId} + className={styles.communityFeed__container} + style={themeStyles} + > + {isMemberPrivateCommunity || community?.isPublic ? ( + renderPublicCommunityFeed() + ) : ( + <LockPrivateContent /> + )} + </div> + ); +}; diff --git a/src/v4/social/components/CommunityFeed/index.ts b/src/v4/social/components/CommunityFeed/index.ts new file mode 100644 index 000000000..e88887fef --- /dev/null +++ b/src/v4/social/components/CommunityFeed/index.ts @@ -0,0 +1 @@ +export { CommunityFeed } from './CommunityFeed'; diff --git a/src/v4/social/components/CommunityHeader/CommunityHeader.module.css b/src/v4/social/components/CommunityHeader/CommunityHeader.module.css new file mode 100644 index 000000000..fb6850695 --- /dev/null +++ b/src/v4/social/components/CommunityHeader/CommunityHeader.module.css @@ -0,0 +1,134 @@ +.container { + width: 100%; + margin: 0 auto; + background: var(--asc-color-background-default); + overflow: hidden; +} + +.headerImageContainer { + position: relative; + height: 17.0625rem; + overflow: hidden; + border-top-left-radius: 0.5rem; + border-top-right-radius: 0.5rem; +} + +.headerImage { + width: 100%; + height: 100%; + object-fit: cover; +} + +.backButton, +.moreButton { + position: absolute; + top: 0.625rem; + background: rgb(0 0 0 / 50%); + color: var(--asc-color-white); + border: none; + border-radius: 50%; + width: 2rem; + height: 2rem; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; +} + +.backButton { + left: 0.625rem; +} + +.moreButton { + right: 0.625rem; +} + +.content { + width: 100%; + display: flex; + flex-direction: column; + padding: 1rem 1rem 0.75rem; + align-items: flex-start; + gap: 0.625rem; + overflow: hidden; +} + +.name { + display: inline-flex; + align-items: center; + gap: 0.5rem; + color: var(--asc-color-base-default); +} + +.description { + overflow: hidden; + color: var(--asc-color-base-default); + margin-bottom: var(--asc-spacing-s2); +} + +.communityProfile__communityInfo__container { + display: flex; + gap: var(--asc-spacing-s1); +} + +.divider { + width: 1px; + background-color: var(--asc-color-base-shade3); + margin-left: var(--asc-spacing-s1); + margin-right: var(--asc-spacing-s1); +} + +.statCount { + color: var(--asc-color-base-default); +} + +.statTitle { + color: var(--asc-color-base-shade2); +} + +.menuButton { + display: flex; + padding: 0.5rem; + justify-content: center; + align-items: center; + gap: 0.5rem; + border-radius: 0.5rem; + border: 1px solid var(--asc-color-base-shade3); +} + +.communityProfile__joinButton__container { + width: 100%; + display: flex; + justify-content: center; + align-items: center; + color: var(--asc-color-primary-shade4); +} + +.communityProfile__joinButton { + width: 100%; + background-color: var(--asc-color-primary-default); + color: var(--asc-color-secondary-default); + padding: 0.625rem 1rem 0.625rem 0.75rem; + border-radius: 0.5rem; + cursor: pointer; + text-align: center; +} + +.communityProfile__pendingPost__container { + width: 100%; +} + +.communityProfile__menuItem { + display: flex; + padding: 0.75rem 1rem; + align-items: flex-start; + gap: 0.75rem; + align-self: stretch; + background: var(--asc-color-background-shade1); +} + +.communityProfile__privateIcon { + width: 1.25rem; + height: 1rem; + fill: var(--asc-color-base-default); +} diff --git a/src/v4/social/components/CommunityHeader/CommunityHeader.tsx b/src/v4/social/components/CommunityHeader/CommunityHeader.tsx new file mode 100644 index 000000000..7a9d66035 --- /dev/null +++ b/src/v4/social/components/CommunityHeader/CommunityHeader.tsx @@ -0,0 +1,91 @@ +import React from 'react'; +import styles from './CommunityHeader.module.css'; +import { StoryTab } from '~/v4/social/components/StoryTab'; +import { CommunityPendingPost } from '~/v4/social/elements/CommunityPendingPost'; +import { CommunityProfileTab } from '~/v4/social/elements/CommunityProfileTab'; +import { CommunityCover } from '~/v4/social/elements/CommunityCover'; +import { CommunityJoinButton } from '~/v4/social/elements/CommunityJoinButton'; +import { useCommunityInfo } from '~/v4/social/hooks/useCommunityInfo'; +import { useCommunityTabContext } from '~/v4/core/providers/CommunityTabProvider'; +import { useNavigation } from '~/v4/core/providers/NavigationProvider'; +import { CommunityVerifyBadge } from '~/v4/social/elements/CommunityVerifyBadge'; +import { CommunityDescription } from '~/v4/social/elements/CommunityDescription'; +import { CommunityName } from '~/v4/social/elements/CommunityName'; +import { CommunityCategory } from '~/v4/social/elements/CommunityCategory'; +import { CommunityInfo } from '~/v4/social/elements/CommunityInfo'; +import Lock from '~/v4/icons/Lock'; + +interface CommunityProfileHeaderProps { + pageId?: string; + community: Amity.Community; +} + +export const CommunityHeader: React.FC<CommunityProfileHeaderProps> = ({ + pageId = '*', + community, +}) => { + const { onBack, onEditCommunity } = useNavigation(); + const { activeTab, setActiveTab } = useCommunityTabContext(); + + const { + communityCategories, + avatarFileUrl, + joinCommunity, + pendingPostsCount, + canReviewCommunityPosts, + } = useCommunityInfo(community.communityId); + + const handleTabChange = (tab: 'community_feed' | 'community_pin') => { + setActiveTab(tab); + }; + + const isShowPendingPost = + community.isJoined && community.postSetting && canReviewCommunityPosts && pendingPostsCount > 0; + + return ( + <div className={styles.container}> + <CommunityCover + pageId={pageId} + image={avatarFileUrl} + onBack={onBack} + onClickMenu={() => onEditCommunity(community.communityId)} + /> + <div className={styles.content}> + <div className={styles.name}> + {!community.isPublic && <Lock className={styles.communityProfile__privateIcon} />} + <CommunityName pageId={pageId} name={community.displayName} /> + {community.isOfficial && <CommunityVerifyBadge />} + </div> + + <CommunityCategory pageId={pageId} categories={communityCategories} /> + + <CommunityDescription pageId={pageId} description={community.description || ''} /> + + <div className={styles.communityProfile__communityInfo__container}> + <CommunityInfo count={community.postsCount} text="posts" /> + <div className={styles.divider}></div> + <CommunityInfo + count={community.membersCount} + text="members" + onClick={() => onEditCommunity(community.communityId, 'MEMBERS')} + /> + </div> + + {!community.isJoined && community.isPublic && ( + <div className={styles.communityProfile__joinButton__container}> + <CommunityJoinButton onClick={joinCommunity} /> + </div> + )} + <div> + <StoryTab type="communityFeed" communityId={community.communityId} /> + </div> + {isShowPendingPost && ( + <div className={styles.communityProfile__pendingPost__container}> + <CommunityPendingPost /> + </div> + )} + <CommunityProfileTab pageId={pageId} activeTab={activeTab} onTabChange={handleTabChange} /> + </div> + </div> + ); +}; diff --git a/src/v4/social/components/CommunityHeader/index.ts b/src/v4/social/components/CommunityHeader/index.ts new file mode 100644 index 000000000..b275a1aa5 --- /dev/null +++ b/src/v4/social/components/CommunityHeader/index.ts @@ -0,0 +1 @@ +export { CommunityHeader } from './CommunityHeader'; diff --git a/src/v4/social/components/CommunityPin/CommunityPin.module.css b/src/v4/social/components/CommunityPin/CommunityPin.module.css new file mode 100644 index 000000000..e3dc19f10 --- /dev/null +++ b/src/v4/social/components/CommunityPin/CommunityPin.module.css @@ -0,0 +1,5 @@ +.communityPin__container { + display: flex; + flex-direction: column; + gap: 0.5rem; +} diff --git a/src/v4/social/components/CommunityPin/CommunityPin.tsx b/src/v4/social/components/CommunityPin/CommunityPin.tsx new file mode 100644 index 000000000..56fe2b157 --- /dev/null +++ b/src/v4/social/components/CommunityPin/CommunityPin.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { useAmityComponent } from '~/v4/core/hooks/uikit'; +import { EmptyPinnedPost } from '~/v4/social/components/EmptyPinnedPost'; +import styles from './CommunityPin.module.css'; +import useCommunity from '~/v4/core/hooks/collections/useCommunity'; +import LockPrivateContent from '~/v4/social/internal-components/LockPrivateContent'; + +interface CommunityPinProps { + pageId?: string; + communityId: string; +} + +export const CommunityPin = ({ pageId = '*', communityId }: CommunityPinProps) => { + const componentId = 'community_pin'; + const { accessibilityId, themeStyles, isExcluded } = useAmityComponent({ + pageId, + componentId, + }); + + if (isExcluded) return null; + + const { community } = useCommunity({ communityId, shouldCall: !!communityId }); + + const isMemberPrivateCommunity = community?.isJoined && !community?.isPublic; + + //TODO : Integrate with the new pinned post API + //TODO : Fix condition to show empty pinned post and lock private content + + return ( + <div + data-qa-anchor={accessibilityId} + className={styles.communityPin__container} + style={themeStyles} + > + {isMemberPrivateCommunity || community?.isPublic ? ( + <EmptyPinnedPost /> + ) : ( + <LockPrivateContent /> + )} + </div> + ); +}; diff --git a/src/v4/social/components/CommunityPin/index.ts b/src/v4/social/components/CommunityPin/index.ts new file mode 100644 index 000000000..b15e23891 --- /dev/null +++ b/src/v4/social/components/CommunityPin/index.ts @@ -0,0 +1 @@ +export { CommunityPin } from './CommunityPin'; diff --git a/src/v4/social/components/CommunityPinnedPost/CommunityPinnedPost.tsx b/src/v4/social/components/CommunityPinnedPost/CommunityPinnedPost.tsx new file mode 100644 index 000000000..861343082 --- /dev/null +++ b/src/v4/social/components/CommunityPinnedPost/CommunityPinnedPost.tsx @@ -0,0 +1,9 @@ +import React from 'react'; + +interface CommunityPinnedPostProps { + communityId?: string; +} + +export const CommunityPinnedPost = ({ communityId }: CommunityPinnedPostProps) => { + return <div>CommunityPinnedPost</div>; +}; diff --git a/src/v4/social/components/CommunityPinnedPost/index.ts b/src/v4/social/components/CommunityPinnedPost/index.ts new file mode 100644 index 000000000..c89cf7961 --- /dev/null +++ b/src/v4/social/components/CommunityPinnedPost/index.ts @@ -0,0 +1 @@ +export { CommunityPinnedPost } from './CommunityPinnedPost'; diff --git a/src/v4/social/components/EmptyPinnedPost/EmptyPinnedPost.module.css b/src/v4/social/components/EmptyPinnedPost/EmptyPinnedPost.module.css new file mode 100644 index 000000000..320efe94a --- /dev/null +++ b/src/v4/social/components/EmptyPinnedPost/EmptyPinnedPost.module.css @@ -0,0 +1,18 @@ +.emptyPinnedPost__container { + margin: 5rem auto 3rem; + text-align: center; +} + +.emptyPinnedPost__text { + color: var(--asc-color-base-shade3); + font-size: 1rem; + font-weight: 600; + line-height: 1.375rem; + margin-top: 0.5rem; +} + +.emptyPinnedPost__icon { + fill: var(--asc-color-base-shade4); + width: 3.75rem; + height: 2.8125rem; +} diff --git a/src/v4/social/components/EmptyPinnedPost/EmptyPinnedPost.tsx b/src/v4/social/components/EmptyPinnedPost/EmptyPinnedPost.tsx new file mode 100644 index 000000000..4aee57b89 --- /dev/null +++ b/src/v4/social/components/EmptyPinnedPost/EmptyPinnedPost.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { Typography } from '~/v4/core/components'; +import { useAmityComponent } from '~/v4/core/hooks/uikit'; +import styles from './EmptyPinnedPost.module.css'; +import EmptyPost from '~/v4/icons/EmptyPost'; + +interface EmptyPinnedPostProps { + pageId?: string; +} + +export const EmptyPinnedPost = ({ pageId = '*' }: EmptyPinnedPostProps) => { + const componentId = 'empty_pinned_post'; + const { config, accessibilityId, isExcluded, themeStyles } = useAmityComponent({ + pageId, + componentId, + }); + if (isExcluded) return null; + return ( + <div + data-qa-anchor={accessibilityId} + className={styles.emptyPinnedPost__container} + style={themeStyles} + > + <EmptyPost className={styles.emptyPinnedPost__icon} /> + <Typography.Title className={styles.emptyPinnedPost__text}> + No pinned post yet + </Typography.Title> + </div> + ); +}; diff --git a/src/v4/social/components/EmptyPinnedPost/index.ts b/src/v4/social/components/EmptyPinnedPost/index.ts new file mode 100644 index 000000000..6088b7a11 --- /dev/null +++ b/src/v4/social/components/EmptyPinnedPost/index.ts @@ -0,0 +1 @@ +export { EmptyPinnedPost } from './EmptyPinnedPost'; diff --git a/src/v4/social/components/GlobalFeed/GlobalFeed.tsx b/src/v4/social/components/GlobalFeed/GlobalFeed.tsx index 2e15cc94a..5f6921911 100644 --- a/src/v4/social/components/GlobalFeed/GlobalFeed.tsx +++ b/src/v4/social/components/GlobalFeed/GlobalFeed.tsx @@ -8,6 +8,10 @@ import styles from './GlobalFeed.module.css'; import { useAmityComponent } from '~/v4/core/hooks/uikit'; import { PostAd } from '~/v4/social/internal-components/PostAd/PostAd'; import { Button } from '~/v4/core/natives/Button'; +import { + AmityPostCategory, + AmityPostContentComponentStyle, +} from '~/v4/social/components/PostContent/PostContent'; interface GlobalFeedProps { pageId?: string; @@ -78,7 +82,8 @@ export const GlobalFeed = ({ <PostContent pageId={pageId} post={item} - type="feed" + category={AmityPostCategory.GENERAL} + style={AmityPostContentComponentStyle.FEED} onClick={() => { AmityGlobalFeedComponentBehavior?.goToPostDetailPage?.({ postId: item.postId }); }} diff --git a/src/v4/social/components/PostContent/PostContent.module.css b/src/v4/social/components/PostContent/PostContent.module.css index 93fe49961..a2afeebcb 100644 --- a/src/v4/social/components/PostContent/PostContent.module.css +++ b/src/v4/social/components/PostContent/PostContent.module.css @@ -36,8 +36,12 @@ } .postContent__bar__actionButton { + display: flex; + justify-content: center; + align-items: center; justify-self: flex-end; cursor: pointer; + gap: 0.5rem; } .postContent__content_and_reactions { @@ -166,6 +170,133 @@ } .postContent__bar__information__editedTag { - margin-left: 0.125rem; color: var(--asc-color-base-shade2); } + +.button { + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 0.375rem; + background-color: rgb(0 0 0 / 20%); + background-clip: padding-box; + border: 1px solid rgb(255 255 255 / 20%); + padding: 0.5rem 0.75rem; + color: white; + cursor: pointer; + outline: none; + transition: background-color 0.3s; +} + +.button:hover { + background-color: rgb(0 0 0 / 30%); +} + +.button:active { + background-color: rgb(0 0 0 / 40%); +} + +.button:focus-visible { + box-shadow: 0 0 0 2px rgb(255 255 255 / 75%); +} + +.popover { + padding: 0.25rem; + width: 14rem; + overflow: auto; + border-radius: 0.375rem; + background-color: white; + box-shadow: + 0 10px 15px -3px rgb(0 0 0 / 10%), + 0 4px 6px -2px rgb(0 0 0 / 5%); + box-shadow: 0 0 0 1px rgb(0 0 0 / 5%); +} + +.popover[data-entering] { + animation: + fade-in 0.2s ease-out, + zoom-in 0.2s ease-out; +} + +.popover[data-exiting] { + animation: + fade-out 0.2s ease-in, + zoom-out 0.2s ease-in; +} + +.menu { + outline: none; +} + +.menuItem { + display: flex; + width: 100%; + align-items: center; + border-radius: 0.375rem; + padding: 0.5rem 0.75rem; + box-sizing: border-box; + outline: none; + cursor: pointer; + color: var(--asc-color-secondary-default); +} + +.menuItem:focus { + background-color: var(--asc-color-base-shade1); + color: white; +} + +.separator { + background-color: #d1d5db; + height: 1px; + margin: 0.25rem 0.75rem; +} + +.postContent__notMember { + display: flex; + justify-content: start; + align-items: center; + gap: 0.25rem; + padding-top: 0.25rem; + line-height: 1.25rem; + color: var(--asc-color-base-shade2); +} + +@keyframes fade-in { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +@keyframes fade-out { + from { + opacity: 1; + } + + to { + opacity: 0; + } +} + +@keyframes zoom-in { + from { + transform: scale(0.95); + } + + to { + transform: scale(1); + } +} + +@keyframes zoom-out { + from { + transform: scale(1); + } + + to { + transform: scale(0.95); + } +} diff --git a/src/v4/social/components/PostContent/PostContent.tsx b/src/v4/social/components/PostContent/PostContent.tsx index 3a5bcac3e..b87617589 100644 --- a/src/v4/social/components/PostContent/PostContent.tsx +++ b/src/v4/social/components/PostContent/PostContent.tsx @@ -39,12 +39,24 @@ import { PageTypes, useNavigation } from '~/v4/core/providers/NavigationProvider import dayjs from 'dayjs'; import { useVisibilitySensor } from '~/v4/social/hooks/useVisibilitySensor'; +export enum AmityPostContentComponentStyle { + FEED = 'feed', + DETAIL = 'detail', +} + +export enum AmityPostCategory { + GENERAL = 'general', + ANNOUNCEMENT = 'announcement', + PIN = 'pin', +} + interface PostTitleProps { post: Amity.Post; pageId?: string; + hideTarget?: boolean; } -const PostTitle = ({ pageId, post }: PostTitleProps) => { +const PostTitle = ({ pageId, post, hideTarget }: PostTitleProps) => { const shouldCall = useMemo(() => post?.targetType === 'community', [post?.targetType]); const { community: targetCommunity } = useCommunity({ @@ -52,6 +64,8 @@ const PostTitle = ({ pageId, post }: PostTitleProps) => { shouldCall, }); + const { goToCommunityProfilePage } = useNavigation(); + const { user: postedUser } = useUser(post.postedUserId); const { onClickCommunity, onClickUser } = useNavigation(); @@ -65,19 +79,13 @@ const PostTitle = ({ pageId, post }: PostTitleProps) => { </Typography.BodyBold> </Button> )} - {targetCommunity && ( - <> + {targetCommunity && !hideTarget && ( + <Button onPress={() => goToCommunityProfilePage(targetCommunity.communityId)}> <AngleRight className={styles.postTitle__icon} /> - <Button - onPress={() => { - onClickCommunity(targetCommunity.communityId); - }} - > - <Typography.BodyBold className={styles.postTitle__text}> - {targetCommunity.displayName} - </Typography.BodyBold> - </Button> - </> + <Typography.BodyBold className={styles.postTitle__text}> + {targetCommunity.displayName} + </Typography.BodyBold>{' '} + </Button> )} </div> ); @@ -151,21 +159,25 @@ const ChildrenPostContent = ({ }; interface PostContentProps { - pageId?: string; post: Amity.Post; - type: 'feed' | 'detail'; - drawerRef?: React.RefObject<HTMLDivElement>; onClick?: () => void; onPostDeleted?: (post: Amity.Post) => void; + style: AmityPostContentComponentStyle; + category: AmityPostCategory; + hideMenu?: boolean; + hideTarget?: boolean; + pageId?: string; } export const PostContent = ({ pageId = '*', post: initialPost, - type, - drawerRef, onClick, onPostDeleted, + category, + hideMenu = false, + hideTarget, + style, }: PostContentProps) => { const componentId = 'post_content'; const { themeStyles } = useAmityComponent({ @@ -288,11 +300,17 @@ export const PostContent = ({ setClickedVideoIndex(null); }; - const hasLike = post?.reactions.like > 0; - const hasLove = post?.reactions.love > 0; - const hasFire = post?.reactions.fire > 0; - const hasHappy = post?.reactions.happy > 0; - const hasCrying = post?.reactions.crying > 0; + const handleUnpinPost = async () => {}; + + const handleEditPost = () => {}; + + const handleDeletePost = () => {}; + + const hasLike = post?.reactions?.like > 0; + const hasLove = post?.reactions?.love > 0; + const hasFire = post?.reactions?.fire > 0; + const hasHappy = post?.reactions?.happy > 0; + const hasCrying = post?.reactions?.crying > 0; const hasReaction = hasLike || hasLove || hasFire || hasHappy || hasCrying; @@ -310,13 +328,13 @@ export const PostContent = ({ return ( <div ref={elementRef} className={styles.postContent} style={themeStyles}> - <div className={styles.postContent__bar} data-type={type}> + <div className={styles.postContent__bar} data-type={style}> <div className={styles.postContent__bar__userAvatar}> <UserAvatar userId={post?.postedUserId} /> </div> <div> <div> - <PostTitle post={post} /> + <PostTitle post={post} hideTarget={hideTarget} /> </div> <div className={styles.postContent__bar__information__subtitle}> {isCommunityModerator ? ( @@ -333,27 +351,30 @@ export const PostContent = ({ )} </div> </div> - <div className={styles.postContent__bar__actionButton}> - {type === 'feed' ? ( - <MenuButton - pageId={pageId} - componentId={componentId} - onClick={() => - setDrawerData({ - content: ( - <PostMenu - post={post} - onCloseMenu={() => removeDrawerData()} - pageId={pageId} - componentId={componentId} - onPostDeleted={onPostDeleted} - /> - ), - }) - } - /> - ) : null} - </div> + + {style === AmityPostContentComponentStyle.FEED ? ( + <div className={styles.postContent__bar__actionButton}> + {!hideMenu && ( + <MenuButton + pageId={pageId} + componentId={componentId} + onClick={() => + setDrawerData({ + content: ( + <PostMenu + post={post} + onCloseMenu={() => removeDrawerData()} + pageId={pageId} + componentId={componentId} + onPostDeleted={onPostDeleted} + /> + ), + }) + } + /> + )} + </div> + ) : null} </div> <div className={styles.postContent__content_and_reactions}> <div className={styles.postContent__content}> @@ -366,7 +387,7 @@ export const PostContent = ({ /> ) : null} </div> - {type === 'detail' ? ( + {style === AmityPostContentComponentStyle.DETAIL ? ( <div className={styles.postContent__reactions_and_comments}> <div className={styles.postContent__reactionsBar} @@ -413,31 +434,45 @@ export const PostContent = ({ </Typography.Caption> </div> ) : null} - <div className={styles.postContent__divider} /> - <div className={styles.postContent__reactionBar}> - <div className={styles.postContent__reactionBar__leftPane}> - <ReactionButton - pageId={pageId} - componentId={componentId} - reactionsCount={type === 'feed' ? reactionsCount : undefined} - myReaction={reactionByMe} - defaultIconClassName={styles.postContent__reactionBar__leftPane__icon} - imgIconClassName={styles.postContent__reactionBar__leftPane__iconImg} - onReactionClick={handleReactionClick} - /> - <CommentButton - pageId={pageId} - componentId={componentId} - commentsCount={type === 'feed' ? post.commentsCount : undefined} - defaultIconClassName={styles.postContent__reactionBar__leftPane__icon} - imgIconClassName={styles.postContent__reactionBar__leftPane__iconImg} - onPress={() => onClick?.()} - /> - </div> - <div className={styles.postContent__reactionBar__rightPane}> - <ShareButton pageId={pageId} componentId={componentId} /> - </div> - </div> + {!targetCommunity?.isJoined && page.type === PageTypes.CommunityProfilePage ? ( + <> + <Typography.Body className={styles.postContent__notMember}> + Join community to interact with all posts + </Typography.Body> + </> + ) : !targetCommunity?.isJoined && page.type === PageTypes.PostDetailPage ? null : ( + <> + <div className={styles.postContent__divider} /> + <div className={styles.postContent__reactionBar}> + <div className={styles.postContent__reactionBar__leftPane}> + <ReactionButton + pageId={pageId} + componentId={componentId} + reactionsCount={ + style === AmityPostContentComponentStyle.FEED ? reactionsCount : undefined + } + myReaction={reactionByMe} + defaultIconClassName={styles.postContent__reactionBar__leftPane__icon} + imgIconClassName={styles.postContent__reactionBar__leftPane__iconImg} + onReactionClick={handleReactionClick} + /> + <CommentButton + pageId={pageId} + componentId={componentId} + commentsCount={ + style === AmityPostContentComponentStyle.FEED ? post.commentsCount : undefined + } + defaultIconClassName={styles.postContent__reactionBar__leftPane__icon} + imgIconClassName={styles.postContent__reactionBar__leftPane__iconImg} + onPress={() => onClick?.()} + /> + </div> + <div className={styles.postContent__reactionBar__rightPane}> + <ShareButton pageId={pageId} componentId={componentId} /> + </div> + </div> + </> + )} </div> {isImageViewerOpen && typeof clickedImageIndex === 'number' ? ( <ImageViewer post={post} onClose={closeImageViewer} initialImageIndex={clickedImageIndex} /> diff --git a/src/v4/social/components/index.ts b/src/v4/social/components/index.ts index 8cbcd8eb9..aff4b42c5 100644 --- a/src/v4/social/components/index.ts +++ b/src/v4/social/components/index.ts @@ -15,3 +15,6 @@ export * from './GlobalFeed'; export * from './EmptyNewsFeed'; export * from './Newsfeed'; export * from './TopNavigation'; +export * from './CommunityHeader'; +export * from './CommunityFeed'; +export * from './CommunityPinnedPost'; diff --git a/src/v4/social/elements/BackButton/BackButton.tsx b/src/v4/social/elements/BackButton/BackButton.tsx index 977aca3a9..c02c05c81 100644 --- a/src/v4/social/elements/BackButton/BackButton.tsx +++ b/src/v4/social/elements/BackButton/BackButton.tsx @@ -9,7 +9,7 @@ const BackButtonSvg = (props: React.SVGProps<SVGSVGElement>) => ( width="10" height="17" viewBox="0 0 10 17" - fill="none" + fill="currentColor" xmlns="http://www.w3.org/2000/svg" {...props} > diff --git a/src/v4/social/elements/CommunityCategory/CommunityCategory.module.css b/src/v4/social/elements/CommunityCategory/CommunityCategory.module.css new file mode 100644 index 000000000..fee67d974 --- /dev/null +++ b/src/v4/social/elements/CommunityCategory/CommunityCategory.module.css @@ -0,0 +1,11 @@ +.community__category { + display: flex; + align-items: center; + gap: 0.5rem; + overflow-x: scroll; + width: 100%; +} + +.community__category::-webkit-scrollbar { + display: none; +} diff --git a/src/v4/social/elements/CommunityCategory/CommunityCategory.tsx b/src/v4/social/elements/CommunityCategory/CommunityCategory.tsx new file mode 100644 index 000000000..debf081ec --- /dev/null +++ b/src/v4/social/elements/CommunityCategory/CommunityCategory.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import styles from './CommunityCategory.module.css'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { CommunityCategoryName } from '~/v4/social/elements/CommunityCategoryName'; + +interface CommunityCategoryProps { + categories: Amity.Category[]; + pageId?: string; + componentId?: string; +} + +export const CommunityCategory = ({ + pageId = '*', + componentId = '*', + categories, +}: CommunityCategoryProps) => { + const elementId = 'community_category'; + const { config, themeStyles, accessibilityId, isExcluded } = useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + return ( + <div + data-qa-anchor={accessibilityId} + style={themeStyles} + className={styles.community__category} + > + {categories.map((category) => ( + <CommunityCategoryName categoryName={category.name} /> + ))} + </div> + ); +}; diff --git a/src/v4/social/elements/CommunityCategory/index.ts b/src/v4/social/elements/CommunityCategory/index.ts new file mode 100644 index 000000000..9012445ee --- /dev/null +++ b/src/v4/social/elements/CommunityCategory/index.ts @@ -0,0 +1 @@ +export { CommunityCategory } from './CommunityCategory'; diff --git a/src/v4/social/elements/CommunityCategoryName/CommunityCategoryName.module.css b/src/v4/social/elements/CommunityCategoryName/CommunityCategoryName.module.css index 496881b71..1986addeb 100644 --- a/src/v4/social/elements/CommunityCategoryName/CommunityCategoryName.module.css +++ b/src/v4/social/elements/CommunityCategoryName/CommunityCategoryName.module.css @@ -1,9 +1,12 @@ .communityCategoryName { display: flex; - padding: 0 0.5rem; + padding: 0.12rem 0.5rem; justify-content: center; align-items: center; border-radius: 1.25rem; background-color: var(--asc-color-base-shade4); color: var(--asc-color-base-default); + line-height: 1.125rem; + font-size: 0.8125rem; + white-space: nowrap; } diff --git a/src/v4/social/elements/CommunityCategoryName/CommunityCategoryName.tsx b/src/v4/social/elements/CommunityCategoryName/CommunityCategoryName.tsx index b4d41b8bd..37b240d5f 100644 --- a/src/v4/social/elements/CommunityCategoryName/CommunityCategoryName.tsx +++ b/src/v4/social/elements/CommunityCategoryName/CommunityCategoryName.tsx @@ -1,9 +1,7 @@ import React from 'react'; +import styles from './CommunityCategoryName.module.css'; import { Typography } from '~/v4/core/components'; import { useAmityElement } from '~/v4/core/hooks/uikit'; -import { useCustomization } from '~/v4/core/providers/CustomizationProvider'; -import { useGenerateStylesShadeColors } from '~/v4/core/providers/ThemeProvider'; -import styles from './CommunityCategoryName.module.css'; export interface CommunityCategoryNameProps { pageId?: string; @@ -17,12 +15,11 @@ export function CommunityCategoryName({ categoryName, }: CommunityCategoryNameProps) { const elementId = 'community_category_name'; - const { accessibilityId, config, defaultConfig, isExcluded, uiReference, themeStyles } = - useAmityElement({ - pageId, - componentId, - elementId, - }); + const { accessibilityId, isExcluded, themeStyles } = useAmityElement({ + pageId, + componentId, + elementId, + }); if (isExcluded) return null; diff --git a/src/v4/social/elements/CommunityCover/CommunityCover.module.css b/src/v4/social/elements/CommunityCover/CommunityCover.module.css new file mode 100644 index 000000000..90bee2be0 --- /dev/null +++ b/src/v4/social/elements/CommunityCover/CommunityCover.module.css @@ -0,0 +1,45 @@ +.communityCover__container { + position: relative; + width: 100%; + padding-top: 56.25%; /* 16:9 Aspect Ratio for mobile */ + background-size: cover; + background-position: center; + background-repeat: no-repeat; + overflow: hidden; +} + +@media (width >= 768px) { + .communityCover__container { + padding-top: 33.33%; /* 3:1 Aspect Ratio for desktop */ + max-height: 11.75rem; /* Limit the maximum height on larger screens */ + } +} + +.communityCover__topBar { + position: absolute; + top: 3rem; + left: 1rem; + right: 1rem; + display: flex; + justify-content: space-between; +} + +.communityCover__backButton { + fill: var(--asc-color-primary-shade4); + background: rgb(0 0 0 / 50%); + border-radius: 50%; + padding: 0.5rem; + width: 2rem; + height: 2rem; + cursor: pointer; +} + +.communityCover__menuButton { + fill: var(--asc-color-primary-shade4); + background: rgb(0 0 0 / 50%); + border-radius: 50%; + padding: 0.5rem; + width: 1rem; + height: 1rem; + cursor: pointer; +} diff --git a/src/v4/social/elements/CommunityCover/CommunityCover.tsx b/src/v4/social/elements/CommunityCover/CommunityCover.tsx new file mode 100644 index 000000000..8c62c8a74 --- /dev/null +++ b/src/v4/social/elements/CommunityCover/CommunityCover.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import styles from './CommunityCover.module.css'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { BackButton } from '~/v4/social/elements/BackButton'; +import { CommunityProfileMenuButton } from '~/v4/social/elements/CommunityProfileMenuButton'; + +interface CommunityCoverProps { + pageId?: string; + componentId?: string; + image?: string; + onBack?: () => void; + onClickMenu?: () => void; +} + +export const CommunityCover: React.FC<CommunityCoverProps> = ({ + pageId = '*', + componentId = '*', + image, + onBack, + onClickMenu, +}) => { + const elementId = 'community_cover'; + const { isExcluded, accessibilityId, themeStyles, config } = useAmityElement({ + pageId, + componentId, + elementId, + }); + + const backgroundStyle = image + ? { backgroundImage: `url(${image})` } + : { background: 'linear-gradient(180deg, #a5a9b5 0%, #636878 100%)' }; + + if (isExcluded) return null; + + return ( + <div + className={styles.communityCover__container} + data-qa-anchor={accessibilityId} + style={{ + ...themeStyles, + ...backgroundStyle, + }} + > + <div + style={{ + ...backgroundStyle, + }} + /> + <div className={styles.communityCover__topBar}> + <BackButton defaultClassName={styles.communityCover__backButton} onPress={onBack} /> + <CommunityProfileMenuButton + className={styles.communityCover__menuButton} + onClick={onClickMenu} + /> + </div> + </div> + ); +}; diff --git a/src/v4/social/elements/CommunityCover/index.ts b/src/v4/social/elements/CommunityCover/index.ts new file mode 100644 index 000000000..57a928db9 --- /dev/null +++ b/src/v4/social/elements/CommunityCover/index.ts @@ -0,0 +1 @@ +export { CommunityCover } from './CommunityCover'; diff --git a/src/v4/social/elements/CommunityCreatePostButton/CommunityCreatePostButton.tsx b/src/v4/social/elements/CommunityCreatePostButton/CommunityCreatePostButton.tsx new file mode 100644 index 000000000..2371786f8 --- /dev/null +++ b/src/v4/social/elements/CommunityCreatePostButton/CommunityCreatePostButton.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { IconComponent } from '~/v4/core/IconComponent'; +import { Button, ButtonProps } from '~/v4/core/natives/Button'; +import { CommunityCreatePostButtonIcon } from '~/v4/icons/CommunityCreatePost'; + +interface CommunityCreatePostButtonProps { + pageId?: string; + componentId?: string; + defaultClassName?: string; + imgClassName?: string; + onPress?: ButtonProps['onPress']; +} + +export const CommunityCreatePostButton = ({ + pageId = '*', + componentId = '*', + defaultClassName, + imgClassName, + onPress, +}: CommunityCreatePostButtonProps) => { + const elementId = 'community_create_post_button'; + const { config, accessibilityId, themeStyles, isExcluded, defaultConfig, uiReference } = + useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + return ( + <Button onPress={onPress}> + <IconComponent + defaultIcon={() => <CommunityCreatePostButtonIcon className={defaultClassName} />} + imgIcon={() => <img src={config.icon} alt={uiReference} className={imgClassName} />} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} + /> + </Button> + ); +}; diff --git a/src/v4/social/elements/CommunityCreatePostButton/index.ts b/src/v4/social/elements/CommunityCreatePostButton/index.ts new file mode 100644 index 000000000..d7bd69b88 --- /dev/null +++ b/src/v4/social/elements/CommunityCreatePostButton/index.ts @@ -0,0 +1 @@ +export { CommunityCreatePostButton } from './CommunityCreatePostButton'; diff --git a/src/v4/social/elements/CommunityDescription/CommunityDescription.module.css b/src/v4/social/elements/CommunityDescription/CommunityDescription.module.css new file mode 100644 index 000000000..ac12760f7 --- /dev/null +++ b/src/v4/social/elements/CommunityDescription/CommunityDescription.module.css @@ -0,0 +1,26 @@ +.descriptionWrapper { + width: 100%; + box-sizing: border-box; +} + +.description { + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + line-height: 1.5rem; + word-wrap: break-word; + word-break: break-word; +} + +.description.expanded { + overflow: visible; +} + +.toggleButton { + background: none; + border: none; + color: var(--asc-color-primary-default); + cursor: pointer; + padding: 0; + font-size: inherit; +} diff --git a/src/v4/social/elements/CommunityDescription/CommunityDescription.tsx b/src/v4/social/elements/CommunityDescription/CommunityDescription.tsx new file mode 100644 index 000000000..c0cb1948b --- /dev/null +++ b/src/v4/social/elements/CommunityDescription/CommunityDescription.tsx @@ -0,0 +1,62 @@ +import React, { useState, useRef, useEffect } from 'react'; +import styles from './CommunityDescription.module.css'; +import { Typography } from '~/v4/core/components'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { Button } from '~/v4/core/natives/Button'; + +interface CommunityDescriptionProps { + description: string; + pageId?: string; + componentId?: string; +} + +export const CommunityDescription: React.FC<CommunityDescriptionProps> = ({ + pageId = '*', + componentId = '*', + description = '', +}) => { + const [isExpanded, setIsExpanded] = useState(false); + const [showToggle, setShowToggle] = useState(false); + const descriptionRef = useRef<HTMLDivElement>(null); + + const elementId = 'community_description'; + const { themeStyles, accessibilityId, isExcluded } = useAmityElement({ + pageId, + componentId, + elementId, + }); + + const maxLength = 180; + + const truncatedText = + description.length > maxLength ? description.slice(0, maxLength) : description; + + useEffect(() => { + if (descriptionRef.current) { + setShowToggle(description.length > maxLength); + } + }, [description]); + + if (isExcluded) return null; + + const expandText = () => setIsExpanded(true); + + return ( + <div data-qa-anchor={accessibilityId} style={themeStyles} className={styles.descriptionWrapper}> + <div + ref={descriptionRef} + className={`${styles.description} ${isExpanded ? styles.expanded : ''}`} + > + <Typography.Body> + {isExpanded ? description : truncatedText}{' '} + {showToggle && !isExpanded && ( + <Button onPress={expandText} className={styles.toggleButton}> + {' '} + ...See more + </Button> + )} + </Typography.Body> + </div> + </div> + ); +}; diff --git a/src/v4/social/elements/CommunityDescription/index.ts b/src/v4/social/elements/CommunityDescription/index.ts new file mode 100644 index 000000000..114e4f949 --- /dev/null +++ b/src/v4/social/elements/CommunityDescription/index.ts @@ -0,0 +1 @@ +export { CommunityDescription } from './CommunityDescription'; diff --git a/src/v4/social/elements/CommunityInfo/CommunityInfo.module.css b/src/v4/social/elements/CommunityInfo/CommunityInfo.module.css new file mode 100644 index 000000000..d3569d498 --- /dev/null +++ b/src/v4/social/elements/CommunityInfo/CommunityInfo.module.css @@ -0,0 +1,20 @@ +.communityInfo__container { + display: flex; + justify-content: space-between; + align-items: center; + align-self: stretch; +} + +.communityInfo__wrapper { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.communityInfo__count { + color: var(--asc-color-base-default); +} + +.communityInfo__title { + color: var(--asc-color-base-shade2); +} diff --git a/src/v4/social/elements/CommunityInfo/CommunityInfo.tsx b/src/v4/social/elements/CommunityInfo/CommunityInfo.tsx new file mode 100644 index 000000000..456bf11ee --- /dev/null +++ b/src/v4/social/elements/CommunityInfo/CommunityInfo.tsx @@ -0,0 +1,40 @@ +import millify from 'millify'; +import React from 'react'; +import { Typography } from '~/v4/core/components'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import styles from './CommunityInfo.module.css'; +import { Button } from '~/v4/core/natives/Button'; + +interface CommunityInfoProps { + count: number; + text: string; + pageId?: string; + componentId?: string; + onClick?: () => void; +} + +export const CommunityInfo = ({ + pageId = '*', + componentId = '*', + count, + text, + onClick, +}: CommunityInfoProps) => { + const elementId = 'community_info'; + const { config, accessibilityId, themeStyles, isExcluded } = useAmityElement({ + pageId, + componentId, + elementId, + }); + if (isExcluded) return null; + return ( + <Button onPress={onClick} className={styles.communityInfo__container}> + <div className={styles.communityInfo__wrapper}> + <Typography.BodyBold className={styles.communityInfo__count}> + {millify(count)} + </Typography.BodyBold> + <Typography.Caption className={styles.communityInfo__title}>{text}</Typography.Caption> + </div> + </Button> + ); +}; diff --git a/src/v4/social/elements/CommunityInfo/index.ts b/src/v4/social/elements/CommunityInfo/index.ts new file mode 100644 index 000000000..b3f90956b --- /dev/null +++ b/src/v4/social/elements/CommunityInfo/index.ts @@ -0,0 +1 @@ +export { CommunityInfo } from './CommunityInfo'; diff --git a/src/v4/social/elements/CommunityJoinButton/CommunityJoinButton.module.css b/src/v4/social/elements/CommunityJoinButton/CommunityJoinButton.module.css new file mode 100644 index 000000000..da25c1d29 --- /dev/null +++ b/src/v4/social/elements/CommunityJoinButton/CommunityJoinButton.module.css @@ -0,0 +1,16 @@ +.communityJoinButton { + display: flex; + width: 100%; + background: var(--asc-color-primary-default); + padding: 0.625rem 1rem 0.625rem 0.75rem; + justify-content: center; + align-items: center; + gap: 0.5rem; + border-radius: 0.5rem; + margin-top: 1rem; +} + +.joinButton { + width: 1.25rem; + height: 1rem; +} diff --git a/src/v4/social/elements/CommunityJoinButton/CommunityJoinButton.tsx b/src/v4/social/elements/CommunityJoinButton/CommunityJoinButton.tsx new file mode 100644 index 000000000..6e775613d --- /dev/null +++ b/src/v4/social/elements/CommunityJoinButton/CommunityJoinButton.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { Button } from '~/v4/core/natives/Button'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import styles from './CommunityJoinButton.module.css'; +import { IconComponent } from '~/v4/core/IconComponent'; +import { Plus as PlusIcon } from '~/v4/icons/Plus'; +import clsx from 'clsx'; + +interface CommunityJoinButtonProps { + pageId?: string; + componentId?: string; + onClick?: () => void; + className?: string; + defaultClassName?: string; +} + +export const CommunityJoinButton = ({ + pageId = '*', + componentId = '*', + onClick, + className, + defaultClassName, +}: CommunityJoinButtonProps) => { + const elementId = 'community_join_button'; + const { config, themeStyles, accessibilityId, isExcluded, uiReference, defaultConfig } = + useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + return ( + <Button + data-qa-anchor={accessibilityId} + style={themeStyles} + onPress={onClick} + className={clsx(styles.communityJoinButton, className)} + > + <IconComponent + defaultIcon={() => <PlusIcon className={clsx(styles.joinButton, defaultClassName)} />} + imgIcon={() => <img src={config.icon} alt={uiReference} />} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} + /> + Join + </Button> + ); +}; diff --git a/src/v4/social/elements/CommunityJoinButton/index.ts b/src/v4/social/elements/CommunityJoinButton/index.ts new file mode 100644 index 000000000..68c4ad0f6 --- /dev/null +++ b/src/v4/social/elements/CommunityJoinButton/index.ts @@ -0,0 +1 @@ +export { CommunityJoinButton } from './CommunityJoinButton'; diff --git a/src/v4/social/elements/CommunityName/CommunityName.module.css b/src/v4/social/elements/CommunityName/CommunityName.module.css new file mode 100644 index 000000000..7b5b9e6a2 --- /dev/null +++ b/src/v4/social/elements/CommunityName/CommunityName.module.css @@ -0,0 +1,8 @@ +.communityName__truncate { + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 2; + line-clamp: 2; + -webkit-box-orient: vertical; +} diff --git a/src/v4/social/elements/CommunityName/CommunityName.tsx b/src/v4/social/elements/CommunityName/CommunityName.tsx new file mode 100644 index 000000000..6a67cfa08 --- /dev/null +++ b/src/v4/social/elements/CommunityName/CommunityName.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { Typography } from '~/v4/core/components'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import styles from './CommunityName.module.css'; + +interface CommunityNameProps { + name: string; + pageId?: string; + componentId?: string; +} + +export const CommunityName = ({ + pageId = '*', + componentId = '*', + name = '', +}: CommunityNameProps) => { + const elementId = 'community_name'; + const { config, themeStyles, accessibilityId, isExcluded } = useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + return ( + <Typography.Heading data-qa-anchor={accessibilityId} className={styles.communityName__truncate}> + {name} + </Typography.Heading> + ); +}; diff --git a/src/v4/social/elements/CommunityName/index.ts b/src/v4/social/elements/CommunityName/index.ts new file mode 100644 index 000000000..444e64fdd --- /dev/null +++ b/src/v4/social/elements/CommunityName/index.ts @@ -0,0 +1 @@ +export { CommunityName } from './CommunityName'; diff --git a/src/v4/social/elements/CommunityPendingPost/CommunityPendingPost.module.css b/src/v4/social/elements/CommunityPendingPost/CommunityPendingPost.module.css new file mode 100644 index 000000000..1d7f827f8 --- /dev/null +++ b/src/v4/social/elements/CommunityPendingPost/CommunityPendingPost.module.css @@ -0,0 +1,41 @@ +.communityPendingPost__container { + background-color: var(--asc-color-base-shade4); + border-radius: 0.25rem; + padding: 1rem; +} + +.communityPendingPost__content { + display: flex; + justify-content: center; + align-items: center; +} + +.communityPendingPost__icon { + width: 0.375rem; + height: 0.375rem; + background-color: var(--asc-color-primary-default); + border-radius: 50%; +} + +.communityPendingPost__textContainer { + display: flex; + flex-direction: column; + text-align: center; +} + +.communityPendingPost__title__wrapper { + display: flex; + align-items: center; + justify-content: center; + gap: 0.375rem; +} + +.communityPendingPost__title { + color: var(--asc-color-base-default); + margin: 0; +} + +.communityPendingPost__subtext { + color: var(--asc-color-base-shade1); + margin: 0; +} diff --git a/src/v4/social/elements/CommunityPendingPost/CommunityPendingPost.tsx b/src/v4/social/elements/CommunityPendingPost/CommunityPendingPost.tsx new file mode 100644 index 000000000..af1210c80 --- /dev/null +++ b/src/v4/social/elements/CommunityPendingPost/CommunityPendingPost.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { Typography } from '~/v4/core/components'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import styles from './CommunityPendingPost.module.css'; + +interface CommunityPendingPostProps { + pageId?: string; + componentId?: string; + pendingPostsCount?: number; +} + +export const CommunityPendingPost: React.FC<CommunityPendingPostProps> = ({ + pageId = '*', + componentId = '*', + pendingPostsCount = 0, +}) => { + const elementId = 'community_pending_post'; + const { config, themeStyles, accessibilityId, isExcluded } = useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + return ( + <div + className={styles.communityPendingPost__container} + style={themeStyles} + data-qa-anchor={accessibilityId} + > + <div className={styles.communityPendingPost__content}> + <div className={styles.communityPendingPost__textContainer}> + <div className={styles.communityPendingPost__title__wrapper}> + <div className={styles.communityPendingPost__icon} /> + <Typography.BodyBold>Pending posts</Typography.BodyBold> + </div> + <Typography.Caption className={styles.communityPendingPost__subtext}> + {pendingPostsCount == 1 + ? '1 post need approval' + : `${pendingPostsCount} posts need approval`} + </Typography.Caption> + </div> + </div> + </div> + ); +}; diff --git a/src/v4/social/elements/CommunityPendingPost/index.ts b/src/v4/social/elements/CommunityPendingPost/index.ts new file mode 100644 index 000000000..43f6ac2cc --- /dev/null +++ b/src/v4/social/elements/CommunityPendingPost/index.ts @@ -0,0 +1 @@ +export { CommunityPendingPost } from './CommunityPendingPost'; diff --git a/src/v4/social/elements/CommunityPinTabButton/CommunityPinTabButton.tsx b/src/v4/social/elements/CommunityPinTabButton/CommunityPinTabButton.tsx new file mode 100644 index 000000000..3830dc44f --- /dev/null +++ b/src/v4/social/elements/CommunityPinTabButton/CommunityPinTabButton.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; + +interface CommunityPinTabButtonProps { + pageId?: string; + componentId?: string; +} + +export const CommunityPinTabButton = ({ + pageId = '*', + componentId = '*', +}: CommunityPinTabButtonProps) => { + const elementId = 'community_pin_tab_button'; + const { config, themeStyles, accessibilityId, isExcluded } = useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + return ( + <div data-qa-anchor={accessibilityId} style={themeStyles}> + CommunityPinTabButton + </div> + ); +}; diff --git a/src/v4/social/elements/CommunityPinTabButton/index.ts b/src/v4/social/elements/CommunityPinTabButton/index.ts new file mode 100644 index 000000000..3f875611c --- /dev/null +++ b/src/v4/social/elements/CommunityPinTabButton/index.ts @@ -0,0 +1 @@ +export { CommunityPinTabButton } from './CommunityPinTabButton'; diff --git a/src/v4/social/elements/CommunityProfileMenuButton/CommunityProfileMenuButton.module.css b/src/v4/social/elements/CommunityProfileMenuButton/CommunityProfileMenuButton.module.css new file mode 100644 index 000000000..2d5e13162 --- /dev/null +++ b/src/v4/social/elements/CommunityProfileMenuButton/CommunityProfileMenuButton.module.css @@ -0,0 +1,6 @@ +.menuButton { + display: flex; + justify-content: center; + align-items: center; + fill: var(--asc-color-primary-shade4); +} diff --git a/src/v4/social/elements/CommunityProfileMenuButton/CommunityProfileMenuButton.tsx b/src/v4/social/elements/CommunityProfileMenuButton/CommunityProfileMenuButton.tsx new file mode 100644 index 000000000..4723e3ba7 --- /dev/null +++ b/src/v4/social/elements/CommunityProfileMenuButton/CommunityProfileMenuButton.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { IconComponent } from '~/v4/core/IconComponent'; +import { Button } from '~/v4/core/natives/Button'; +import styles from './CommunityProfileMenuButton.module.css'; +import clsx from 'clsx'; +import { EllipsisH } from '~/v4/icons/Ellipsis'; + +export interface CommunityProfileMenuButtonProps { + pageId?: string; + componentId?: string; + onClick?: () => void; + className?: string; + defaultClassName?: string; +} + +export function CommunityProfileMenuButton({ + pageId = '*', + componentId = '*', + onClick, + className, + defaultClassName, +}: CommunityProfileMenuButtonProps) { + const elementId = 'menu_button'; + const { isExcluded, accessibilityId, themeStyles, config, defaultConfig, uiReference } = + useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + return ( + <Button className={className} onPress={onClick} data-qa-anchor={accessibilityId}> + <IconComponent + defaultIcon={() => ( + <EllipsisH className={clsx(styles.menuButton, defaultClassName)} style={themeStyles} /> + )} + imgIcon={() => <img src={config.icon} alt={uiReference} />} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} + /> + </Button> + ); +} diff --git a/src/v4/social/elements/CommunityProfileMenuButton/index.tsx b/src/v4/social/elements/CommunityProfileMenuButton/index.tsx new file mode 100644 index 000000000..4ca489bd8 --- /dev/null +++ b/src/v4/social/elements/CommunityProfileMenuButton/index.tsx @@ -0,0 +1 @@ +export { CommunityProfileMenuButton } from './CommunityProfileMenuButton'; diff --git a/src/v4/social/elements/CommunityProfileTab/CommunityProfileTab.module.css b/src/v4/social/elements/CommunityProfileTab/CommunityProfileTab.module.css new file mode 100644 index 000000000..29698c649 --- /dev/null +++ b/src/v4/social/elements/CommunityProfileTab/CommunityProfileTab.module.css @@ -0,0 +1,25 @@ +.communityTabs__container { + background-color: var(--asc-color-base-background); + border-radius: 8px; + display: flex; + align-items: center; +} + +.communityTabs__tab { + display: flex; + align-items: flex-start; + justify-content: center; + padding: 1rem; + flex-grow: 1; + width: 4.5rem; + + --asc-icon-color: var(--asc-color-base-shade3); + + color: var(--asc-icon-color); +} + +.communityTabs__tab[data-is-active='true'] { + border-bottom: 2px solid var(--asc-color-primary-default); + + --asc-icon-color: var(--asc-color-base-default); +} diff --git a/src/v4/social/elements/CommunityProfileTab/CommunityProfileTab.tsx b/src/v4/social/elements/CommunityProfileTab/CommunityProfileTab.tsx new file mode 100644 index 000000000..b673a1cb8 --- /dev/null +++ b/src/v4/social/elements/CommunityProfileTab/CommunityProfileTab.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import styles from './CommunityProfileTab.module.css'; +import clsx from 'clsx'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { Button } from '~/v4/core/natives/Button'; +import { Feed as FeedIcon } from '~/v4/icons/Feed'; +import { Pin as PinIcon } from '~/v4/icons/Pin'; + +interface CommunityTabsProps { + pageId: string; + componentId?: string; + activeTab: 'community_feed' | 'community_pin'; + onTabChange: (tab: 'community_feed' | 'community_pin') => void; +} + +export const CommunityProfileTab: React.FC<CommunityTabsProps> = ({ + pageId, + componentId = '*', + activeTab, + onTabChange, +}) => { + const elementId = 'community_profile_tab'; + + const { isExcluded, accessibilityId, themeStyles } = useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + return ( + <div + data-qa-anchor={accessibilityId} + style={themeStyles} + className={styles.communityTabs__container} + > + <Button + data-is-active={activeTab === 'community_feed'} + onPress={() => onTabChange('community_feed')} + className={styles.communityTabs__tab} + > + <FeedIcon /> + </Button> + <Button + data-is-active={activeTab === 'community_pin'} + onPress={() => onTabChange('community_pin')} + className={styles.communityTabs__tab} + > + <PinIcon /> + </Button> + </div> + ); +}; diff --git a/src/v4/social/elements/CommunityProfileTab/index.ts b/src/v4/social/elements/CommunityProfileTab/index.ts new file mode 100644 index 000000000..276c79d9b --- /dev/null +++ b/src/v4/social/elements/CommunityProfileTab/index.ts @@ -0,0 +1 @@ +export { CommunityProfileTab } from './CommunityProfileTab'; diff --git a/src/v4/social/elements/CommunityVerifyBadge/CommunityVerifyBadge.tsx b/src/v4/social/elements/CommunityVerifyBadge/CommunityVerifyBadge.tsx new file mode 100644 index 000000000..d2a1b9442 --- /dev/null +++ b/src/v4/social/elements/CommunityVerifyBadge/CommunityVerifyBadge.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { IconComponent } from '~/v4/core/IconComponent'; +import { VerifyBadgeIcon } from '~/v4/icons/VerifyBadge'; + +interface CommunityVerifyBadgeProps { + pageId?: string; + componentId?: string; +} + +export const CommunityVerifyBadge = ({ + pageId = '*', + componentId = '*', +}: CommunityVerifyBadgeProps) => { + const elementId = 'community_verify_badge'; + const { config, themeStyles, accessibilityId, isExcluded, uiReference, defaultConfig } = + useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + return ( + <IconComponent + data-qa-anchor={accessibilityId} + defaultIcon={() => <VerifyBadgeIcon />} + imgIcon={() => <img src={config.icon} alt={uiReference} />} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} + /> + ); +}; diff --git a/src/v4/social/elements/CommunityVerifyBadge/index.ts b/src/v4/social/elements/CommunityVerifyBadge/index.ts new file mode 100644 index 000000000..b9d605686 --- /dev/null +++ b/src/v4/social/elements/CommunityVerifyBadge/index.ts @@ -0,0 +1 @@ +export { CommunityVerifyBadge } from './CommunityVerifyBadge'; diff --git a/src/v4/social/elements/MenuButton/MenuButton.module.css b/src/v4/social/elements/MenuButton/MenuButton.module.css index 2aed04dc5..f1f084701 100644 --- a/src/v4/social/elements/MenuButton/MenuButton.module.css +++ b/src/v4/social/elements/MenuButton/MenuButton.module.css @@ -1,3 +1,14 @@ .menuButton { + display: flex; + justify-content: center; + align-items: center; fill: var(--asc-color-base-default); } + +.menuButton__button { + display: flex; + align-items: center; + justify-content: center; + width: 1.5rem; + height: 1.125rem; +} diff --git a/src/v4/social/elements/MenuButton/MenuButton.tsx b/src/v4/social/elements/MenuButton/MenuButton.tsx index e2e8aa71b..904401111 100644 --- a/src/v4/social/elements/MenuButton/MenuButton.tsx +++ b/src/v4/social/elements/MenuButton/MenuButton.tsx @@ -3,19 +3,7 @@ import { useAmityElement } from '~/v4/core/hooks/uikit'; import { IconComponent } from '~/v4/core/IconComponent'; import { Button } from '~/v4/core/natives/Button'; import styles from './MenuButton.module.css'; - -const EllipsisH = ({ ...props }: React.SVGProps<SVGSVGElement>) => ( - <svg - width="16" - height="4" - viewBox="0 0 16 4" - fill="none" - xmlns="http://www.w3.org/2000/svg" - {...props} - > - <path d="M9.6875 2.25C9.6875 3.19922 8.91406 3.9375 8 3.9375C7.05078 3.9375 6.3125 3.19922 6.3125 2.25C6.3125 1.33594 7.05078 0.5625 8 0.5625C8.91406 0.5625 9.6875 1.33594 9.6875 2.25ZM13.9062 0.5625C14.8203 0.5625 15.5938 1.33594 15.5938 2.25C15.5938 3.19922 14.8203 3.9375 13.9062 3.9375C12.957 3.9375 12.2188 3.19922 12.2188 2.25C12.2188 1.33594 12.957 0.5625 13.9062 0.5625ZM2.09375 0.5625C3.00781 0.5625 3.78125 1.33594 3.78125 2.25C3.78125 3.19922 3.00781 3.9375 2.09375 3.9375C1.14453 3.9375 0.40625 3.19922 0.40625 2.25C0.40625 1.33594 1.14453 0.5625 2.09375 0.5625Z" /> - </svg> -); +import { EllipsisH } from '~/v4/icons/Ellipsis'; export interface MenuButtonProps { pageId?: string; @@ -35,7 +23,11 @@ export function MenuButton({ pageId = '*', componentId = '*', onClick }: MenuBut if (isExcluded) return null; return ( - <Button onPress={onClick} data-qa-anchor={accessibilityId}> + <Button + onPress={onClick} + data-qa-anchor={accessibilityId} + className={styles.menuButton__button} + > <IconComponent defaultIcon={() => <EllipsisH className={styles.menuButton} style={themeStyles} />} imgIcon={() => <img src={config.icon} alt={uiReference} />} diff --git a/src/v4/social/elements/NonMemberSection/NonMemberSection.tsx b/src/v4/social/elements/NonMemberSection/NonMemberSection.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/src/v4/social/elements/NonMemberSection/index.ts b/src/v4/social/elements/NonMemberSection/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/v4/social/elements/PinBadge/PinBadge.tsx b/src/v4/social/elements/PinBadge/PinBadge.tsx new file mode 100644 index 000000000..c9173e407 --- /dev/null +++ b/src/v4/social/elements/PinBadge/PinBadge.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { IconComponent } from '~/v4/core/IconComponent'; +import { PinBadgeIcon } from '~/v4/icons/PinBadge'; + +interface PinBadgeProps { + pageId: string; + componentId: string; +} + +export const PinBadge = ({ pageId, componentId }: PinBadgeProps) => { + const elementId = 'pin_badge'; + const { config, themeStyles, isExcluded, accessibilityId, uiReference, defaultConfig } = + useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + return ( + <IconComponent + data-qa-anchor={accessibilityId} + defaultIcon={() => <PinBadgeIcon />} + imgIcon={() => <img src={config.icon} alt={uiReference} />} + defaultIconName={defaultConfig.icon} + configIconName={config.icon} + /> + ); +}; diff --git a/src/v4/social/elements/PinBadge/index.ts b/src/v4/social/elements/PinBadge/index.ts new file mode 100644 index 000000000..13b643b0c --- /dev/null +++ b/src/v4/social/elements/PinBadge/index.ts @@ -0,0 +1 @@ +export { PinBadge } from './PinBadge'; diff --git a/src/v4/social/hooks/collections/usePostsCollection.ts b/src/v4/social/hooks/collections/usePostsCollection.ts new file mode 100644 index 000000000..d86e3cccf --- /dev/null +++ b/src/v4/social/hooks/collections/usePostsCollection.ts @@ -0,0 +1,27 @@ +import { PostRepository } from '@amityco/ts-sdk'; +import useLiveCollection from '~/v4/core/hooks/useLiveCollection'; + +const QUERY_LIMIT = 10; + +export default function usePostsCollection({ + targetType, + targetId, + feedType, + limit = QUERY_LIMIT, +}: Partial<Parameters<typeof PostRepository.getPosts>[0]>) { + const { items, ...rest } = useLiveCollection({ + fetcher: PostRepository.getPosts, + params: { + targetType, + targetId: targetId as string, + feedType, + limit, + }, + shouldCall: !!targetId && !!targetType, + }); + + return { + posts: items, + ...rest, + }; +} diff --git a/src/v4/social/hooks/useCommunityInfo.ts b/src/v4/social/hooks/useCommunityInfo.ts index 300d63707..cc55f3109 100644 --- a/src/v4/social/hooks/useCommunityInfo.ts +++ b/src/v4/social/hooks/useCommunityInfo.ts @@ -13,6 +13,7 @@ export const useCommunityInfo = (communityId?: string) => { communityId, shouldCall: !!communityId, }); + const avatarFileUrl = useImage({ fileId: community?.avatarFileId, imageSize: 'medium' }); const { posts: reviewingPosts } = usePostsCollection({ diff --git a/src/v4/social/internal-components/LockPrivateContent/LockPrivateContent.module.css b/src/v4/social/internal-components/LockPrivateContent/LockPrivateContent.module.css new file mode 100644 index 000000000..70c4473ec --- /dev/null +++ b/src/v4/social/internal-components/LockPrivateContent/LockPrivateContent.module.css @@ -0,0 +1,24 @@ +.lockPrivateContent__wrap { + margin: 5rem auto 3rem; + text-align: center; +} + +.lockPrivateContent__lockIcon { + width: 3.75rem; + height: 2.8125rem; + fill: var(--asc-color-base-shade4); +} + +.lockPrivateContent__title { + margin-top: 0.5rem; + font-size: 1.0625rem; + font-weight: 600; + line-height: 1rem; + color: var(--asc-color-base-shade3); +} + +.lockPrivateContent__body { + font-size: 0.8125rem; + font-weight: 400; + color: var(--asc-color-base-shade3); +} diff --git a/src/v4/social/internal-components/LockPrivateContent/LockPrivateContent.tsx b/src/v4/social/internal-components/LockPrivateContent/LockPrivateContent.tsx new file mode 100644 index 000000000..70901a4bb --- /dev/null +++ b/src/v4/social/internal-components/LockPrivateContent/LockPrivateContent.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import styles from './LockPrivateContent.module.css'; +import Lock from '~/v4/icons/Lock'; +import { Typography } from '~/v4/core/components'; + +export const LockPrivateContent = () => { + return ( + <div className={styles.lockPrivateContent__wrap}> + <Lock className={styles.lockPrivateContent__lockIcon} /> + <Typography.Title className={styles.lockPrivateContent__title}> + This community is private + </Typography.Title> + <Typography.Body className={styles.lockPrivateContent__body}> + Only invited members can see the posts. + </Typography.Body> + </div> + ); +}; diff --git a/src/v4/social/internal-components/LockPrivateContent/index.tsx b/src/v4/social/internal-components/LockPrivateContent/index.tsx new file mode 100644 index 000000000..5bc655db6 --- /dev/null +++ b/src/v4/social/internal-components/LockPrivateContent/index.tsx @@ -0,0 +1 @@ +export { LockPrivateContent as default } from './LockPrivateContent'; diff --git a/src/v4/social/pages/Application/index.tsx b/src/v4/social/pages/Application/index.tsx index 140a6f1b0..a268d62c8 100644 --- a/src/v4/social/pages/Application/index.tsx +++ b/src/v4/social/pages/Application/index.tsx @@ -17,6 +17,8 @@ import CommunityFeed from '~/social/pages/CommunityFeed'; import UserFeedPage from '~/social/pages/UserFeed'; import CommunityEditPage from '~/social/pages/CommunityEdit'; import ProfileSettings from '~/social/components/ProfileSettings'; +import { CommunityProfilePage } from '~/v4/social/pages/CommunityProfilePage'; +import { CommunityTabProvider } from '~/v4/core/providers/CommunityTabProvider'; const Application = () => { const { page } = useNavigation(); @@ -33,8 +35,15 @@ const Application = () => { <div className={styles.applicationContainer}> {page.type === PageTypes.SocialHomePage && <SocialHomePage />} {page.type === PageTypes.SocialGlobalSearchPage && <SocialGlobalSearchPage />} - {page.type === PageTypes.PostDetailPage && <PostDetailPage id={page.context?.postId} />} + {page.type === PageTypes.PostDetailPage && ( + <PostDetailPage id={page.context?.postId} hideTarget={page.context?.hideTarget} /> + )} {page.type === PageTypes.StoryTargetSelectionPage && <StoryTargetSelectionPage />} + {page.type === PageTypes.CommunityProfilePage && ( + <CommunityTabProvider> + <CommunityProfilePage communityId={page.context.communityId} /> + </CommunityTabProvider> + )} {page.type === PageTypes.ViewStoryPage && ( <ViewStoryPage type={page.context.storyType} targetId={page.context?.targetId} /> )} diff --git a/src/v4/social/pages/CommunityProfilePage/CommunityProfilePage.module.css b/src/v4/social/pages/CommunityProfilePage/CommunityProfilePage.module.css new file mode 100644 index 000000000..dc39006e0 --- /dev/null +++ b/src/v4/social/pages/CommunityProfilePage/CommunityProfilePage.module.css @@ -0,0 +1,59 @@ +.communityProfilePage__container { + overflow-y: auto; + height: 100%; + position: relative; + background-color: var(--asc-color-background-default); +} + +.communityProfilePage__header { + position: sticky; + top: 0; + background-color: var(--asc-color-background-default); +} + +.communityProfilePage__content { + position: relative; +} + +.communityProfilePage__refreshIndicator { + position: absolute; + top: 0; + left: 50%; + transform: translateX(-50%); +} + +.communityProfilePage__tabContainer { + position: relative; + z-index: 1; +} + +.communityProfilePage__tabContainer.sticky { + position: fixed; + top: 0; + left: 0; + right: 0; + background-color: var(--asc-color-background-default); +} + +.communityProfilePage__createPostButton { + position: fixed; + bottom: 1rem; + right: 1rem; +} + +.communityProfilePage__pullToRefresh { + height: var(--asc-pull-to-refresh-height); + display: flex; + justify-content: center; + align-items: center; + overflow: hidden; +} + +.communityProfilePage__pullToRefresh__spinner { + width: 1.25rem; + height: 1.25rem; + animation-name: spin; + animation-duration: 1000ms; + animation-iteration-count: infinite; + animation-timing-function: linear; +} diff --git a/src/v4/social/pages/CommunityProfilePage/CommunityProfilePage.tsx b/src/v4/social/pages/CommunityProfilePage/CommunityProfilePage.tsx new file mode 100644 index 000000000..b7fd48644 --- /dev/null +++ b/src/v4/social/pages/CommunityProfilePage/CommunityProfilePage.tsx @@ -0,0 +1,87 @@ +import React, { useRef, useState } from 'react'; +import styles from './CommunityProfilePage.module.css'; +import { useAmityPage } from '~/v4/core/hooks/uikit'; +import { CommunityHeader } from '~/v4/social/components/CommunityHeader'; +import { CommunityFeed } from '~/v4/social/components/CommunityFeed'; +import { CommunityProfileSkeleton } from '~/v4/social/pages/CommunityProfilePage/CommunityProfilePageSkeleton'; +import { useCommunityTabContext } from '~/v4/core/providers/CommunityTabProvider'; +import { CommunityPin } from '~/v4/social/components/CommunityPin'; +import useCommunity from '~/v4/core/hooks/collections/useCommunity'; +import { RefreshSpinner } from '~/v4/icons/RefreshSpinner'; + +interface CommunityProfileProps { + communityId: string; +} + +export const CommunityProfilePage: React.FC<CommunityProfileProps> = ({ communityId }) => { + const { activeTab } = useCommunityTabContext(); + const pageId = 'community_profile_page'; + const { themeStyles, accessibilityId } = useAmityPage({ + pageId, + }); + const { community } = useCommunity({ + communityId, + shouldCall: !!communityId, + }); + const containerRef = useRef(null); + + const touchStartY = useRef(0); + const [touchDiff, setTouchDiff] = useState(0); + + const renderTabContent = () => { + switch (activeTab) { + case 'community_feed': + return <CommunityFeed pageId={pageId} communityId={communityId} />; + case 'community_pin': + return <CommunityPin pageId={pageId} communityId={communityId} />; + default: + return null; + } + }; + + const handleRefresh = async () => {}; + + return ( + <div + ref={containerRef} + data-qa-anchor={accessibilityId} + className={styles.communityProfilePage__container} + style={themeStyles} + onDrag={(event) => event.stopPropagation()} + onTouchStart={(e) => { + touchStartY.current = e.touches[0].clientY; + }} + onTouchMove={(e) => { + const touchY = e.touches[0].clientY; + + if (touchStartY.current > touchY) { + return; + } + + setTouchDiff(Math.min(touchY - touchStartY.current, 100)); + }} + onTouchEnd={(e) => { + touchStartY.current = 0; + if (touchDiff >= 75) { + handleRefresh(); + } + setTouchDiff(0); + }} + > + <div + style={ + { + '--asc-pull-to-refresh-height': `${touchDiff}px`, + } as React.CSSProperties + } + className={styles.communityProfilePage__pullToRefresh} + > + <RefreshSpinner className={styles.communityProfilePage__pullToRefresh__spinner} /> + </div> + + {community ? <CommunityHeader community={community} /> : <CommunityProfileSkeleton />} + + {renderTabContent()} + </div> + ); +}; diff --git a/src/v4/social/pages/CommunityProfilePage/CommunityProfilePageSkeleton.tsx b/src/v4/social/pages/CommunityProfilePage/CommunityProfilePageSkeleton.tsx new file mode 100644 index 000000000..425581a6f --- /dev/null +++ b/src/v4/social/pages/CommunityProfilePage/CommunityProfilePageSkeleton.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import styles from './CommunityProfileSkeleton.module.css'; + +export const CommunityProfileSkeleton: React.FC = () => { + return ( + <div className={styles.communityProfileSkeleton__container}> + <div className={styles.communityProfileSkeleton__headerImage}></div> + <div className={styles.communityProfileSkeleton__content}> + <div className={styles.communityProfileSkeleton__title}></div> + <div className={styles.communityProfileSkeleton__category__container}> + <div className={styles.communityProfileSkeleton__category__item}></div> + <div className={styles.communityProfileSkeleton__category__item}></div> + <div className={styles.communityProfileSkeleton__category__item}></div> + </div> + <div className={styles.communityProfileSkeleton__description}></div> + <div className={styles.communityProfileSkeleton__description}></div> + <div className={styles.communityProfileSkeleton__communityInfo__container}> + <div className={styles.communityProfileSkeleton__communityInfo__info}></div> + <div className={styles.communityProfileSkeleton__communityInfo__info}></div> + </div> + <div className={styles.communityProfileSkeleton__storyTab}></div> + <div className={styles.communityProfileSkeleton__storyTab__title}></div> + </div> + </div> + ); +}; diff --git a/src/v4/social/pages/CommunityProfilePage/CommunityProfileSkeleton.module.css b/src/v4/social/pages/CommunityProfilePage/CommunityProfileSkeleton.module.css new file mode 100644 index 000000000..378252dfc --- /dev/null +++ b/src/v4/social/pages/CommunityProfilePage/CommunityProfileSkeleton.module.css @@ -0,0 +1,98 @@ +.communityProfileSkeleton__container { + width: 100%; + margin: 0 auto; + background-color: var(--asc-color-background-default); + overflow: hidden; + box-shadow: 0 0.0625rem 0.1875rem rgb(0 0 0 / 10%); +} + +.communityProfileSkeleton__headerImage { + width: 100%; + height: 12.5rem; + background-color: var(--asc-color-base-shade4); +} + +.communityProfileSkeleton__content { + padding: 1rem; +} + +.communityProfileSkeleton__title { + height: 1.5rem; + width: 60%; + background-color: var(--asc-color-base-shade4); + margin-bottom: 1rem; + border-radius: 0.25rem; +} + +.communityProfileSkeleton__category__container { + display: flex; + gap: 0.5rem; + margin-bottom: 1rem; +} + +.communityProfileSkeleton__category__item { + height: 1.25rem; + width: 3.75rem; + background-color: var(--asc-color-base-shade4); + border-radius: 0.625rem; +} + +.communityProfileSkeleton__description { + height: 1rem; + width: 100%; + background-color: var(--asc-color-base-shade4); + margin-bottom: 0.5rem; + border-radius: 0.25rem; +} + +.communityProfileSkeleton__communityInfo__container { + display: flex; + gap: 1rem; + margin-bottom: 1rem; +} + +.communityProfileSkeleton__communityInfo__info { + height: 1.25rem; + width: 5rem; + background-color: var(--asc-color-base-shade4); + border-radius: 0.25rem; +} + +.communityProfileSkeleton__storyTab { + width: 2.5rem; + height: 2.5rem; + border-radius: 50%; + background-color: var(--asc-color-base-shade4); + margin-bottom: 0.5rem; +} + +.communityProfileSkeleton__storyTab__title { + height: 1rem; + width: 2.5rem; + background-color: var(--asc-color-base-shade4); + border-radius: 0.25rem; +} + +@keyframes pulse { + 0% { + opacity: 1; + } + + 50% { + opacity: 0.5; + } + + 100% { + opacity: 1; + } +} + +.communityProfileSkeleton__headerImage, +.communityProfileSkeleton__title, +.communityProfileSkeleton__tag, +.communityProfileSkeleton__description, +.communityProfileSkeleton__stat, +.communityProfileSkeleton__avatar, +.communityProfileSkeleton__username { + animation: pulse 1.5s ease-in-out infinite; +} diff --git a/src/v4/social/pages/CommunityProfilePage/index.ts b/src/v4/social/pages/CommunityProfilePage/index.ts new file mode 100644 index 000000000..811cf1625 --- /dev/null +++ b/src/v4/social/pages/CommunityProfilePage/index.ts @@ -0,0 +1 @@ +export { CommunityProfilePage } from './CommunityProfilePage'; diff --git a/src/v4/social/pages/CommunityProfilePage/ui.stories.tsx b/src/v4/social/pages/CommunityProfilePage/ui.stories.tsx new file mode 100644 index 000000000..4b1a450f2 --- /dev/null +++ b/src/v4/social/pages/CommunityProfilePage/ui.stories.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import { Story, Meta } from '@storybook/react'; +import { CommunityProfilePage } from './CommunityProfilePage'; + +export default { + title: 'v4-social/pages/CommunityProfilePage', + component: CommunityProfilePage, + argTypes: { + community: { control: 'object' }, + featuredPost: { control: 'object' }, + }, +} as Meta; + +const Template: Story<{ community: any; featuredPost: any }> = (args) => ( + <CommunityProfilePage {...args} /> +); + +export const Default = Template.bind({}); +Default.args = { + community: { + name: 'Planet Savers', + headerImage: 'https://picsum.photos/800/400', // Placeholder image + tags: ['Environment', 'Forest', 'Earth Saver', 'Climate Change'], + description: + 'A community dedicated to communicating climate & justice. Another world is possible 🌍 Join us.', + posts: 135600, + members: 45600, + }, + featuredPost: { + authorName: 'Jackie Jones', + authorRole: 'Moderator', + authorAvatar: 'https://picsum.photos/100/100', // Placeholder image + date: '12 Jan', + title: 'Community Rules', + content: + 'Welcome to Planet Savers! To keep our community positive, please follow these rules:\n\nBe Respectful: No hate speech or bullying.\nStay On Topic: Focus on sustainability and climate action.\nNo Spam: Avoid irrelevant posts.\nCite Sources: Share credible info.\nEngage Positively: Support fellow members.', + likes: 10500, + comments: 10, + }, +}; + +export const NoFeaturedPost = Template.bind({}); +NoFeaturedPost.args = { + community: { + name: 'Ocean Guardians', + headerImage: 'https://picsum.photos/800/400?random=1', // Different placeholder image + tags: ['Ocean', 'Marine Life', 'Conservation'], + description: 'Protecting our oceans, one action at a time. Dive in and make a difference!', + posts: 50000, + members: 20000, + }, + featuredPost: null, +}; + +export const LongDescription = Template.bind({}); +LongDescription.args = { + community: { + name: 'Green Energy Innovators', + headerImage: 'https://picsum.photos/800/400?random=2', // Another different placeholder image + tags: ['Renewable Energy', 'Innovation', 'Sustainability'], + description: + "Exploring and promoting cutting-edge green energy solutions. From solar and wind to emerging technologies, we're at the forefront of the renewable revolution. Join us in powering a sustainable future!", + posts: 75000, + members: 30000, + }, + featuredPost: { + authorName: 'Alex Green', + authorRole: 'Energy Expert', + authorAvatar: 'https://picsum.photos/100/100?random=1', + date: '15 Feb', + title: 'The Future of Solar Energy', + content: + "Solar technology is advancing at an incredible pace. In this post, we'll explore the latest breakthroughs and what they mean for our clean energy future.", + likes: 5000, + comments: 120, + }, +}; diff --git a/src/v4/social/pages/PostDetailPage/PostDetailPage.module.css b/src/v4/social/pages/PostDetailPage/PostDetailPage.module.css index 814dc72bc..30f3e2427 100644 --- a/src/v4/social/pages/PostDetailPage/PostDetailPage.module.css +++ b/src/v4/social/pages/PostDetailPage/PostDetailPage.module.css @@ -82,6 +82,22 @@ padding: 0.75rem 1rem; } +.postDetailPage__divider { + width: 100%; + background-color: var(--asc-color-base-shade4); + height: 0.0625rem; +} + +.postDetailPage__notMember { + display: flex; + justify-content: start; + align-items: center; + gap: 0.25rem; + padding: 1rem; + line-height: 1.25rem; + color: var(--asc-color-base-shade2); +} + @keyframes skeleton-pulse { 0% { opacity: 0.6; diff --git a/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx b/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx index 9e159c741..da4c64c49 100644 --- a/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx +++ b/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { Typography } from '~/v4/core/components'; import { PostContent, PostContentSkeleton } from '~/v4/social/components/PostContent'; @@ -6,19 +6,25 @@ import { MenuButton } from '~/v4/social/elements/MenuButton'; import { PostMenu } from '~/v4/social/internal-components/PostMenu/PostMenu'; import usePost from '~/v4/core/hooks/objects/usePost'; -import { useNavigation } from '~/v4/core/providers/NavigationProvider'; +import { PageTypes, useNavigation } from '~/v4/core/providers/NavigationProvider'; import { BackButton } from '~/v4/social/elements/BackButton'; import { useAmityPage } from '~/v4/core/hooks/uikit'; import styles from './PostDetailPage.module.css'; import { useDrawer } from '~/v4/core/providers/DrawerProvider'; import { CommentComposer } from '~/v4/social/components/CommentComposer/CommentComposer'; import { CommentList } from '~/v4/social/components/CommentList/CommentList'; +import { + AmityPostCategory, + AmityPostContentComponentStyle, +} from '~/v4/social/components/PostContent/PostContent'; +import useCommunity from '~/v4/core/hooks/collections/useCommunity'; interface PostDetailPageProps { id: string; + hideTarget?: boolean; } -export function PostDetailPage({ id }: PostDetailPageProps) { +export function PostDetailPage({ id, hideTarget }: PostDetailPageProps) { const pageId = 'post_detail_page'; const { post, isLoading: isPostLoading } = usePost(id); const { themeStyles } = useAmityPage({ @@ -28,6 +34,15 @@ export function PostDetailPage({ id }: PostDetailPageProps) { const [replyComment, setReplyComment] = useState<Amity.Comment | undefined>(); const { setDrawerData, removeDrawerData } = useDrawer(); + const [communityId, setCommunityId] = useState<string | null | undefined>(null); + + useEffect(() => { + if (post?.targetId) { + setCommunityId(post.targetId); + } + }, [post?.targetId]); + + const { community } = useCommunity({ communityId }); return ( <div className={styles.postDetailPage} style={themeStyles}> @@ -36,7 +51,13 @@ export function PostDetailPage({ id }: PostDetailPageProps) { {isPostLoading ? ( <PostContentSkeleton pageId={pageId} /> ) : post ? ( - <PostContent pageId={pageId} post={post} type="detail" /> + <PostContent + pageId={pageId} + post={post} + category={AmityPostCategory.GENERAL} + style={AmityPostContentComponentStyle.DETAIL} + hideTarget={hideTarget} + /> ) : null} </div> <div className={styles.postDetailPage__comments__divider} data-is-loading={isPostLoading} /> @@ -70,13 +91,22 @@ export function PostDetailPage({ id }: PostDetailPageProps) { /> </div> </div> - {post && ( - <CommentComposer - referenceId={post.postId} - referenceType={'post'} - replyTo={replyComment} - onCancelReply={() => setReplyComment(undefined)} - /> + {post?.targetType === 'community' && !community?.isJoined ? ( + <div> + <div className={styles.postDetailPage__divider} /> + <Typography.Body className={styles.postDetailPage__notMember}> + Join community to interact with all posts + </Typography.Body> + </div> + ) : ( + post && ( + <CommentComposer + referenceId={post.postId} + referenceType={'post'} + replyTo={replyComment} + onCancelReply={() => setReplyComment(undefined)} + /> + ) )} </div> ); diff --git a/src/v4/social/pages/index.ts b/src/v4/social/pages/index.ts index 3ff55cd60..aae0eddcb 100644 --- a/src/v4/social/pages/index.ts +++ b/src/v4/social/pages/index.ts @@ -7,3 +7,4 @@ export { SelectPostTargetPage } from './SelectPostTargetPage'; export { MyCommunitiesSearchPage } from './MyCommunitiesSearchPage'; export { SocialGlobalSearchPage } from './SocialGlobalSearchPage'; export { PostDetailPage } from './PostDetailPage'; +export { CommunityProfilePage } from './CommunityProfilePage'; From 36980da6c5ff2963caa81304bbcf9159219a69d8 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Fri, 23 Aug 2024 15:30:14 +0700 Subject: [PATCH 287/300] fix: ASC-00000 - ulta fixes (#577) * fix: ASC-24926 - responsive image carousel (#574) * style: fix color item (#572) * fix: ASC-24930 - user profile about color (#573) * style: add bg * fix: color-scheme light * style: ratio edit profile * style: change rem to px * style: spacing * fix: ASC-24929 - mobile responsive edit community profile (#578) * style: mobile responsive * style: center * fix: ASC-24927 - mobile create community page (#576) * fix: mobile create community page * chore: update form layout * chore: ASC-24925 - post permissions (#579) * chore: add skeleton loader * chore: * fix: remove skeleton loader * fix: post permissions * fix: ASC-24931 - navigation (#582) * fix: hamburger navigation * fix: add hideSideMenu prop * fix: ASC-24924 - update each attachment type (#575) * fix: update each attachment type * fix: modal style on mobile screen * fix: not return just setPosts Co-authored-by: Bonn <pittawat@amity.co> --------- Co-authored-by: Bonn <pittawat@amity.co> * fix: ASC-24922 - remove subscription (#583) * fix: remove subscription on post and comment * chore: upgreade ts-sdk * fix: remove unused * fix: ASC-24930 - edit user profile ratio (#585) * style: mobile responsive * fix: responsive edit user button * fix: ASC-23387 - discard post (#586) * style: text modal color * fix: clear input * fix: modal block * fix: redirect page * fix: user comment link (#588) * fix: avatar click link (#591) * style: fix sticky side (#587) * fix: ASC-24978 - permission typo (#592) * fix: permission typo * chore: remove console.log * fix: ASC-24972 - handle error submit (#590) * fix: handle error submit * fix: remove log * fix: error type Co-authored-by: Bonn <pittawat@amity.co> * fix: type any --------- Co-authored-by: Bonn <pittawat@amity.co> * chore: upgrade sdk version (#593) * fix: bring back isModerator (#594) * fix: move hook to a top of a component * chore: upgrade sdk version (#596) * fix: export v3 provider * fix: remove unused code * chore: remove default font * chore: circular std font * chore: update max-width * fix: remove max-width on Explore * fix: remove min-width and min-height from a community card * fix: update CategoriesCard and RecommendedList layout * fix: ASC-25140 - active and hover state sidebar V3 (#604) * fix: z-index (#600) * style: active and hover state * fix: ASC-25143 - gallery grid (#605) * fix: grid 3 * fix: remove grid-gap * fix: ASC-00000 - skeleton css (#609) * fix: skeleton css * fix: lint # Conflicts: # src/core/components/Button/styles.tsx # src/core/components/SideMenuActionItem/styles.tsx # src/icons/Verified.tsx # src/social/components/CommunitiesList/index.tsx # src/social/components/CommunitiesList/styles.tsx # src/social/components/community/AllCommunities/index.tsx # src/social/components/community/Card/UICommunityCard.tsx # src/social/components/community/Header/UICommunityHeader.tsx # src/social/components/community/Header/styles.tsx # src/social/components/community/Name/index.tsx # src/social/components/community/Name/styles.tsx # src/social/components/community/TrendingItem/UITrendingItem.tsx # src/social/pages/Application/index.tsx # src/social/pages/Explore/styles.tsx # src/social/pages/UserFeed/index.tsx # src/v4/social/pages/Application/index.tsx * chore: revert fonts * Revert "chore: remove default font" This reverts commit b62f1aa5e77828772cd1fffd5a46d84480b35626. # Conflicts: # src/core/providers/UiKitProvider/index.css # src/v4/styles/global.css * chore: change sdk version to a latest one * fix: bring back v4 export --------- Co-authored-by: ChayanitBm <chayanit@amity.co> Co-authored-by: Pitchaya T. <33589608+ptchayap@users.noreply.github.com> --- package.json | 4 +- pnpm-lock.yaml | 210 +++++++++++------- src/core/components/Confirm/index.tsx | 8 +- src/core/components/Dropdown/styles.tsx | 2 +- .../components/GalleryGrid/TruncatedGrid.tsx | 151 ++++++++----- src/core/components/GalleryGrid/index.tsx | 3 + src/core/components/ImageGallery/index.tsx | 48 +++- src/core/components/ImageGallery/styles.tsx | 50 ++++- src/core/components/Modal/styles.tsx | 6 + src/core/components/ModalContainer.tsx | 6 + src/core/components/Notification/index.tsx | 13 +- src/core/components/Notification/styles.tsx | 8 + .../components/SideMenuActionItem/index.tsx | 7 +- .../components/SideMenuActionItem/styles.tsx | 10 +- .../components/Uploaders/File/StyledFile.tsx | 3 +- src/core/providers/ConfirmProvider.tsx | 1 + .../providers/UiKitProvider/fonts/inter.css | 56 +++-- src/core/providers/UiKitProvider/index.css | 55 ----- src/core/providers/UiKitProvider/index.tsx | 2 +- src/core/providers/UiKitProvider/styles.tsx | 11 + src/global.d.ts | 5 + src/i18n/en.json | 1 + .../components/Comment/StyledComment.tsx | 7 +- src/social/components/Comment/index.tsx | 7 +- src/social/components/Comment/styles.tsx | 2 + .../components/CommentLikeButton/index.tsx | 10 - src/social/components/CommentList/index.tsx | 50 ----- .../CommunityForm/AvatarUploader.tsx | 7 + .../components/CommunityForm/styles.tsx | 23 +- .../components/CommunityMembers/index.tsx | 5 - .../EngagementBar/UIEngagementBar.tsx | 5 - src/social/components/EngagementBar/index.tsx | 11 - src/social/components/Feed/index.tsx | 5 - src/social/components/MediaGallery/index.tsx | 4 +- .../components/ProfileSettings/index.tsx | 12 +- .../components/ProfileSettings/styles.tsx | 4 + .../SideSectionMyCommunity/index.tsx | 6 +- src/social/components/UserInfo/styles.tsx | 9 +- .../components/UserProfileForm/index.tsx | 18 +- .../category/CategoriesCard/index.tsx | 1 - .../components/community/Card/styles.tsx | 2 - .../components/community/Header/styles.tsx | 2 +- .../community/RecommendedList/index.tsx | 8 +- src/social/components/post/Creator/index.tsx | 10 + src/social/components/post/Editor/styles.tsx | 2 +- .../components/post/Editor/usePostEditor.ts | 20 +- .../components/post/GalleryContent/index.tsx | 3 + .../components/post/PollComposer/styles.tsx | 1 + .../post/Post/DefaultPostRenderer.tsx | 5 - src/social/components/post/Post/index.tsx | 13 +- src/social/constants.ts | 8 +- .../hooks/useCommunityPostPermission.ts | 69 ++++-- src/social/hooks/usePostByIds.ts | 9 +- src/social/layouts/Main/index.tsx | 5 + src/social/layouts/Page/index.tsx | 25 ++- src/social/pages/Application/index.tsx | 8 +- src/social/pages/CommunityEdit/styles.tsx | 3 + src/social/pages/CommunityFeed/index.tsx | 12 +- src/social/pages/CommunityFeed/styles.tsx | 2 +- src/social/pages/Explore/index.tsx | 56 ++++- src/social/pages/NewsFeed/index.tsx | 4 +- src/social/pages/NewsFeed/styles.tsx | 7 +- src/social/pages/UserFeed/styles.tsx | 2 +- src/social/providers/NavigationProvider.tsx | 4 +- src/social/providers/PostRendererProvider.tsx | 1 - .../pages/SocialHomePage/SocialHomePage.tsx | 4 +- 66 files changed, 685 insertions(+), 446 deletions(-) create mode 100644 src/core/components/ModalContainer.tsx delete mode 100644 src/core/providers/UiKitProvider/index.css diff --git a/package.json b/package.json index cb41b6e97..fc1c0d326 100644 --- a/package.json +++ b/package.json @@ -39,12 +39,12 @@ "tsc": "tsc" }, "peerDependencies": { - "@amityco/ts-sdk": "^6.27.0", + "@amityco/ts-sdk": "^6.29.2", "react": ">=17.0.2", "react-dom": ">=17.0.2" }, "devDependencies": { - "@amityco/ts-sdk": "^6.27.0", + "@amityco/ts-sdk": "^6.29.2", "@eslint/js": "^9.4.0", "@storybook/addon-a11y": "^7.6.7", "@storybook/addon-actions": "^7.6.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 596f9b382..956aae6e0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -133,8 +133,8 @@ importers: version: 3.23.8 devDependencies: '@amityco/ts-sdk': - specifier: ^6.27.0 - version: 6.27.0 + specifier: ^6.29.2 + version: 6.29.2 '@eslint/js': specifier: ^9.4.0 version: 9.7.0 @@ -170,7 +170,7 @@ importers: version: 7.6.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@4.9.5) '@storybook/react-vite': specifier: ^7.6.7 - version: 7.6.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.18.0)(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2)) + version: 7.6.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.18.0)(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.6)) '@storybook/theming': specifier: ^7.6.7 version: 7.6.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -218,7 +218,7 @@ importers: version: 6.21.0(eslint@9.7.0)(typescript@4.9.5) '@vitejs/plugin-react': specifier: ^4.2.1 - version: 4.3.1(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2)) + version: 4.3.1(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.6)) autoprefixer: specifier: ^10.4.19 version: 10.4.19(postcss@8.4.38) @@ -239,7 +239,7 @@ importers: version: 27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@9.7.0)(typescript@4.9.5))(eslint@9.7.0)(typescript@4.9.5))(eslint@9.7.0)(jest@29.7.0(@types/node@20.14.4))(typescript@4.9.5) eslint-plugin-prettier: specifier: ^5.1.3 - version: 5.1.3(@types/eslint@8.56.10)(eslint-config-prettier@9.1.0(eslint@9.7.0))(eslint@9.7.0)(prettier@3.3.1) + version: 5.1.3(@types/eslint@9.6.0)(eslint-config-prettier@9.1.0(eslint@9.7.0))(eslint@9.7.0)(prettier@3.3.1) globals: specifier: ^15.4.0 version: 15.8.0 @@ -305,18 +305,18 @@ importers: version: 5.1.0(typescript@4.9.5) vite: specifier: ^4.5.1 - version: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2) + version: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.6) vite-tsconfig-paths: specifier: ^4.2.3 - version: 4.3.2(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2)) + version: 4.3.2(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.6)) packages: '@adobe/css-tools@4.3.3': resolution: {integrity: sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==} - '@amityco/ts-sdk@6.27.0': - resolution: {integrity: sha512-6I55XYqhet6MDlB/qw9YunFO1BD2TwZSr2BBZ3+dWsBjBVp8BTDr7rPy74gpoVmPWRVbD4XTRM1c8dPeWeLEPQ==} + '@amityco/ts-sdk@6.29.2': + resolution: {integrity: sha512-g6+5gWmOKKgbQeWj8wC+zNEeTmYIMjOFqCSc+X/kog5Cs2EjlWgZzL3QrIb7ulnjQDZ/h8CC8ZepkGcEBJIkjQ==} engines: {node: '>=12', npm: '>=6'} '@ampproject/remapping@2.3.0': @@ -2892,6 +2892,9 @@ packages: '@types/eslint@8.56.10': resolution: {integrity: sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==} + '@types/eslint@9.6.0': + resolution: {integrity: sha512-gi6WQJ7cHRgZxtkQEoyHMppPjq9Kxo5Tjn2prSKDSmZrCz8TZ3jSRCeTJm+WoM+oB0WG37bRqLzaaU3q7JypGg==} + '@types/eslint__js@8.42.3': resolution: {integrity: sha512-alfG737uhmPdnvkrLdZLcEKJ/B8s9Y4hrZ+YAdzUeoArBlSUERA2E87ROfOaS4jd/C45fzOoZzidLc1IPwLqOw==} @@ -2967,12 +2970,12 @@ packages: '@types/node@18.19.36': resolution: {integrity: sha512-tX1BNmYSWEvViftB26VLNxT6mEr37M7+ldUtq7rlKnv4/2fKYsJIOmqJAjT6h1DNuwQjIKgw3VJ/Dtw3yiTIQw==} - '@types/node@20.14.10': - resolution: {integrity: sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==} - '@types/node@20.14.4': resolution: {integrity: sha512-1ChboN+57suCT2t/f8lwtPY/k3qTpuD/qnqQuYoBg6OQOcPyaw7PiZVdGpaZYAvhDDtqrt0oAaM8+oSu1xsUGw==} + '@types/node@22.5.0': + resolution: {integrity: sha512-DkFrJOe+rfdHTqqMg0bSNlGlQ85hSoh2TPzZyhHsXnMtligRWpxUySiyw8FY14ITt24HVCiQPWxS3KO/QlGmWg==} + '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -3461,8 +3464,8 @@ packages: resolution: {integrity: sha512-QbUdXJVTpvUTHU7871ppZkdOLBeGUKBQWHkHrvN2V9IQWGMt61zf3B45BtzjxEJzYuj0JBjBZP/hmYS/R9pmAw==} engines: {node: '>=4'} - axios@1.7.2: - resolution: {integrity: sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==} + axios@1.7.4: + resolution: {integrity: sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==} babel-core@7.0.0-bridge.0: resolution: {integrity: sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==} @@ -3584,8 +3587,8 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - browserslist@4.23.2: - resolution: {integrity: sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==} + browserslist@4.23.3: + resolution: {integrity: sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -3652,8 +3655,8 @@ packages: caniuse-lite@1.0.30001636: resolution: {integrity: sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg==} - caniuse-lite@1.0.30001641: - resolution: {integrity: sha512-Phv5thgl67bHYo1TtMY/MurjkHhV4EDaCosezRXgZ8jzA/Ub+wjxAvbGvjoFENStinwi5kCyOYV3mi5tOGykwA==} + caniuse-lite@1.0.30001651: + resolution: {integrity: sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==} caseless@0.12.0: resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} @@ -4056,6 +4059,15 @@ packages: supports-color: optional: true + debug@4.3.6: + resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decamelize-keys@1.1.1: resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} engines: {node: '>=0.10.0'} @@ -4197,8 +4209,8 @@ packages: electron-to-chromium@1.4.805: resolution: {integrity: sha512-8W4UJwX/w9T0QSzINJckTKG6CYpAUTqsaWcWIsdud3I1FYJcMgW9QqT1/4CBff/pP/TihWh13OmiyY8neto6vw==} - electron-to-chromium@1.4.825: - resolution: {integrity: sha512-OCcF+LwdgFGcsYPYC5keEEFC2XT0gBhrYbeGzHCx7i9qRFbzO/AqTmc/C/1xNhJj+JA7rzlN7mpBuStshh96Cg==} + electron-to-chromium@1.5.13: + resolution: {integrity: sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==} element-resize-detector@1.2.4: resolution: {integrity: sha512-Fl5Ftk6WwXE0wqCgNoseKWndjzZlDCwuPTcoVZfCP9R3EHQF8qUtr3YUPNETegRBOKqQKPW3n4kiIWngGi8tKg==} @@ -4230,8 +4242,8 @@ packages: engine.io-parser@2.1.3: resolution: {integrity: sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==} - enhanced-resolve@5.17.0: - resolution: {integrity: sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==} + enhanced-resolve@5.17.1: + resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==} engines: {node: '>=10.13.0'} env-paths@2.2.1: @@ -5409,8 +5421,8 @@ packages: lexical@0.16.1: resolution: {integrity: sha512-+R05d3+N945OY8pTUjTqQrWoApjC+ctzvjnmNETtx9WmVAaiW0tQVG+AYLt5pDGY8dQXtd4RPorvnxBTECt9SA==} - lib0@0.2.94: - resolution: {integrity: sha512-hZ3p54jL4Wpu7IOg26uC7dnEWiMyNlUrb9KoG7+xYs45WkQwpVvKFndVq2+pqLYKe1u8Fp3+zAfZHVvTK34PvQ==} + lib0@0.2.97: + resolution: {integrity: sha512-Q4d1ekgvufi9FiHkkL46AhecfNjznSL9MRNoJRQ76gBHS9OqU2ArfQK0FvBpuxgWeJeNI0LVgAYMIpsGeX4gYg==} engines: {node: '>=16'} hasBin: true @@ -5808,6 +5820,9 @@ packages: node-releases@2.0.14: resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} + node-releases@2.0.18: + resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} + normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} @@ -6725,8 +6740,8 @@ packages: socket.io-client@2.2.0: resolution: {integrity: sha512-56ZrkTDbdTLmBIyfFYesgOxsjcLnwAKoN4CiPyTVkMQj3zTUh0QAx3GbvIvLpFEOvQWu92yyWICxB0u7wkVbYA==} - socket.io-parser@3.3.3: - resolution: {integrity: sha512-qOg87q1PMWWTeO01768Yh9ogn7chB9zkKtQnya41Y355S0UmpXgpcrFwAgjYJxu9BdKug5r5e9YtVSeWhKBUZg==} + socket.io-parser@3.3.4: + resolution: {integrity: sha512-z/pFQB3x+EZldRRzORYW1vwVO8m/3ILkswtnpoeU6Ve3cbMWkmHEWDAVJn4QJtchiiFTo5j7UG2QvwxvaA9vow==} source-map-js@1.2.0: resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} @@ -7023,8 +7038,8 @@ packages: uglify-js: optional: true - terser@5.31.2: - resolution: {integrity: sha512-LGyRZVFm/QElZHy/CPr/O4eNZOZIzsrQ92y4v9UJe/pFJjypje2yI3C2FmPtvUEnhadlSbmG2nXtdcjHOjCfxw==} + terser@5.31.6: + resolution: {integrity: sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==} engines: {node: '>=10'} hasBin: true @@ -7277,6 +7292,9 @@ packages: undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + unicode-canonical-property-names-ecmascript@2.0.0: resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} engines: {node: '>=4'} @@ -7481,6 +7499,10 @@ packages: resolution: {integrity: sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==} engines: {node: '>=10.13.0'} + watchpack@2.4.2: + resolution: {integrity: sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==} + engines: {node: '>=10.13.0'} + wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} @@ -7669,11 +7691,11 @@ snapshots: '@adobe/css-tools@4.3.3': {} - '@amityco/ts-sdk@6.27.0': + '@amityco/ts-sdk@6.29.2': dependencies: agentkeepalive: 4.5.0 - axios: 1.7.2(debug@4.3.5) - debug: 4.3.5 + axios: 1.7.4(debug@4.3.6) + debug: 4.3.6 hls.js: 1.5.11 js-base64: 3.7.7 mitt: 3.0.1 @@ -7775,7 +7797,7 @@ snapshots: '@babel/core': 7.24.7 '@babel/helper-compilation-targets': 7.24.7 '@babel/helper-plugin-utils': 7.24.7 - debug: 4.3.5 + debug: 4.3.6 lodash.debounce: 4.0.8 resolve: 1.22.8 transitivePeerDependencies: @@ -9047,13 +9069,13 @@ snapshots: '@types/yargs': 17.0.32 chalk: 4.1.2 - '@joshwooding/vite-plugin-react-docgen-typescript@0.3.0(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2))': + '@joshwooding/vite-plugin-react-docgen-typescript@0.3.0(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.6))': dependencies: glob: 7.2.3 glob-promise: 4.2.2(glob@7.2.3) magic-string: 0.27.0 react-docgen-typescript: 2.2.2(typescript@4.9.5) - vite: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2) + vite: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.6) optionalDependencies: typescript: 4.9.5 @@ -10799,7 +10821,7 @@ snapshots: - encoding - supports-color - '@storybook/builder-vite@7.6.19(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2))': + '@storybook/builder-vite@7.6.19(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.6))': dependencies: '@storybook/channels': 7.6.19 '@storybook/client-logger': 7.6.19 @@ -10817,7 +10839,7 @@ snapshots: fs-extra: 11.2.0 magic-string: 0.30.10 rollup: 3.29.4 - vite: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2) + vite: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.6) optionalDependencies: typescript: 4.9.5 transitivePeerDependencies: @@ -11142,18 +11164,18 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@storybook/react-vite@7.6.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.18.0)(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2))': + '@storybook/react-vite@7.6.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.18.0)(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.6))': dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.3.0(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2)) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.3.0(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.6)) '@rollup/pluginutils': 5.1.0(rollup@4.18.0) - '@storybook/builder-vite': 7.6.19(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2)) + '@storybook/builder-vite': 7.6.19(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.6)) '@storybook/react': 7.6.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@4.9.5) - '@vitejs/plugin-react': 3.1.0(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2)) + '@vitejs/plugin-react': 3.1.0(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.6)) magic-string: 0.30.10 react: 18.3.1 react-docgen: 7.0.3 react-dom: 18.3.1(react@18.3.1) - vite: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2) + vite: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.6) transitivePeerDependencies: - '@preact/preset-vite' - encoding @@ -11304,7 +11326,7 @@ snapshots: '@types/eslint-scope@3.7.7': dependencies: - '@types/eslint': 8.56.10 + '@types/eslint': 9.6.0 '@types/estree': 1.0.5 '@types/eslint@8.56.10': @@ -11312,6 +11334,11 @@ snapshots: '@types/estree': 1.0.5 '@types/json-schema': 7.0.15 + '@types/eslint@9.6.0': + dependencies: + '@types/estree': 1.0.5 + '@types/json-schema': 7.0.15 + '@types/eslint__js@8.42.3': dependencies: '@types/eslint': 8.56.10 @@ -11394,13 +11421,13 @@ snapshots: dependencies: undici-types: 5.26.5 - '@types/node@20.14.10': + '@types/node@20.14.4': dependencies: undici-types: 5.26.5 - '@types/node@20.14.4': + '@types/node@22.5.0': dependencies: - undici-types: 5.26.5 + undici-types: 6.19.8 '@types/normalize-package-data@2.4.4': {} @@ -11578,7 +11605,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 7.16.1(typescript@4.9.5) '@typescript-eslint/utils': 7.16.1(eslint@9.7.0)(typescript@4.9.5) - debug: 4.3.5 + debug: 4.3.6 eslint: 9.7.0 ts-api-utils: 1.3.0(typescript@4.9.5) optionalDependencies: @@ -11691,25 +11718,25 @@ snapshots: '@typescript-eslint/types': 7.16.1 eslint-visitor-keys: 3.4.3 - '@vitejs/plugin-react@3.1.0(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2))': + '@vitejs/plugin-react@3.1.0(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.6))': dependencies: '@babel/core': 7.24.7 '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.24.7) '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.24.7) magic-string: 0.27.0 react-refresh: 0.14.2 - vite: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2) + vite: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.6) transitivePeerDependencies: - supports-color - '@vitejs/plugin-react@4.3.1(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2))': + '@vitejs/plugin-react@4.3.1(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.6))': dependencies: '@babel/core': 7.24.7 '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.24.7) '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.24.7) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2) + vite: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.6) transitivePeerDependencies: - supports-color @@ -11976,9 +12003,9 @@ snapshots: axe-core@4.9.1: {} - axios@1.7.2(debug@4.3.5): + axios@1.7.4(debug@4.3.6): dependencies: - follow-redirects: 1.15.6(debug@4.3.5) + follow-redirects: 1.15.6(debug@4.3.6) form-data: 4.0.0 proxy-from-env: 1.1.0 transitivePeerDependencies: @@ -12149,12 +12176,12 @@ snapshots: node-releases: 2.0.14 update-browserslist-db: 1.0.16(browserslist@4.23.1) - browserslist@4.23.2: + browserslist@4.23.3: dependencies: - caniuse-lite: 1.0.30001641 - electron-to-chromium: 1.4.825 - node-releases: 2.0.14 - update-browserslist-db: 1.1.0(browserslist@4.23.2) + caniuse-lite: 1.0.30001651 + electron-to-chromium: 1.5.13 + node-releases: 2.0.18 + update-browserslist-db: 1.1.0(browserslist@4.23.3) bs-logger@0.2.6: dependencies: @@ -12210,7 +12237,7 @@ snapshots: caniuse-lite@1.0.30001636: {} - caniuse-lite@1.0.30001641: {} + caniuse-lite@1.0.30001651: {} caseless@0.12.0: {} @@ -12634,6 +12661,10 @@ snapshots: dependencies: ms: 2.1.2 + debug@4.3.6: + dependencies: + ms: 2.1.2 + decamelize-keys@1.1.1: dependencies: decamelize: 1.2.0 @@ -12704,7 +12735,7 @@ snapshots: detect-port@1.6.1: dependencies: address: 1.2.2 - debug: 4.3.5 + debug: 4.3.6 transitivePeerDependencies: - supports-color @@ -12772,7 +12803,7 @@ snapshots: electron-to-chromium@1.4.805: {} - electron-to-chromium@1.4.825: {} + electron-to-chromium@1.5.13: {} element-resize-detector@1.2.4: dependencies: @@ -12818,7 +12849,7 @@ snapshots: blob: 0.0.5 has-binary2: 1.0.3 - enhanced-resolve@5.17.0: + enhanced-resolve@5.17.1: dependencies: graceful-fs: 4.2.11 tapable: 2.2.1 @@ -12947,14 +12978,14 @@ snapshots: - supports-color - typescript - eslint-plugin-prettier@5.1.3(@types/eslint@8.56.10)(eslint-config-prettier@9.1.0(eslint@9.7.0))(eslint@9.7.0)(prettier@3.3.1): + eslint-plugin-prettier@5.1.3(@types/eslint@9.6.0)(eslint-config-prettier@9.1.0(eslint@9.7.0))(eslint@9.7.0)(prettier@3.3.1): dependencies: eslint: 9.7.0 prettier: 3.3.1 prettier-linter-helpers: 1.0.0 synckit: 0.8.8 optionalDependencies: - '@types/eslint': 8.56.10 + '@types/eslint': 9.6.0 eslint-config-prettier: 9.1.0(eslint@9.7.0) eslint-scope@5.1.1: @@ -13262,9 +13293,9 @@ snapshots: flow-parser@0.238.0: {} - follow-redirects@1.15.6(debug@4.3.5): + follow-redirects@1.15.6(debug@4.3.6): optionalDependencies: - debug: 4.3.5 + debug: 4.3.6 for-each@0.3.3: dependencies: @@ -13791,7 +13822,7 @@ snapshots: istanbul-lib-source-maps@4.0.1: dependencies: - debug: 4.3.5 + debug: 4.3.6 istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: @@ -14106,7 +14137,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 20.14.10 + '@types/node': 22.5.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -14262,7 +14293,7 @@ snapshots: lexical@0.16.1: {} - lib0@0.2.94: + lib0@0.2.97: dependencies: isomorphic.js: 0.2.5 @@ -14555,7 +14586,7 @@ snapshots: mqtt-packet@6.10.0: dependencies: bl: 4.1.0 - debug: 4.3.5 + debug: 4.3.6 process-nextick-args: 2.0.1 transitivePeerDependencies: - supports-color @@ -14564,7 +14595,7 @@ snapshots: dependencies: commist: 1.1.0 concat-stream: 2.0.0 - debug: 4.3.5 + debug: 4.3.6 duplexify: 4.1.3 help-me: 3.0.0 inherits: 2.0.4 @@ -14657,6 +14688,8 @@ snapshots: node-releases@2.0.14: {} + node-releases@2.0.18: {} + normalize-package-data@2.5.0: dependencies: hosted-git-info: 2.8.9 @@ -14685,7 +14718,7 @@ snapshots: number-allocator@1.0.14: dependencies: - debug: 4.3.5 + debug: 4.3.6 js-sdsl: 4.3.0 transitivePeerDependencies: - supports-color @@ -15729,14 +15762,14 @@ snapshots: object-component: 0.0.3 parseqs: 0.0.5 parseuri: 0.0.5 - socket.io-parser: 3.3.3 + socket.io-parser: 3.3.4 to-array: 0.1.4 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - socket.io-parser@3.3.3: + socket.io-parser@3.3.4: dependencies: component-emitter: 1.3.1 debug: 3.1.0 @@ -16114,12 +16147,12 @@ snapshots: jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 - terser: 5.31.2 + terser: 5.31.6 webpack: 5.92.0(esbuild@0.19.12) optionalDependencies: esbuild: 0.19.12 - terser@5.31.2: + terser@5.31.6: dependencies: '@jridgewell/source-map': 0.3.6 acorn: 8.12.1 @@ -16346,6 +16379,8 @@ snapshots: undici-types@5.26.5: {} + undici-types@6.19.8: {} + unicode-canonical-property-names-ecmascript@2.0.0: {} unicode-match-property-ecmascript@2.0.0: @@ -16395,9 +16430,9 @@ snapshots: escalade: 3.1.2 picocolors: 1.0.1 - update-browserslist-db@1.1.0(browserslist@4.23.2): + update-browserslist-db@1.1.0(browserslist@4.23.3): dependencies: - browserslist: 4.23.2 + browserslist: 4.23.3 escalade: 3.1.2 picocolors: 1.0.1 @@ -16493,18 +16528,18 @@ snapshots: core-util-is: 1.0.2 extsprintf: 1.3.0 - vite-tsconfig-paths@4.3.2(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2)): + vite-tsconfig-paths@4.3.2(typescript@4.9.5)(vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.6)): dependencies: debug: 4.3.5 globrex: 0.1.2 tsconfck: 3.1.0(typescript@4.9.5) optionalDependencies: - vite: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2) + vite: 4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.6) transitivePeerDependencies: - supports-color - typescript - vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.2): + vite@4.5.3(@types/node@20.14.4)(less@4.2.0)(sass@1.77.6)(stylus@0.62.0)(terser@5.31.6): dependencies: esbuild: 0.18.20 postcss: 8.4.38 @@ -16515,7 +16550,7 @@ snapshots: less: 4.2.0 sass: 1.77.6 stylus: 0.62.0 - terser: 5.31.2 + terser: 5.31.6 walker@1.0.8: dependencies: @@ -16526,6 +16561,11 @@ snapshots: glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 + watchpack@2.4.2: + dependencies: + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + wcwidth@1.0.1: dependencies: defaults: 1.0.4 @@ -16547,9 +16587,9 @@ snapshots: '@webassemblyjs/wasm-parser': 1.12.1 acorn: 8.12.1 acorn-import-attributes: 1.9.5(acorn@8.12.1) - browserslist: 4.23.2 + browserslist: 4.23.3 chrome-trace-event: 1.0.4 - enhanced-resolve: 5.17.0 + enhanced-resolve: 5.17.1 es-module-lexer: 1.5.4 eslint-scope: 5.1.1 events: 3.3.0 @@ -16562,7 +16602,7 @@ snapshots: schema-utils: 3.3.0 tapable: 2.2.1 terser-webpack-plugin: 5.3.10(esbuild@0.19.12)(webpack@5.92.0(esbuild@0.19.12)) - watchpack: 2.4.1 + watchpack: 2.4.2 webpack-sources: 3.2.3 transitivePeerDependencies: - '@swc/core' @@ -16691,7 +16731,7 @@ snapshots: yjs@13.6.17: dependencies: - lib0: 0.2.94 + lib0: 0.2.97 yocto-queue@0.1.0: {} diff --git a/src/core/components/Confirm/index.tsx b/src/core/components/Confirm/index.tsx index 9470c6621..9abd1ae94 100644 --- a/src/core/components/Confirm/index.tsx +++ b/src/core/components/Confirm/index.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { useConfirmContext } from '~/core/providers/ConfirmProvider'; +import { ModalContainer } from '../ModalContainer'; import { ConfirmModal, @@ -61,9 +62,14 @@ export const ConfirmComponent = () => { const onOk = () => { closeConfirm(); confirmData?.onOk && confirmData.onOk(); + confirmData?.onSuccess && confirmData?.onSuccess(); }; - return <Confirm {...confirmData} onCancel={onCancel} onOk={onOk} />; + return ( + <ModalContainer> + <Confirm {...confirmData} onCancel={onCancel} onOk={onOk} /> + </ModalContainer> + ); }; export default Confirm; diff --git a/src/core/components/Dropdown/styles.tsx b/src/core/components/Dropdown/styles.tsx index a9c005e92..748f10110 100644 --- a/src/core/components/Dropdown/styles.tsx +++ b/src/core/components/Dropdown/styles.tsx @@ -26,7 +26,7 @@ export const Frame = styled.div<{ scrollableHeight?: number; }>` position: absolute; - z-index: 2; + z-index: 10; ${({ position }) => getCssPosition(position)} ${({ align }) => align && getCssPosition(align)} background: ${({ theme }) => theme.palette.system.background}; diff --git a/src/core/components/GalleryGrid/TruncatedGrid.tsx b/src/core/components/GalleryGrid/TruncatedGrid.tsx index d6f22d283..96bee1b42 100644 --- a/src/core/components/GalleryGrid/TruncatedGrid.tsx +++ b/src/core/components/GalleryGrid/TruncatedGrid.tsx @@ -1,5 +1,5 @@ import React, { useMemo } from 'react'; -import styled from 'styled-components'; +import styled, { css } from 'styled-components'; import cx from 'clsx'; import Square from '~/core/components/Square'; @@ -34,64 +34,101 @@ import Square from '~/core/components/Square'; => ((100% / 3) / .75) */ -const Gallery = styled.div<{ count?: number }>` +const Gallery = styled.div<{ count?: number; grid?: boolean }>` display: grid; width: 100%; - height: 100%; - grid-template-columns: repeat(6, 1fr); - grid-template-rows: 1fr calc((100% / 3) / 0.75); - grid-gap: 0.5rem; - border-radius: 4px; - - &.one > :nth-child(1) { - grid-column: 1 / 7; - grid-row: 1 / 3; - } - - &.two > :nth-child(1) { - grid-column: 1 / 4; - grid-row: 1 / 3; - } - - &.two > :nth-child(2) { - grid-column: 4 / 7; - grid-row: 1 / 3; - } - - &.three > :nth-child(1), - &.four > :nth-child(1), - &.many > :nth-child(1) { - grid-column: 1 / 7; - grid-row: 1 / 2; - } - &.three > :nth-child(2) { - grid-column: 1 / 4; - grid-row: 2 / 3; - } - - &.three > :nth-child(3) { - grid-column: 4 / 7; - grid-row: 2 / 3; - } - - &.four > :nth-child(2), - &.many > :nth-child(2) { - grid-column: 1 / 3; - grid-row: 2 / 3; - } - - &.four > :nth-child(3), - &.many > :nth-child(3) { - grid-column: 3 / 5; - grid-row: 2 / 3; - } + gap: 0.5rem; + border-radius: 4px; - &.four > :nth-child(4), - &.many > :nth-child(4) { - grid-column: 5 / 7; - grid-row: 2 / 3; - } + ${({ grid }) => + grid && + css` + grid-template-columns: repeat(auto-fill, minmax(30%, 1fr)); + + > * { + position: relative; + box-sizing: border-box; + + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 100%; + + > * { + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 100%; + } + + &::before { + content: ''; + display: block; + padding-top: 100%; + } + } + `} + + ${({ grid }) => + !grid && + css` + height: 100%; + grid-template-columns: repeat(6, 1fr); + grid-template-rows: 1fr calc((100% / 3) / 0.75); + + &.one > :nth-child(1) { + grid-column: 1 / 7; + grid-row: 1 / 3; + } + + &.two > :nth-child(1) { + grid-column: 1 / 4; + grid-row: 1 / 3; + } + + &.two > :nth-child(2) { + grid-column: 4 / 7; + grid-row: 1 / 3; + } + + &.three > :nth-child(1), + &.four > :nth-child(1), + &.many > :nth-child(1) { + grid-column: 1 / 7; + grid-row: 1 / 2; + } + + &.three > :nth-child(2) { + grid-column: 1 / 4; + grid-row: 2 / 3; + } + + &.three > :nth-child(3) { + grid-column: 4 / 7; + grid-row: 2 / 3; + } + + &.four > :nth-child(2), + &.many > :nth-child(2) { + grid-column: 1 / 3; + grid-row: 2 / 3; + } + + &.four > :nth-child(3), + &.many > :nth-child(3) { + grid-column: 3 / 5; + grid-row: 2 / 3; + } + + &.four > :nth-child(4), + &.many > :nth-child(4) { + grid-column: 5 / 7; + grid-row: 2 / 3; + } + `} `; export const TruncatedGridCell = styled.div` @@ -128,6 +165,7 @@ interface TruncatedGridProps<T extends Amity.Post> { itemKey?: keyof T; onClick?: (index: number) => void; renderItem: (item: T) => React.ReactNode; + grid?: boolean; } const TruncatedGrid = <T extends Amity.Post>({ @@ -136,6 +174,7 @@ const TruncatedGrid = <T extends Amity.Post>({ renderItem, items = [], itemKey, + grid, }: TruncatedGridProps<T>) => { const config = useMemo(() => { switch (items.length) { @@ -154,7 +193,7 @@ const TruncatedGrid = <T extends Amity.Post>({ return ( <Square ratio={0.75} className={className}> - <Gallery className={cx(config)} count={items.length}> + <Gallery className={cx(config)} count={items.length} grid={grid}> {items.slice(0, 3).map((item, index) => ( <TruncatedGridCell key={`#${itemKey ? item[itemKey] : index}`} diff --git a/src/core/components/GalleryGrid/index.tsx b/src/core/components/GalleryGrid/index.tsx index aaa6a913c..8a40d8449 100644 --- a/src/core/components/GalleryGrid/index.tsx +++ b/src/core/components/GalleryGrid/index.tsx @@ -10,6 +10,7 @@ interface GalleryGridProps<T> { truncate?: boolean; onClick?: (index: number) => void; renderItem: (item: T) => React.ReactNode; + grid?: boolean; } const GalleryGrid = <T,>({ @@ -19,6 +20,7 @@ const GalleryGrid = <T,>({ truncate, onClick, renderItem, + grid, }: GalleryGridProps<T>) => { if (truncate || items.length <= 4) { return ( @@ -28,6 +30,7 @@ const GalleryGrid = <T,>({ renderItem={renderItem} items={items} itemKey={itemKey} + grid={grid} /> ); } diff --git a/src/core/components/ImageGallery/index.tsx b/src/core/components/ImageGallery/index.tsx index cb27222ba..8dbd889f7 100644 --- a/src/core/components/ImageGallery/index.tsx +++ b/src/core/components/ImageGallery/index.tsx @@ -2,8 +2,18 @@ import React, { ReactNode } from 'react'; import useKeyboard from '~/core/hooks/useKeyboard'; -import { Container, Frame, Counter, LeftButton, RightButton, CloseButton } from './styles'; +import { + Container, + Frame, + Counter, + LeftButton, + RightButton, + CloseButton, + InnerContainer, + GridContainer, +} from './styles'; import { useCustomComponent } from '~/core/providers/CustomComponentsProvider'; +import { useMedia } from 'react-use'; interface ImageGalleryProps<T extends Amity.Post> { index?: number; @@ -29,16 +39,40 @@ const ImageGallery = <T extends Amity.Post>({ useKeyboard('ArrowRight', next); useKeyboard('Escape', handleClose); + const isDesktop = useMedia('(min-width: 768px)'); + + if (isDesktop) { + return ( + <Container> + <InnerContainer> + <GridContainer> + <Frame>{renderItem(items[index])}</Frame> + + {showCounter && <Counter>{`${index + 1} / ${items.length}`}</Counter>} + + {items.length > 1 && <LeftButton onClick={prev} />} + {items.length > 1 && <RightButton onClick={next} />} + + <CloseButton onClick={handleClose} /> + </GridContainer> + </InnerContainer> + </Container> + ); + } + return ( <Container> - <Frame>{renderItem(items[index])}</Frame> - - {showCounter && <Counter>{`${index + 1} / ${items.length}`}</Counter>} + <InnerContainer> + <Frame>{renderItem(items[index])}</Frame> + <GridContainer> + {showCounter && <Counter>{`${index + 1} / ${items.length}`}</Counter>} - {items.length > 1 && <LeftButton onClick={prev} />} - {items.length > 1 && <RightButton onClick={next} />} + {items.length > 1 && <LeftButton onClick={prev} />} + {items.length > 1 && <RightButton onClick={next} />} - <CloseButton onClick={handleClose} /> + <CloseButton onClick={handleClose} /> + </GridContainer> + </InnerContainer> </Container> ); }; diff --git a/src/core/components/ImageGallery/styles.tsx b/src/core/components/ImageGallery/styles.tsx index 583cb6cc7..a00999bf3 100644 --- a/src/core/components/ImageGallery/styles.tsx +++ b/src/core/components/ImageGallery/styles.tsx @@ -8,14 +8,6 @@ export const Container = styled.div` position: fixed; overflow: hidden; - display: grid; - grid-gap: 1rem 3rem; - grid-template-columns: 2rem auto 2rem; - grid-template-rows: min-content auto; - grid-template-areas: - 'none counter close' - 'left image right'; - align-items: center; top: 0; @@ -24,7 +16,6 @@ export const Container = styled.div` left: 0; width: 100vw; height: 100vh; - padding: 3rem; background: rgba(0, 0, 0, 0.75); color: ${({ theme }) => theme.palette.system.background}; @@ -45,6 +36,34 @@ export const Container = styled.div` } `; +export const InnerContainer = styled.div` + z-index: 9999; + width: 100%; + height: 100%; + position: relative; +`; + +export const GridContainer = styled.div` + position: relative; + z-index: 9999; + display: grid; + grid-gap: 1rem 3rem; + grid-template-columns: 2rem auto 2rem; + grid-template-rows: min-content auto; + grid-template-areas: + 'none counter close' + 'left image right'; + + width: 100%; + height: 100%; + + padding: 2rem 1rem 2rem 1rem; + + @media (min-width: 768px) { + padding: 3rem; + } +`; + const Image = styled.img.attrs({ loading: 'lazy' })` display: block; width: 100%; @@ -56,10 +75,21 @@ const Image = styled.img.attrs({ loading: 'lazy' })` export const ImageRenderer = (url: string) => <Image key={url} src={url} />; export const Frame = styled.div` - grid-area: image; + z-index: 9999; width: 100%; height: 100%; overflow: hidden; + + position: absolute; + top: 0; + left: 0; + + @media (min-width: 768px) { + position: unset; + top: unset; + left: unset; + grid-area: image; + } `; export const Counter = styled.div` diff --git a/src/core/components/Modal/styles.tsx b/src/core/components/Modal/styles.tsx index 1e277816f..98f3543ec 100644 --- a/src/core/components/Modal/styles.tsx +++ b/src/core/components/Modal/styles.tsx @@ -44,10 +44,16 @@ export const ModalWindow = styled.div` max-width: 520px; min-width: 360px; ${({ theme }) => theme.typography.body} + color: ${({ theme }) => theme.palette.neutral.main}; &:focus { outline: none; } + + @media (max-width: 520px) { + width: 95vw; + min-width: unset; + } `; const SmallModalWindow = styled(ModalWindow)` diff --git a/src/core/components/ModalContainer.tsx b/src/core/components/ModalContainer.tsx new file mode 100644 index 000000000..6a5a6db0c --- /dev/null +++ b/src/core/components/ModalContainer.tsx @@ -0,0 +1,6 @@ +import React from 'react'; +import { createPortal } from 'react-dom'; + +export const ModalContainer = ({ children }: { children: React.ReactNode }) => { + return createPortal(children, document.body); +}; diff --git a/src/core/components/Notification/index.tsx b/src/core/components/Notification/index.tsx index 0a84c14fa..b494a7049 100644 --- a/src/core/components/Notification/index.tsx +++ b/src/core/components/Notification/index.tsx @@ -1,5 +1,6 @@ import React, { ReactNode } from 'react'; import { useNotificationData } from '~/core/providers/NotificationProvider'; +import { ModalContainer } from '../ModalContainer'; import { NotificationContainer, Notifications } from './styles'; @@ -20,11 +21,13 @@ export const NotificationsContainer = () => { const notifications = useNotificationData(); return ( - <Notifications> - {notifications.map((notificationData) => { - return <Notification {...notificationData} key={notificationData.id} />; - })} - </Notifications> + <ModalContainer> + <Notifications> + {notifications.map((notificationData) => { + return <Notification {...notificationData} key={notificationData.id} />; + })} + </Notifications> + </ModalContainer> ); }; diff --git a/src/core/components/Notification/styles.tsx b/src/core/components/Notification/styles.tsx index 0fbc266c6..b8ebf89c0 100644 --- a/src/core/components/Notification/styles.tsx +++ b/src/core/components/Notification/styles.tsx @@ -54,4 +54,12 @@ export const NotificationContainer = styled.div` } } pointer-events: auto; + + @media (max-width: 780px) { + width: calc(100% - 32px); + padding: 8px 16px; + display: flex; + justify-content: center; + text-align: center; + } `; diff --git a/src/core/components/SideMenuActionItem/index.tsx b/src/core/components/SideMenuActionItem/index.tsx index 7af742320..c07b7342a 100644 --- a/src/core/components/SideMenuActionItem/index.tsx +++ b/src/core/components/SideMenuActionItem/index.tsx @@ -26,7 +26,12 @@ const SideMenuActionItem = ({ }: SideMenuActionItemProps) => { if (element === 'a') { return ( - <AnchorActionItem data-qa-anchor={dataQaAnchor} className={className} onClick={onClick}> + <AnchorActionItem + data-qa-anchor={dataQaAnchor} + className={className} + onClick={onClick} + active={active} + > {icon && <IconWrapper active={active}>{icon}</IconWrapper>} <span className="actionItemChild">{children}</span> </AnchorActionItem> diff --git a/src/core/components/SideMenuActionItem/styles.tsx b/src/core/components/SideMenuActionItem/styles.tsx index 1b5dcf3a6..aa464daaa 100644 --- a/src/core/components/SideMenuActionItem/styles.tsx +++ b/src/core/components/SideMenuActionItem/styles.tsx @@ -20,7 +20,7 @@ const actionItemContainerStyles = css` color: ${({ theme }) => theme.palette.neutral.main}; justify-content: left; &:hover:not(:disabled) { - background-color: ${({ theme }) => theme.palette.base.shade4}; + background-color: ${({ theme }) => theme.palette.primary.shade3}; } &:disabled { color: ${({ theme }) => theme.palette.neutral.shade2}; @@ -34,11 +34,17 @@ export const ButtonActionItem = styled(SecondaryButton)` width: 100%; `; -export const AnchorActionItem = styled.a` +export const AnchorActionItem = styled.a<{ active?: boolean }>` cursor: pointer; border-radius: 4px; ${actionItemContainerStyles} ${({ theme }) => theme.typography.bodyBold} + ${({ active, theme }) => + active && + css` + color: ${theme.palette.primary.main}; + background-color: ${theme.palette.primary.shade3}; + `}; `; export const IconWrapper = styled.div<{ active?: boolean }>` diff --git a/src/core/components/Uploaders/File/StyledFile.tsx b/src/core/components/Uploaders/File/StyledFile.tsx index 33f473150..b6f7151c6 100644 --- a/src/core/components/Uploaders/File/StyledFile.tsx +++ b/src/core/components/Uploaders/File/StyledFile.tsx @@ -79,7 +79,8 @@ const StyledFile = ({ <RemoveButton data-qa-anchor="uploaders-file-remove-button" onClick={removeCallback} - disabled={isUploading} + // NOTE: This component also be used to show files when edit, The progress is not increased, remain -1 all the time + disabled={progress !== -1 && isUploading} /> )} </ButtonContainer> diff --git a/src/core/providers/ConfirmProvider.tsx b/src/core/providers/ConfirmProvider.tsx index 2e70970d5..7441aa146 100644 --- a/src/core/providers/ConfirmProvider.tsx +++ b/src/core/providers/ConfirmProvider.tsx @@ -12,6 +12,7 @@ type ConfirmType = { okText?: ReactNode; cancelText?: ReactNode; 'data-qa-anchor'?: string; + onSuccess?: () => void; }; interface ConfirmContextProps { diff --git a/src/core/providers/UiKitProvider/fonts/inter.css b/src/core/providers/UiKitProvider/fonts/inter.css index d92263820..302e658ed 100644 --- a/src/core/providers/UiKitProvider/fonts/inter.css +++ b/src/core/providers/UiKitProvider/fonts/inter.css @@ -3,14 +3,17 @@ font-style: normal; font-weight: 100; font-display: swap; - src: url('Inter-Thin.woff2?v=3.19') format('woff2'), url('Inter-Thin.woff?v=3.19') format('woff'); + src: + url('Inter-Thin.woff2?v=3.19') format('woff2'), + url('Inter-Thin.woff?v=3.19') format('woff'); } @font-face { font-family: 'Inter'; font-style: italic; font-weight: 100; font-display: swap; - src: url('Inter-ThinItalic.woff2?v=3.19') format('woff2'), + src: + url('Inter-ThinItalic.woff2?v=3.19') format('woff2'), url('Inter-ThinItalic.woff?v=3.19') format('woff'); } @@ -19,7 +22,8 @@ font-style: normal; font-weight: 200; font-display: swap; - src: url('Inter-ExtraLight.woff2?v=3.19') format('woff2'), + src: + url('Inter-ExtraLight.woff2?v=3.19') format('woff2'), url('Inter-ExtraLight.woff?v=3.19') format('woff'); } @font-face { @@ -27,7 +31,8 @@ font-style: italic; font-weight: 200; font-display: swap; - src: url('Inter-ExtraLightItalic.woff2?v=3.19') format('woff2'), + src: + url('Inter-ExtraLightItalic.woff2?v=3.19') format('woff2'), url('Inter-ExtraLightItalic.woff?v=3.19') format('woff'); } @@ -36,7 +41,8 @@ font-style: normal; font-weight: 300; font-display: swap; - src: url('Inter-Light.woff2?v=3.19') format('woff2'), + src: + url('Inter-Light.woff2?v=3.19') format('woff2'), url('Inter-Light.woff?v=3.19') format('woff'); } @font-face { @@ -44,7 +50,8 @@ font-style: italic; font-weight: 300; font-display: swap; - src: url('Inter-LightItalic.woff2?v=3.19') format('woff2'), + src: + url('Inter-LightItalic.woff2?v=3.19') format('woff2'), url('Inter-LightItalic.woff?v=3.19') format('woff'); } @@ -53,7 +60,8 @@ font-style: normal; font-weight: 400; font-display: swap; - src: url('Inter-Regular.woff2?v=3.19') format('woff2'), + src: + url('Inter-Regular.woff2?v=3.19') format('woff2'), url('Inter-Regular.woff?v=3.19') format('woff'); } @font-face { @@ -61,7 +69,8 @@ font-style: italic; font-weight: 400; font-display: swap; - src: url('Inter-Italic.woff2?v=3.19') format('woff2'), + src: + url('Inter-Italic.woff2?v=3.19') format('woff2'), url('Inter-Italic.woff?v=3.19') format('woff'); } @@ -70,7 +79,8 @@ font-style: normal; font-weight: 500; font-display: swap; - src: url('Inter-Medium.woff2?v=3.19') format('woff2'), + src: + url('Inter-Medium.woff2?v=3.19') format('woff2'), url('Inter-Medium.woff?v=3.19') format('woff'); } @font-face { @@ -78,7 +88,8 @@ font-style: italic; font-weight: 500; font-display: swap; - src: url('Inter-MediumItalic.woff2?v=3.19') format('woff2'), + src: + url('Inter-MediumItalic.woff2?v=3.19') format('woff2'), url('Inter-MediumItalic.woff?v=3.19') format('woff'); } @@ -87,7 +98,8 @@ font-style: normal; font-weight: 600; font-display: swap; - src: url('Inter-SemiBold.woff2?v=3.19') format('woff2'), + src: + url('Inter-SemiBold.woff2?v=3.19') format('woff2'), url('Inter-SemiBold.woff?v=3.19') format('woff'); } @font-face { @@ -95,7 +107,8 @@ font-style: italic; font-weight: 600; font-display: swap; - src: url('Inter-SemiBoldItalic.woff2?v=3.19') format('woff2'), + src: + url('Inter-SemiBoldItalic.woff2?v=3.19') format('woff2'), url('Inter-SemiBoldItalic.woff?v=3.19') format('woff'); } @@ -104,14 +117,17 @@ font-style: normal; font-weight: 700; font-display: swap; - src: url('Inter-Bold.woff2?v=3.19') format('woff2'), url('Inter-Bold.woff?v=3.19') format('woff'); + src: + url('Inter-Bold.woff2?v=3.19') format('woff2'), + url('Inter-Bold.woff?v=3.19') format('woff'); } @font-face { font-family: 'Inter'; font-style: italic; font-weight: 700; font-display: swap; - src: url('Inter-BoldItalic.woff2?v=3.19') format('woff2'), + src: + url('Inter-BoldItalic.woff2?v=3.19') format('woff2'), url('Inter-BoldItalic.woff?v=3.19') format('woff'); } @@ -120,7 +136,8 @@ font-style: normal; font-weight: 800; font-display: swap; - src: url('Inter-ExtraBold.woff2?v=3.19') format('woff2'), + src: + url('Inter-ExtraBold.woff2?v=3.19') format('woff2'), url('Inter-ExtraBold.woff?v=3.19') format('woff'); } @font-face { @@ -128,7 +145,8 @@ font-style: italic; font-weight: 800; font-display: swap; - src: url('Inter-ExtraBoldItalic.woff2?v=3.19') format('woff2'), + src: + url('Inter-ExtraBoldItalic.woff2?v=3.19') format('woff2'), url('Inter-ExtraBoldItalic.woff?v=3.19') format('woff'); } @@ -137,7 +155,8 @@ font-style: normal; font-weight: 900; font-display: swap; - src: url('Inter-Black.woff2?v=3.19') format('woff2'), + src: + url('Inter-Black.woff2?v=3.19') format('woff2'), url('Inter-Black.woff?v=3.19') format('woff'); } @font-face { @@ -145,7 +164,8 @@ font-style: italic; font-weight: 900; font-display: swap; - src: url('Inter-BlackItalic.woff2?v=3.19') format('woff2'), + src: + url('Inter-BlackItalic.woff2?v=3.19') format('woff2'), url('Inter-BlackItalic.woff?v=3.19') format('woff'); } diff --git a/src/core/providers/UiKitProvider/index.css b/src/core/providers/UiKitProvider/index.css deleted file mode 100644 index a1f55f93f..000000000 --- a/src/core/providers/UiKitProvider/index.css +++ /dev/null @@ -1,55 +0,0 @@ -@keyframes react-loading-skeleton { - 100% { - transform: translateX(100%); - } -} - -.react-loading-skeleton { - --base-color: #ebebeb; - --highlight-color: #f5f5f5; - --animation-duration: 1.5s; - --animation-direction: normal; - --pseudo-element-display: block; /* Enable animation */ - - background-color: var(--base-color); - - width: 100%; - border-radius: 0.25rem; - display: inline-flex; - line-height: 1; - - position: relative; - user-select: none; - overflow: hidden; - z-index: 1; /* Necessary for overflow: hidden to work correctly in Safari */ -} - -.react-loading-skeleton::after { - content: ' '; - display: var(--pseudo-element-display); - position: absolute; - top: 0; - left: 0; - right: 0; - height: 100%; - background-repeat: no-repeat; - background-image: linear-gradient( - 90deg, - var(--base-color), - var(--highlight-color), - var(--base-color) - ); - transform: translateX(-100%); - - animation-name: react-loading-skeleton; - animation-direction: var(--animation-direction); - animation-duration: var(--animation-duration); - animation-timing-function: ease-in-out; - animation-iteration-count: infinite; -} - -@media (prefers-reduced-motion) { - .react-loading-skeleton { - --pseudo-element-display: none; /* Disable animation */ - } -} diff --git a/src/core/providers/UiKitProvider/index.tsx b/src/core/providers/UiKitProvider/index.tsx index 00bcb24b6..06a5f433a 100644 --- a/src/core/providers/UiKitProvider/index.tsx +++ b/src/core/providers/UiKitProvider/index.tsx @@ -2,7 +2,7 @@ import './inter.css'; import React, { useState, useEffect, useMemo, useRef } from 'react'; import { Client as ASCClient } from '@amityco/ts-sdk'; -import { ThemeProvider } from 'styled-components'; +import { ThemeProvider, createGlobalStyle } from 'styled-components'; import { NotificationsContainer } from '~/core/components/Notification'; import { ConfirmComponent } from '~/core/components/Confirm'; import { NotificationsContainer as NotificationsContainerV4 } from '~/v4/core/components/Notification'; diff --git a/src/core/providers/UiKitProvider/styles.tsx b/src/core/providers/UiKitProvider/styles.tsx index 04dd10f86..9c8cd6f54 100644 --- a/src/core/providers/UiKitProvider/styles.tsx +++ b/src/core/providers/UiKitProvider/styles.tsx @@ -1,6 +1,9 @@ import styled from 'styled-components'; +import skeletonCss from 'react-loading-skeleton/dist/skeleton.css?inline'; + export const UIStyles = styled.div` + color-scheme: only light; ${({ theme }) => theme.typography.body}; color: ${({ theme }) => theme.palette.base.main}; width: 100%; @@ -20,4 +23,12 @@ export const UIStyles = styled.div` & pre { ${({ theme }) => theme.typography.body} } + + @keyframes react-loading-skeleton { + 100% { + transform: translateX(100%); + } + } + + ${skeletonCss} `; diff --git a/src/global.d.ts b/src/global.d.ts index a52b35ee9..6bc62bb56 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -14,6 +14,11 @@ interface ImportMeta { readonly env: ImportMetaEnv; } +declare module '*.css?inline' { + const classes: string; + export default classes; +} + declare module '*.module.css' { const classes: { [key: string]: string }; export default classes; diff --git a/src/i18n/en.json b/src/i18n/en.json index 458f64e2d..3bd42e1d2 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -204,6 +204,7 @@ "files.all": "View all files", "sidebar.community": "Community", + "sidebar.explore": "Explore", "CategoryCommunitiesList.emptyTitle": "It's empty here...", "CategoryCommunitiesList.emptyDescription": "No community found in this category", diff --git a/src/social/components/Comment/StyledComment.tsx b/src/social/components/Comment/StyledComment.tsx index 6958cacd3..3a082e065 100644 --- a/src/social/components/Comment/StyledComment.tsx +++ b/src/social/components/Comment/StyledComment.tsx @@ -170,6 +170,7 @@ interface StyledCommentProps { isBanned?: boolean; mentionees?: Mentioned[]; metadata?: Metadata; + onClickUser?: () => void; } const StyledComment = (props: StyledCommentProps) => { @@ -192,6 +193,7 @@ const StyledComment = (props: StyledCommentProps) => { queryMentionees, isBanned, mentionees, + onClickUser, } = props; const { formatMessage } = useIntl(); @@ -214,7 +216,7 @@ const StyledComment = (props: StyledCommentProps) => { return ( <> - <Avatar avatar={authorAvatar} backgroundImage={UserImage} /> + <Avatar onClick={onClickUser} avatar={authorAvatar} backgroundImage={UserImage} /> <Content> <Truncate ellipsis={ @@ -231,7 +233,8 @@ const StyledComment = (props: StyledCommentProps) => { lines={2} > <CommentHeader> - <AuthorName>{authorName}</AuthorName> + <AuthorName onClick={onClickUser}>{authorName}</AuthorName> + <Truncate.Atom> <> {isBanned && ( diff --git a/src/social/components/Comment/index.tsx b/src/social/components/Comment/index.tsx index f032b7290..b0d5089af 100644 --- a/src/social/components/Comment/index.tsx +++ b/src/social/components/Comment/index.tsx @@ -38,6 +38,7 @@ import useCommentSubscription from '~/social/hooks/useCommentSubscription'; import { ERROR_RESPONSE } from '~/social/constants'; import { useConfirmContext } from '~/core/providers/ConfirmProvider'; import { useNotifications } from '~/core/providers/NotificationProvider'; +import { useNavigation } from '~/social/providers/NavigationProvider'; const REPLIES_PER_PAGE = 5; @@ -87,6 +88,7 @@ interface CommentProps { } const Comment = ({ commentId, readonly }: CommentProps) => { + const { onClickUser } = useNavigation(); const comment = useComment(commentId); const post = usePost(comment?.referenceId); const { confirm } = useConfirmContext(); @@ -101,10 +103,6 @@ const Comment = ({ commentId, readonly }: CommentProps) => { const { formatMessage } = useIntl(); const [isExpanded, setExpanded] = useState(false); - useCommentSubscription({ - commentId, - }); - const { text, markup, mentions, onChange, queryMentionees, resetState, clearAll } = useSocialMention({ targetId: post?.targetId, @@ -250,6 +248,7 @@ const Comment = ({ commentId, readonly }: CommentProps) => { isReplyComment={isReplyComment} onClickReply={onClickReply} onChange={onChange} + onClickUser={() => onClickUser(comment.userId)} /> ); diff --git a/src/social/components/Comment/styles.tsx b/src/social/components/Comment/styles.tsx index ef860fc01..fc93fc08c 100644 --- a/src/social/components/Comment/styles.tsx +++ b/src/social/components/Comment/styles.tsx @@ -91,6 +91,8 @@ export const AuthorName = styled.span` // react-truncate-markup tries to set to inline-block display: inline !important; ${({ theme }) => theme.typography.body} + cursor: pointer; + font-weight: 600; `; export const CommentDate = styled(Time)` diff --git a/src/social/components/CommentLikeButton/index.tsx b/src/social/components/CommentLikeButton/index.tsx index 9a1e67988..9acf233b8 100644 --- a/src/social/components/CommentLikeButton/index.tsx +++ b/src/social/components/CommentLikeButton/index.tsx @@ -18,16 +18,6 @@ const CommentLikeButton = ({ }: CommentLikeButtonProps) => { const comment = useComment(commentId); - useUserReactionSubscription({ - userId: comment?.targetId, - shouldSubscribe: () => comment?.targetType === 'user', - }); - - useCommunityReactionSubscription({ - communityId: comment?.targetId, - shouldSubscribe: () => comment?.targetType === 'community', - }); - return ( <UICommentLikeButton comment={comment} diff --git a/src/social/components/CommentList/index.tsx b/src/social/components/CommentList/index.tsx index 058eacc6d..c82c4d5ab 100644 --- a/src/social/components/CommentList/index.tsx +++ b/src/social/components/CommentList/index.tsx @@ -80,56 +80,6 @@ const InnerCommentList = ({ }; const PostCommentList = (props: CommentListProps) => { - const { - parentId, - referenceId, - referenceType, - limit = 5, - // TODO: breaking change - // filterByParentId = false, - readonly = false, - isExpanded = true, - } = props; - const { formatMessage } = useIntl(); - const [firstRender, setFirstRender] = useState(true); - const isReplyComment = !!parentId; - const post = usePost(referenceId); - - usePostSubscription({ - postId: referenceId, - level: SubscriptionLevels.COMMENT, - shouldSubscribe: () => referenceType === 'post' && !parentId, - }); - - const loadMoreText = isReplyComment - ? formatMessage({ id: 'collapsible.viewMoreReplies' }) - : formatMessage({ id: 'collapsible.viewMoreComments' }); - - const prependIcon = isReplyComment ? ( - <TabIconContainer> - <TabIcon /> - </TabIconContainer> - ) : null; - - if (firstRender) { - return ( - <LoadMoreWrapper - hasMore={(post?.comments.length || 0) > (post?.latestComments?.length || 0)} - loadMore={() => { - setFirstRender(false); - }} - text={loadMoreText} - className={isReplyComment ? 'reply-button' : 'comments-button'} - prependIcon={prependIcon} - appendIcon={null} - isExpanded={isExpanded} - contentSlot={(post?.latestComments || []).map((comment: Amity.Comment) => ( - <Comment key={comment.commentId} commentId={comment.commentId} readonly={readonly} /> - ))} - /> - ); - } - return <InnerCommentList {...props} callLoadMoreAgain />; }; diff --git a/src/social/components/CommunityForm/AvatarUploader.tsx b/src/social/components/CommunityForm/AvatarUploader.tsx index c5862c7cc..49648f22a 100644 --- a/src/social/components/CommunityForm/AvatarUploader.tsx +++ b/src/social/components/CommunityForm/AvatarUploader.tsx @@ -41,6 +41,13 @@ const AvatarUploadButton = styled.div` padding: 10px 16px; border-radius: 4px; color: #ffffff; + @media (max-width: 768px) { + display: flex; + flex-direction: column; + gap: 4px; + align-items: center; + text-align: center; + } `; const CoverImageLoader = styled(Loader)` diff --git a/src/social/components/CommunityForm/styles.tsx b/src/social/components/CommunityForm/styles.tsx index b1f4c9ade..568e302ff 100644 --- a/src/social/components/CommunityForm/styles.tsx +++ b/src/social/components/CommunityForm/styles.tsx @@ -44,6 +44,9 @@ export const Selector = styled.div` cursor: pointer; max-height: 200px; overflow-y: auto; + @media (max-width: 768px) { + max-height: unset; + } `; export const IconWrapper = styled.div` @@ -66,7 +69,7 @@ export const Counter = styled.div` export const Label = styled.label` ${({ theme }) => theme.typography.bodyBold}; - margin-bottom: 4px; + margin: 8px 0; ${({ theme }) => css` &.required { &::after { @@ -92,6 +95,10 @@ export const Radio = styled.input.attrs({ type: 'radio' })` export const Form = styled.form` min-width: 520px; + + @media (max-width: 768px) { + min-width: 100%; + } `; export const SubmitButton = styled(PrimaryButton).attrs<{ edit?: boolean }>({ @@ -120,9 +127,6 @@ export const FormBlockContainer = styled.div<{ edit?: boolean }>` ${({ theme, edit }) => edit ? css` - > :not(:first-child) { - margin-top: 12px; - } border: 1px solid #edeef2; border-radius: 4px; ` @@ -163,6 +167,10 @@ export const Description = styled.div` color: ${({ theme }) => theme.palette.base.shade1}; ${({ theme }) => theme.typography.body}; width: 357px; + + @media (max-width: 768px) { + width: unset; + } `; export const InformationBlock = styled.div` @@ -240,6 +248,7 @@ export const AboutTextarea = styled(TextareaAutosize).attrs({ minRows: 3, maxRow resize: none; border: 1px solid #e3e4e8; padding: 10px 12px; + background: ${({ theme }) => theme.palette.system.background}; &:focus-within { border-color: ${({ theme }) => theme.palette.primary.main}; } @@ -261,11 +270,11 @@ export const SelectIcon = styled(ChevronDown).attrs({ width: 16, height: 16 })` `; export const Field = styled.div<{ error?: ReactNode }>` - > :not(:first-child) { - margin-top: 20px; - } display: flex; flex-direction: column; + gap: 8px; + padding-top: 8px; + padding-bottom: 8px; ${({ error }) => error && diff --git a/src/social/components/CommunityMembers/index.tsx b/src/social/components/CommunityMembers/index.tsx index f8b66c81d..1abe0d463 100644 --- a/src/social/components/CommunityMembers/index.tsx +++ b/src/social/components/CommunityMembers/index.tsx @@ -199,11 +199,6 @@ interface CommunityMembersProps { const CommunityMembers = ({ communityId }: CommunityMembersProps) => { const { formatMessage } = useIntl(); - useCommunitySubscription({ - level: SubscriptionLevels.COMMUNITY, - communityId, - }); - const { hasMore, loadMore, loadMoreHasBeenCalled, isLoading, members } = useCommunityMembersCollection(communityId); diff --git a/src/social/components/EngagementBar/UIEngagementBar.tsx b/src/social/components/EngagementBar/UIEngagementBar.tsx index 7abef7342..8b155d4f4 100644 --- a/src/social/components/EngagementBar/UIEngagementBar.tsx +++ b/src/social/components/EngagementBar/UIEngagementBar.tsx @@ -38,11 +38,6 @@ const UIEngagementBar = ({ }: UIEngagementBarProps) => { const { postId, targetType, targetId, reactions = {}, commentsCount, latestComments } = post; - usePostSubscription({ - postId, - level: SubscriptionLevels.POST, - }); - const totalLikes = reactions[LIKE_REACTION_KEY] || 0; return ( diff --git a/src/social/components/EngagementBar/index.tsx b/src/social/components/EngagementBar/index.tsx index 23887c296..77cbcec7d 100644 --- a/src/social/components/EngagementBar/index.tsx +++ b/src/social/components/EngagementBar/index.tsx @@ -30,17 +30,6 @@ const EngagementBar = ({ postId, readonly = false }: EngagementBarProps) => { targetId: post?.targetId, }); - usePostSubscription({ - postId, - level: SubscriptionLevels.POST, - }); - - useReactionSubscription({ - targetType: post?.targetType, - targetId: post?.targetId, - shouldSubscribe: () => !!post, - }); - if (!post) return null; const handleAddComment = async ( diff --git a/src/social/components/Feed/index.tsx b/src/social/components/Feed/index.tsx index c77c83f76..0bf2716e8 100644 --- a/src/social/components/Feed/index.tsx +++ b/src/social/components/Feed/index.tsx @@ -239,11 +239,6 @@ const CommunityFeed = ({ feedType, }); - useCommunitySubscription({ - communityId: targetId, - level: SubscriptionLevels.COMMUNITY, - }); - function renderLoadingSkeleton() { return new Array(3).fill(3).map((_, index) => <DefaultPostRenderer key={index} loading />); } diff --git a/src/social/components/MediaGallery/index.tsx b/src/social/components/MediaGallery/index.tsx index 7d0a2c69d..b34857704 100644 --- a/src/social/components/MediaGallery/index.tsx +++ b/src/social/components/MediaGallery/index.tsx @@ -13,9 +13,10 @@ import useMediaCollection from '~/social/hooks/collections/useMediaCollection'; interface MediaGalleryProps { targetId?: string | null; targetType: string; + grid?: boolean; } -const MediaGallery = ({ targetId, targetType }: MediaGalleryProps) => { +const MediaGallery = ({ targetId, targetType, grid = false }: MediaGalleryProps) => { const [activeTab, setActiveTab] = useState<EnumContentType>(EnumContentType.Image); const { media, isLoading, hasMore, loadMore, loadMoreHasBeenCalled } = useMediaCollection({ @@ -43,6 +44,7 @@ const MediaGallery = ({ targetId, targetType }: MediaGalleryProps) => { items={media} loading={isLoading} loadingMore={loadMoreHasBeenCalled} + grid={grid} renderVideoThumbnail={(item) => ( <VideoItem.Thumbnail item={item} showPlayIcon showVideoDuration /> )} diff --git a/src/social/components/ProfileSettings/index.tsx b/src/social/components/ProfileSettings/index.tsx index be00afd2b..42b12c48f 100644 --- a/src/social/components/ProfileSettings/index.tsx +++ b/src/social/components/ProfileSettings/index.tsx @@ -20,18 +20,16 @@ import { Avatar, AvatarContainer, } from './styles'; -import useSDK from '~/core/hooks/useSDK'; -import useFile from '~/core/hooks/useFile'; import { UserRepository } from '@amityco/ts-sdk'; import { useCustomComponent } from '~/core/providers/CustomComponentsProvider'; import useImage from '~/core/hooks/useImage'; +import { useNotifications } from '~/core/providers/NotificationProvider'; interface ProfileSettingsProps { userId?: string; } const ProfileSettings = ({ userId }: ProfileSettingsProps) => { - // const { currentUserId } = useSDK(); const { formatMessage } = useIntl(); const { onClickUser } = useNavigation(); @@ -39,6 +37,13 @@ const ProfileSettings = ({ userId }: ProfileSettingsProps) => { const user = useUser(userId); const avatarFileUrl = useImage({ fileId: user?.avatarFileId, imageSize: 'small' }); + const notification = useNotifications(); + + const handleError = (error: Error) => { + notification.error({ + content: error.message, + }); + }; const handleSubmit = async ( data: Partial<Pick<Amity.User, 'displayName'>> & @@ -49,6 +54,7 @@ const ProfileSettings = ({ userId }: ProfileSettingsProps) => { await UserRepository.updateUser(userId, data); onClickUser(userId); } catch (err) { + handleError(err as Error); console.log(err); } }; diff --git a/src/social/components/ProfileSettings/styles.tsx b/src/social/components/ProfileSettings/styles.tsx index 45ca85c26..41ea8a2fa 100644 --- a/src/social/components/ProfileSettings/styles.tsx +++ b/src/social/components/ProfileSettings/styles.tsx @@ -12,6 +12,10 @@ export const Container = styled.div` display: flex; flex-direction: column; min-width: 600px; + + @media (max-width: 768px) { + min-width: 100%; + } `; export const PageHeader = styled.div` diff --git a/src/social/components/SideSectionMyCommunity/index.tsx b/src/social/components/SideSectionMyCommunity/index.tsx index fad1a9e3f..a8ebdeb54 100644 --- a/src/social/components/SideSectionMyCommunity/index.tsx +++ b/src/social/components/SideSectionMyCommunity/index.tsx @@ -1,5 +1,6 @@ import React, { memo, useState } from 'react'; import { FormattedMessage } from 'react-intl'; +import { ModalContainer } from '~/core/components/ModalContainer'; import SideMenuActionItem from '~/core/components/SideMenuActionItem'; import SideMenuSection from '~/core/components/SideMenuSection'; @@ -22,6 +23,7 @@ const SideSectionMyCommunity = ({ className, activeCommunity }: SideSectionMyCom const open = () => setIsOpen(true); const close = (communityId?: string) => { + console.log('communityId', communityId); setIsOpen(false); communityId && onCommunityCreated(communityId); }; @@ -45,7 +47,9 @@ const SideSectionMyCommunity = ({ className, activeCommunity }: SideSectionMyCom activeCommunity={activeCommunity} /> - <CommunityCreationModal isOpen={isOpen} onClose={close} /> + <ModalContainer> + <CommunityCreationModal isOpen={isOpen} onClose={close} /> + </ModalContainer> </SideMenuSection> ); }; diff --git a/src/social/components/UserInfo/styles.tsx b/src/social/components/UserInfo/styles.tsx index dd0f24f60..bccd3f1df 100644 --- a/src/social/components/UserInfo/styles.tsx +++ b/src/social/components/UserInfo/styles.tsx @@ -44,12 +44,15 @@ export const Avatar = styled(UIAvatar)` export const ActionButtonContainer = styled.div` display: flex; - gap: 8px; margin-right: 8px; > button { - min-width: 136px; - height: 40px; + min-width: 160px; + gap: 8px; + padding: 10px 16px; + @media (max-width: 768px) { + min-width: 130px; + } } `; diff --git a/src/social/components/UserProfileForm/index.tsx b/src/social/components/UserProfileForm/index.tsx index f5b8282c4..d289cc547 100644 --- a/src/social/components/UserProfileForm/index.tsx +++ b/src/social/components/UserProfileForm/index.tsx @@ -4,7 +4,6 @@ import { FormattedMessage, useIntl } from 'react-intl'; import { Controller, useForm } from 'react-hook-form'; import { PrimaryButton } from '~/core/components/Button'; - import AvatarUploader from '~/social/components/CommunityForm/AvatarUploader'; // TODO: should not be importing styles from another component. @@ -22,9 +21,6 @@ import { LabelCounterWrapper, TextField, } from '~/social/components/CommunityForm/styles'; -import useSDK from '~/core/hooks/useSDK'; -import useUser from '~/core/hooks/useUser'; -import { isAdmin as isAdminFn } from '~/helpers/permissions'; const ButtonContainer = styled.div` margin-top: 16px; @@ -47,14 +43,13 @@ interface UserProfileFormProps { onSubmit: ( data: Partial<Pick<Amity.User, 'displayName'>> & Pick<Amity.User, 'description' | 'avatarFileId'>, - ) => void; + ) => Promise<void>; className?: string; } const UserProfileForm = ({ user, onSubmit, className }: UserProfileFormProps) => { - const { currentUserId } = useSDK(); - const currentUser = useUser(currentUserId); const { formatMessage } = useIntl(); + const { register, handleSubmit, @@ -69,8 +64,6 @@ const UserProfileForm = ({ user, onSubmit, className }: UserProfileFormProps) => }, }); - const isAdmin = isAdminFn(currentUser?.roles); - const description = watch('description'); const displayName = watch('displayName'); @@ -78,13 +71,13 @@ const UserProfileForm = ({ user, onSubmit, className }: UserProfileFormProps) => <Form className={className} onSubmit={handleSubmit((data) => { - if (isAdmin) { - onSubmit(data); - } else { + if (user.displayName === data.displayName) { onSubmit({ description: data.description, avatarFileId: data.avatarFileId, }); + } else { + onSubmit(data); } })} > @@ -111,7 +104,6 @@ const UserProfileForm = ({ user, onSubmit, className }: UserProfileFormProps) => data-qa-anchor="user-profile-form-display-name-input" placeholder={formatMessage({ id: 'UserProfileForm.namePlaceholder' })} maxLength={100} - disabled={!isAdmin} /> <ErrorMessage errors={errors} name="displayName" /> </Field> diff --git a/src/social/components/category/CategoriesCard/index.tsx b/src/social/components/category/CategoriesCard/index.tsx index d68de1e66..abb713673 100644 --- a/src/social/components/category/CategoriesCard/index.tsx +++ b/src/social/components/category/CategoriesCard/index.tsx @@ -51,7 +51,6 @@ const List = () => { return ( <HorizontalList columns={{ - 360: 1, 720: 2, 1024: 3, 1280: 5, diff --git a/src/social/components/community/Card/styles.tsx b/src/social/components/community/Card/styles.tsx index c19fb2c74..a81117cf8 100644 --- a/src/social/components/community/Card/styles.tsx +++ b/src/social/components/community/Card/styles.tsx @@ -3,8 +3,6 @@ import styled, { css } from 'styled-components'; import SocialCommunityName from '~/social/components/community/Name'; export const Container = styled.div` - min-width: 278px; - min-height: 289px; cursor: pointer; box-shadow: 0 0 1px rgba(40, 41, 61, 0.08), diff --git a/src/social/components/community/Header/styles.tsx b/src/social/components/community/Header/styles.tsx index b611a4de6..462a70f25 100644 --- a/src/social/components/community/Header/styles.tsx +++ b/src/social/components/community/Header/styles.tsx @@ -26,7 +26,7 @@ export const CommunityHeaderContainer = styled.a.attrs( css` &:hover { cursor: pointer; - background-color: ${({ theme }) => theme.palette.base.shade4}; + background-color: ${({ theme }) => theme.palette.primary.shade3}; } `} diff --git a/src/social/components/community/RecommendedList/index.tsx b/src/social/components/community/RecommendedList/index.tsx index 08671bb1f..60dde5cb9 100644 --- a/src/social/components/community/RecommendedList/index.tsx +++ b/src/social/components/community/RecommendedList/index.tsx @@ -22,7 +22,13 @@ const RecommendedList = () => { if (!communities?.length) return null; return ( - <HorizontalList title={title}> + <HorizontalList + title={title} + columns={{ + 1024: 2, + 1280: 3, + }} + > {isLoading && new Array(4).fill(1).map((x, index) => <UICommunityCard key={index} loading />)} {!isLoading && diff --git a/src/social/components/post/Creator/index.tsx b/src/social/components/post/Creator/index.tsx index c86652f04..572c50789 100644 --- a/src/social/components/post/Creator/index.tsx +++ b/src/social/components/post/Creator/index.tsx @@ -286,6 +286,16 @@ const PostCreatorBar = ({ title: <FormattedMessage id="post.discard.title" />, content: <FormattedMessage id="post.discard.content" />, okText: <FormattedMessage id="general.action.discard" />, + onOk: () => { + clearAll(); + setPostImages([]); + setPostVideos([]); + setPostFiles([]); + setIncomingImages([]); + setIncomingVideos([]); + setIncomingFiles([]); + setNavigationBlocker?.(null); + }, }); } else { setNavigationBlocker?.(null); diff --git a/src/social/components/post/Editor/styles.tsx b/src/social/components/post/Editor/styles.tsx index e4db1955c..e89819814 100644 --- a/src/social/components/post/Editor/styles.tsx +++ b/src/social/components/post/Editor/styles.tsx @@ -2,7 +2,7 @@ import styled from 'styled-components'; import { PrimaryButton } from '~/core/components/Button'; export const PostEditorContainer = styled.div` - width: 520px; + width: 100%; padding: 0; border: none; display: flex; diff --git a/src/social/components/post/Editor/usePostEditor.ts b/src/social/components/post/Editor/usePostEditor.ts index 4aa4f739f..7bd75a153 100644 --- a/src/social/components/post/Editor/usePostEditor.ts +++ b/src/social/components/post/Editor/usePostEditor.ts @@ -33,15 +33,27 @@ export const usePostEditor = ({ postId, onSave }: { postId?: string; onSave: () setLocalRemovedChildren((prevRemovedChildren) => [...prevRemovedChildren, childPostId]); }; - const handleSave = async () => { - localRemovedChildren.forEach((childPostId) => { - PostRepository.deletePost(childPostId); - }); + const formattedAttachment = (post: Amity.Post) => { + if (post.dataType === 'file' || post.dataType === 'image') { + return { + type: post.dataType, + fileId: post.data.fileId, + }; + } + if (post.dataType === 'video') { + return { + type: post.dataType, + fileId: post.data.videoFileId.original, + }; + } + }; + const handleSave = async () => { await PostRepository.updatePost(post.postId, { data: { text }, mentionees, metadata, + attachments: childrenPosts.map(formattedAttachment), }); clearAll(); onSave(); diff --git a/src/social/components/post/GalleryContent/index.tsx b/src/social/components/post/GalleryContent/index.tsx index 7fac4f6db..903a03b65 100644 --- a/src/social/components/post/GalleryContent/index.tsx +++ b/src/social/components/post/GalleryContent/index.tsx @@ -54,6 +54,7 @@ export interface GalleryContentProps<T extends PostWithSkeleton> { renderVideoThumbnail?: (item: Amity.Post<'video'>) => ReactNode; renderImageThumbnail?: (item: Amity.Post<'image'>) => ReactNode; renderLiveStreamThumbnail?: (item: Amity.Post<'liveStream'>) => ReactNode; + grid?: boolean; } const GalleryContent = <T extends PostWithSkeleton>({ @@ -63,6 +64,7 @@ const GalleryContent = <T extends PostWithSkeleton>({ loadingMore = false, showCounter = false, truncate = false, + grid = false, renderVideoItem, renderImageItem, renderLiveStreamItem, @@ -112,6 +114,7 @@ const GalleryContent = <T extends PostWithSkeleton>({ className={className} items={items} truncate={truncate} + grid={grid} onClick={(i) => { if (!isLoadingItem(items[i])) { setIndex(i); diff --git a/src/social/components/post/PollComposer/styles.tsx b/src/social/components/post/PollComposer/styles.tsx index 78ab088df..cf7098465 100644 --- a/src/social/components/post/PollComposer/styles.tsx +++ b/src/social/components/post/PollComposer/styles.tsx @@ -46,6 +46,7 @@ export const OptionInput = styled(TextInput)` background: ${({ theme }) => theme.palette.base.shade4}; width: 100%; padding-right: 60px; + color: ${({ theme }) => theme.palette.neutral.main}; `; export const CloseIcon = styled(CircleRemove)``; diff --git a/src/social/components/post/Post/DefaultPostRenderer.tsx b/src/social/components/post/Post/DefaultPostRenderer.tsx index ce553f85a..3b29c0093 100644 --- a/src/social/components/post/Post/DefaultPostRenderer.tsx +++ b/src/social/components/post/Post/DefaultPostRenderer.tsx @@ -216,11 +216,6 @@ const DefaultPostRenderer = (props: DefaultPostRendererProps) => { const community = useCommunity(communityId); const { currentUserId } = useSDK(); - usePostSubscription({ - postId: post?.postId, - level: SubscriptionLevels.POST, - }); - const { canReview, isPostUnderReview } = useCommunityPostPermission({ community, post, diff --git a/src/social/components/post/Post/index.tsx b/src/social/components/post/Post/index.tsx index 9b474e2a3..89fdba1d3 100644 --- a/src/social/components/post/Post/index.tsx +++ b/src/social/components/post/Post/index.tsx @@ -1,4 +1,4 @@ -import React, { memo } from 'react'; +import React, { memo, useEffect } from 'react'; import usePost from '~/social/hooks/usePost'; import usePoll from '~/social/hooks/usePoll'; @@ -30,17 +30,6 @@ const Post = ({ postId, className, hidePostTarget, readonly, onDeleted }: PostPr const postRenderFn = usePostRenderer(post?.dataType); const { currentUserId } = useSDK(); - usePostSubscription({ - postId, - level: SubscriptionLevels.POST, - }); - - useReactionSubscription({ - targetType: post?.targetType, - targetId: post?.targetId, - shouldSubscribe: () => !!post, - }); - const pollPost = (childrenPosts || []).find((childPost) => childPost.dataType === 'poll'); const poll = usePoll((pollPost?.data as Amity.ContentDataPoll)?.pollId); diff --git a/src/social/constants.ts b/src/social/constants.ts index 037a8097f..d863e575c 100644 --- a/src/social/constants.ts +++ b/src/social/constants.ts @@ -47,10 +47,10 @@ export const Permissions = Object.freeze({ DeleteUserFeedPostPermission: 'DELETE_USER_FEED_POST', EditUserFeedCommentPermission: 'EDIT_USER_FEED_COMMENT', DeleteUserFeedCommentPermission: 'DELETE_USER_FEED_COMMENT', - EditCommunityFeedPostPermission: 'EDIT_COMMUNITY_FEED_POST', - DeleteCommunityFeedPostPermission: 'DELETE_COMMUNITY_FEED_POST', - EditCommunityFeedCommentPermission: 'EDIT_COMMUNITY_FEED_COMMENT', - DeleteCommunityFeedCommentPermission: 'DELETE_COMMUNITY_FEED_COMMENT', + EditCommunityFeedPostPermission: 'EDIT_COMMUNITY_POST', + DeleteCommunityFeedPostPermission: 'DELETE_COMMUNITY_POST', + EditCommunityFeedCommentPermission: 'EDIT_COMMUNITY_COMMENT', + DeleteCommunityFeedCommentPermission: 'DELETE_COMMUNITY_COMMENT', CreateCommunityCategoryPermission: 'CREATE_COMMUNITY_CATEGORY', EditCommunityCategoryPermission: 'EDIT_COMMUNITY_CATEGORY', DeleteCommunityCategoryPermission: 'DELETE_COMMUNITY_CATEGORY', diff --git a/src/social/hooks/useCommunityPostPermission.ts b/src/social/hooks/useCommunityPostPermission.ts index eb3ddd027..ce3ab2bf8 100644 --- a/src/social/hooks/useCommunityPostPermission.ts +++ b/src/social/hooks/useCommunityPostPermission.ts @@ -1,9 +1,9 @@ import { CommunityPostSettings } from '@amityco/ts-sdk'; import { useMemo } from 'react'; -import useUser from '~/core/hooks/useUser'; import usePostsCollection from '~/social/hooks/collections/usePostsCollection'; -import useCommunityModeratorsCollection from './collections/useCommunityModeratorsCollection'; -import useCommunityMembersCollection from './collections/useCommunityMembersCollection'; +import useSDK from '~/core/hooks/useSDK'; +import { Permissions } from '~/social/constants'; +import useCommunityModeratorsCollection from '~/social/hooks/collections/useCommunityModeratorsCollection'; const useCommunityPostPermission = ({ post, @@ -17,13 +17,13 @@ const useCommunityPostPermission = ({ userId?: string; }) => { const { moderators } = useCommunityModeratorsCollection(community?.communityId); - const { members } = useCommunityMembersCollection(community?.communityId); + const { client } = useSDK(); + const { posts: reviewingPosts } = usePostsCollection({ targetType: 'community', targetId: community?.communityId, feedType: 'reviewing', }); - const user = useUser(userId); const isEditable = useMemo(() => { if ( @@ -36,7 +36,6 @@ const useCommunityPostPermission = ({ return true; }, [childrenPosts]); - const member = members.find((member) => member.userId === userId); const moderator = moderators.find((moderator) => moderator.userId === userId); const isMyPost = post?.postedUserId === userId; const isPostUnderReview = useMemo(() => { @@ -45,29 +44,57 @@ const useCommunityPostPermission = ({ } return false; }, [community, reviewingPosts]); - const isGlobalAdmin = user?.roles.find((role) => role === 'global-admin') != null; const isModerator = moderator != null; - const isMember = member != null; - if (community == null) { - return { - isPostUnderReview: false, - isModerator: false, - canEdit: (isGlobalAdmin || isMyPost) && isEditable, - canReport: isGlobalAdmin || !isMyPost, - canDelete: isGlobalAdmin || isMyPost, - canReview: false, - }; + const permissions: { + canEdit: boolean; + canReport: boolean; + canDelete: boolean; + canReview: boolean; + } = { + canEdit: false, + canReport: false, + canDelete: false, + canReview: false, + }; + + if (isMyPost) { + if (!isPostUnderReview && isEditable) { + permissions.canEdit = true; + } + permissions.canDelete = true; + } else { + if (community != null) { + const canEdit = + client + ?.hasPermission(Permissions.EditCommunityFeedPostPermission) + .community(community.communityId) ?? false; + + permissions.canEdit = canEdit && isEditable; + + const canDelete = + client + ?.hasPermission(Permissions.DeleteCommunityFeedPostPermission) + .community(community.communityId) ?? false; + + permissions.canDelete = canDelete; + } else { + const canDelete = + client?.hasPermission(Permissions.EditUserFeedPostPermission).currentUser() ?? false; + + permissions.canDelete = canDelete; + } + + if (!isPostUnderReview) { + permissions.canReport = true; + } } return { isPostUnderReview, isModerator, - canEdit: (isGlobalAdmin || isModerator) && isEditable, - canReview: isGlobalAdmin || isModerator, - canDelete: (!isPostUnderReview && isModerator) || (isMyPost && isMember), - canReport: !isPostUnderReview ? !isMyPost && (isModerator || isMember) : !isMyPost, + ...permissions, }; }; diff --git a/src/social/hooks/usePostByIds.ts b/src/social/hooks/usePostByIds.ts index 720a92f99..f38b970e1 100644 --- a/src/social/hooks/usePostByIds.ts +++ b/src/social/hooks/usePostByIds.ts @@ -6,9 +6,12 @@ const usePostByIds = (postIds: Parameters<typeof PostRepository.getPostByIds>[0] useEffect(() => { async function run() { - if (!postIds || postIds?.length === 0) return; - const response = await PostRepository.getPostByIds(postIds); - setPosts(response.data); + if (!postIds || postIds?.length === 0) { + setPosts([]); + } else { + const response = await PostRepository.getPostByIds(postIds); + setPosts(response.data); + } } run(); }, [postIds]); diff --git a/src/social/layouts/Main/index.tsx b/src/social/layouts/Main/index.tsx index 4af3bbefd..835f2805c 100644 --- a/src/social/layouts/Main/index.tsx +++ b/src/social/layouts/Main/index.tsx @@ -22,6 +22,11 @@ const Main = styled.div` min-width: 20rem; max-width: 90.75rem; margin: 0 auto; + + @media (max-width: 768px) { + max-width: unset; + min-width: unset; + } `; const Side = styled.div` diff --git a/src/social/layouts/Page/index.tsx b/src/social/layouts/Page/index.tsx index a61a4f8fe..6c2d4630a 100644 --- a/src/social/layouts/Page/index.tsx +++ b/src/social/layouts/Page/index.tsx @@ -3,7 +3,6 @@ import styled from 'styled-components'; const Container = styled.div<{ withHeader?: boolean }>` display: grid; - grid-template-areas: 'main side'; grid-template-columns: auto min-content; @@ -21,12 +20,27 @@ const Container = styled.div<{ withHeader?: boolean }>` overflow: hidden; margin: 0 auto; padding: 20px 0; + + @media (max-width: 768px) { + grid-template-areas: + ${({ withHeader }) => (withHeader ? `'header'` : ``)} + 'main' + 'side'; + grid-template-columns: 1fr; + grid-template-rows: auto auto; + height: auto; + overflow: unset; + } `; const HeaderContainer = styled.div` grid-area: header; width: 100%; height: 100%; + + @media (max-width: 768px) { + height: unset; + } `; const Main = styled.div` @@ -34,6 +48,10 @@ const Main = styled.div` width: 100%; height: 100%; overflow: auto; + + @media (max-width: 768px) { + height: unset; + } `; const Side = styled.div` @@ -43,6 +61,11 @@ const Side = styled.div` max-width: 20rem; overflow: auto; + @media (max-width: 768px) { + max-width: unset; + height: unset; + } + & > :not(:first-child) { margin-top: 20px; } diff --git a/src/social/pages/Application/index.tsx b/src/social/pages/Application/index.tsx index 3eb8f88b9..c6fca118e 100644 --- a/src/social/pages/Application/index.tsx +++ b/src/social/pages/Application/index.tsx @@ -70,11 +70,17 @@ const Community = () => { run(); }, [client]); + useEffect(() => { + if (open) { + setOpen(false); + } + }, [page.type, page.communityId]); + return ( <StoryProvider> <ApplicationContainer> <MainLayout aside={<StyledCommunitySideMenu activeCommunity={page.communityId} />}> - {page.type === PageTypes.Explore && <ExplorePage />} + {page.type === PageTypes.Explore && <ExplorePage isOpen={open} toggleOpen={toggleOpen} />} {page.type === PageTypes.NewsFeed && ( <NewsFeedPage toggleOpen={toggleOpen} isOpen={open} /> diff --git a/src/social/pages/CommunityEdit/styles.tsx b/src/social/pages/CommunityEdit/styles.tsx index b7073103c..ade9087ee 100644 --- a/src/social/pages/CommunityEdit/styles.tsx +++ b/src/social/pages/CommunityEdit/styles.tsx @@ -11,6 +11,9 @@ export const ExtraActionContainer = styled.div` padding: 16px; width: 330px; flex-shrink: 0; + @media (max-width: 768px) { + width: 100%; + } `; export const ExtraActionContainerHeader = styled.div` diff --git a/src/social/pages/CommunityFeed/index.tsx b/src/social/pages/CommunityFeed/index.tsx index f2eb42bbe..690f9df8f 100644 --- a/src/social/pages/CommunityFeed/index.tsx +++ b/src/social/pages/CommunityFeed/index.tsx @@ -23,11 +23,10 @@ import { CommunitySideMenuOverlay, HeadTitle, MobileContainer, + StyledBarsIcon, StyledCommunitySideMenu, } from '../NewsFeed/styles'; -import { BarsIcon } from '~/icons'; - import { useNavigation } from '~/social/providers/NavigationProvider'; import { useGetActiveStoriesByTarget } from '~/v4/social/hooks/useGetActiveStories'; @@ -68,11 +67,6 @@ const CommunityFeed = ({ communityId, isNewCommunity, isOpen, toggleOpen }: Comm const [activeTab, setActiveTab] = useState(CommunityFeedTabs.TIMELINE); - useCommunitySubscription({ - communityId, - level: SubscriptionLevels.POST, - }); - const isJoined = community?.isJoined || false; const [isCreatedModalOpened, setCreatedModalOpened] = useState(isNewCommunity); @@ -88,7 +82,7 @@ const CommunityFeed = ({ communityId, isNewCommunity, isOpen, toggleOpen }: Comm <CommunitySideMenuOverlay isOpen={isOpen} onClick={toggleOpen} /> <StyledCommunitySideMenu isOpen={isOpen} /> <MobileContainer> - <BarsIcon onClick={toggleOpen} /> + <StyledBarsIcon onClick={toggleOpen} /> <HeadTitle> <FormattedMessage id="sidebar.community" /> </HeadTitle> @@ -112,7 +106,7 @@ const CommunityFeed = ({ communityId, isNewCommunity, isOpen, toggleOpen }: Comm )} {activeTab === CommunityFeedTabs.GALLERY && ( - <MediaGallery targetType={'community'} targetId={communityId} /> + <MediaGallery targetType={'community'} targetId={communityId} grid /> )} {activeTab === CommunityFeedTabs.MEMBERS && <CommunityMembers communityId={communityId} />} diff --git a/src/social/pages/CommunityFeed/styles.tsx b/src/social/pages/CommunityFeed/styles.tsx index e80cda536..62ccbe583 100644 --- a/src/social/pages/CommunityFeed/styles.tsx +++ b/src/social/pages/CommunityFeed/styles.tsx @@ -2,7 +2,7 @@ import styled from 'styled-components'; export const Wrapper = styled.div` height: 100%; - max-width: 550px; + max-width: 700px; margin: 0 auto; padding: 28px 0; overflow-y: auto; diff --git a/src/social/pages/Explore/index.tsx b/src/social/pages/Explore/index.tsx index 486dad725..424a11c0a 100644 --- a/src/social/pages/Explore/index.tsx +++ b/src/social/pages/Explore/index.tsx @@ -5,13 +5,55 @@ import TrendingList from '~/social/components/community/TrendingList'; import CategoriesCard from '~/social/components/category/CategoriesCard'; import { PageContainer } from './styles'; +import { + CommunitySideMenuOverlay, + HeadTitle, + MobileContainer, + StyledCommunitySideMenu, + StyledBarsIcon, +} from '../NewsFeed/styles'; +import { useIntl } from 'react-intl'; +import { styled } from 'styled-components'; -const ExplorePage = () => ( - <PageContainer> - <RecommendedList /> - <TrendingList /> - <CategoriesCard /> - </PageContainer> -); +const StyledMobileContainer = styled(MobileContainer)` + background-color: #f7f7f8; +`; + +export const Wrapper = styled.div` + height: 100%; + margin: 0 auto; + padding: 28px 0; + overflow-y: auto; +`; + +interface ExplorePageProps { + isOpen: boolean; + toggleOpen: () => void; + hideSideMenu?: boolean; +} + +const ExplorePage = ({ isOpen, toggleOpen, hideSideMenu }: ExplorePageProps) => { + const { formatMessage } = useIntl(); + + return ( + <Wrapper> + {hideSideMenu !== true && ( + <> + <CommunitySideMenuOverlay isOpen={isOpen} onClick={toggleOpen} /> + <StyledCommunitySideMenu isOpen={isOpen} /> + <StyledMobileContainer> + <StyledBarsIcon onClick={toggleOpen} /> + <HeadTitle>{formatMessage({ id: 'sidebar.explore' })}</HeadTitle> + </StyledMobileContainer> + </> + )} + <PageContainer> + <RecommendedList /> + <TrendingList /> + <CategoriesCard /> + </PageContainer> + </Wrapper> + ); +}; export default ExplorePage; diff --git a/src/social/pages/NewsFeed/index.tsx b/src/social/pages/NewsFeed/index.tsx index 7caa3bd99..937a452f2 100644 --- a/src/social/pages/NewsFeed/index.tsx +++ b/src/social/pages/NewsFeed/index.tsx @@ -9,10 +9,10 @@ import { CommunitySideMenuOverlay, HeadTitle, MobileContainer, + StyledBarsIcon, StyledCommunitySideMenu, Wrapper, } from './styles'; -import { BarsIcon } from '~/icons'; import { useIntl } from 'react-intl'; import { StoryTab } from '~/social/components/StoryTab'; @@ -30,7 +30,7 @@ const NewsFeed = ({ isOpen, toggleOpen }: NewsFeedProps) => { <CommunitySideMenuOverlay isOpen={isOpen} onClick={toggleOpen} /> <StyledCommunitySideMenu isOpen={isOpen} /> <MobileContainer> - <BarsIcon onClick={toggleOpen} /> + <StyledBarsIcon onClick={toggleOpen} /> <HeadTitle>{formatMessage({ id: 'sidebar.community' })}</HeadTitle> </MobileContainer> <StoryTab type="globalFeed" /> diff --git a/src/social/pages/NewsFeed/styles.tsx b/src/social/pages/NewsFeed/styles.tsx index ee9f08309..df00274b3 100644 --- a/src/social/pages/NewsFeed/styles.tsx +++ b/src/social/pages/NewsFeed/styles.tsx @@ -1,9 +1,10 @@ import styled from 'styled-components'; import CommunitySideMenu from '~/social/components/CommunitySideMenu'; +import { BarsIcon } from '~/icons/index'; export const Wrapper = styled.div` height: 100%; - max-width: 550px; + max-width: 700px; margin: 0 auto; padding: 28px 0; overflow-y: auto; @@ -57,3 +58,7 @@ export const StyledCommunitySideMenu = styled(CommunitySideMenu)<{ isOpen: boole transform: translateX(${({ isOpen }) => (isOpen ? 0 : '-100%')}); transition: transform 0.3s ease-in-out; `; + +export const StyledBarsIcon = styled(BarsIcon)` + cursor: pointer; +`; diff --git a/src/social/pages/UserFeed/styles.tsx b/src/social/pages/UserFeed/styles.tsx index 6bab3f2f8..2ff85fd44 100644 --- a/src/social/pages/UserFeed/styles.tsx +++ b/src/social/pages/UserFeed/styles.tsx @@ -2,7 +2,7 @@ import styled from 'styled-components'; export const Wrapper = styled.div` height: 100%; - max-width: 550px; + max-width: 700px; margin: 0 auto; padding: 28px 0; overflow-y: auto; diff --git a/src/social/providers/NavigationProvider.tsx b/src/social/providers/NavigationProvider.tsx index 056501c3d..31ffb6e89 100644 --- a/src/social/providers/NavigationProvider.tsx +++ b/src/social/providers/NavigationProvider.tsx @@ -75,6 +75,7 @@ type ContextValue = { title: ReactNode; content: ReactNode; okText: ReactNode; + onOk?: () => void; } | null | undefined, @@ -182,6 +183,7 @@ export default function NavigationProvider({ title: ReactNode; content: ReactNode; okText: ReactNode; + onOk?: () => void; } | null | undefined @@ -207,7 +209,7 @@ export default function NavigationProvider({ } return true; - }, [askForConfirmation, navigationBlocker]); + }, [askForConfirmation, navigationBlocker, setNavigationBlocker]); const pushPage = useCallback( async (newPage) => { diff --git a/src/social/providers/PostRendererProvider.tsx b/src/social/providers/PostRendererProvider.tsx index 9c5b8b979..623d84781 100644 --- a/src/social/providers/PostRendererProvider.tsx +++ b/src/social/providers/PostRendererProvider.tsx @@ -37,7 +37,6 @@ const defaultPostRenderer: PostRendererConfigType = { [PostContentType.POLL]: (props: PostRendererProps) => <DefaultPostRenderer {...props} />, [PostContentType.TEXT]: (props: PostRendererProps) => <DefaultPostRenderer {...props} />, [PostContentType.VIDEO]: (props: PostRendererProps) => <DefaultPostRenderer {...props} />, - [PostContentType.CUSTOM]: (props: PostRendererProps) => <DefaultPostRenderer {...props} />, }; const PostRendererContext = createContext(defaultPostRenderer); diff --git a/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx b/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx index aa1419160..9fb77328f 100644 --- a/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx +++ b/src/v4/social/pages/SocialHomePage/SocialHomePage.tsx @@ -93,7 +93,9 @@ export function SocialHomePage() { </div> <div className={styles.socialHomePage__contents} ref={containerRef} onScroll={handleScroll}> {activeTab === HomePageTab.Newsfeed && <Newsfeed pageId={pageId} />} - {activeTab === HomePageTab.Explore && <ExplorePage />} + {activeTab === HomePageTab.Explore && ( + <ExplorePage isOpen={false} toggleOpen={() => {}} hideSideMenu={true} /> + )} {activeTab === HomePageTab.MyCommunities && <MyCommunities pageId={pageId} />} </div> {isShowCreatePostMenu && ( From a7d03dd738a8c02e7f4d4d9482dccc388cd00255 Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Fri, 23 Aug 2024 21:49:10 +0700 Subject: [PATCH 288/300] feat: ASC-23849 - announcement post (#610) * fix: merge from develop * fix: remove unused * fix: community profile component * fix: community tab control state * fix: community profile style * fix: verify badge * fix: community description max lines * fix: community categories * fix: comunity cover to use bem convention * fix: community cover add max-height * fix: export page and components * fix: update navigation and page behavior * fix: add community info * feat: community feed post skeleton * fix: remove unused * fix: community tab active color state * fix: pin * fix: css * Revert "fix: css" This reverts commit d10c48f94a79d0ab4dcd876a35553457ad6abbc3. * fix: css * style: z index * fix: cover icon color * feat: empty pinpost * feat: lock private content * fix: redirect edit post * style: pass fill default icon as props * style: remove props * fix: skeleton feed * refactor: icons and conditions * fix: locate files * fix: duplicated code * feat: featured tag * feat: announcement icon * feat: integrate annouce post with mock data * feat: add SDK getPinnedPosts * feat: integrate announcement post * chore: install stg SDK * feat: show tag category in post detail * refactor: remove mock data * refactor: PR comment * refactor: remove class not used * fix: duplicate css * fix: error unknow type --------- Co-authored-by: Chaiwat Trisuwan <chaiwattsw@gmail.com> --- pnpm-lock.yaml | 1 - src/v4/core/providers/NavigationProvider.tsx | 13 +- .../core/providers/PageBehaviorProvider.tsx | 15 +- src/v4/icons/Featured/Featured.module.css | 7 + src/v4/icons/Featured/Featured.tsx | 31 ++++ .../CommunityFeed/CommunityFeed.module.css | 7 +- .../CommunityFeed/CommunityFeed.tsx | 134 +++++++++++++----- .../CommunityHeader.module.css | 9 +- .../CommunityHeader/CommunityHeader.tsx | 1 + .../components/PostContent/PostContent.tsx | 6 +- .../AnnouncementBadge.module.css | 5 + .../AnnouncementBadge/AnnouncementBadge.tsx | 31 ++++ .../elements/AnnouncementBadge/index.ts | 1 + .../CommunityFeedTabButton.tsx | 27 ++++ .../elements/CommunityFeedTabButton/index.ts | 0 .../elements/CommunityName/CommunityName.tsx | 2 +- .../CommunityProfileTab.module.css | 3 +- .../collections/usePinnedPostCollection.ts | 25 ++++ src/v4/social/pages/Application/index.tsx | 6 +- .../pages/PostDetailPage/PostDetailPage.tsx | 5 +- 20 files changed, 273 insertions(+), 56 deletions(-) create mode 100644 src/v4/icons/Featured/Featured.module.css create mode 100644 src/v4/icons/Featured/Featured.tsx create mode 100644 src/v4/social/elements/AnnouncementBadge/AnnouncementBadge.module.css create mode 100644 src/v4/social/elements/AnnouncementBadge/AnnouncementBadge.tsx create mode 100644 src/v4/social/elements/AnnouncementBadge/index.ts create mode 100644 src/v4/social/elements/CommunityFeedTabButton/CommunityFeedTabButton.tsx create mode 100644 src/v4/social/elements/CommunityFeedTabButton/index.ts create mode 100644 src/v4/social/hooks/collections/usePinnedPostCollection.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 956aae6e0..d8c5b121e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6242,7 +6242,6 @@ packages: engines: {node: '>=0.6.0', teleport: '>=0.2.0'} deprecated: |- You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. - (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) qs@6.11.0: diff --git a/src/v4/core/providers/NavigationProvider.tsx b/src/v4/core/providers/NavigationProvider.tsx index 188313b25..f9748acf5 100644 --- a/src/v4/core/providers/NavigationProvider.tsx +++ b/src/v4/core/providers/NavigationProvider.tsx @@ -2,6 +2,7 @@ import React, { createContext, useCallback, useContext, useState, useMemo, React import { AmityStoryMediaType } from '~/v4/social/pages/DraftsPage/DraftsPage'; import { Mode } from '~/v4/social/pages/PostComposerPage/PostComposerPage'; import { NavigationContext as NavigationContextV3 } from '~/social/providers/NavigationProvider'; +import { AmityPostCategory } from '~/v4/social/components/PostContent/PostContent'; export enum PageTypes { Explore = 'explore', @@ -71,6 +72,7 @@ type Page = postId: string; communityId?: string; hideTarget?: boolean; + category?: AmityPostCategory; }; } | { type: PageTypes.CommunityProfilePage; context: { communityId: string } } @@ -115,7 +117,7 @@ type ContextValue = { onMessageUser: (userId: string) => void; onBack: () => void; goToUserProfilePage: (userId: string) => void; - goToPostDetailPage: (postId: string, hideTarget?: boolean) => void; + goToPostDetailPage: (postId: string, hideTarget?: boolean, category?: AmityPostCategory) => void; goToCommunityProfilePage: (communityId: string) => void; goToSocialGlobalSearchPage: (tab?: string) => void; goToMyCommunitiesSearchPage: () => void; @@ -178,7 +180,7 @@ let defaultValue: ContextValue = { onEditUser: (userId: string) => {}, onMessageUser: (userId: string) => {}, goToUserProfilePage: (userId: string) => {}, - goToPostDetailPage: (postId: string, hideTarget?: boolean) => {}, + goToPostDetailPage: (postId: string, hideTarget?: boolean, category?: AmityPostCategory) => {}, goToViewStoryPage: (context: { targetId: string; targetType: Amity.StoryTargetType; @@ -228,8 +230,8 @@ if (process.env.NODE_ENV !== 'production') { onBack: () => console.log('NavigationContext onBack()'), goToUserProfilePage: (userId) => console.log(`NavigationContext goToUserProfilePage(${userId})`), - goToPostDetailPage: (postId, hideTarget) => - console.log(`NavigationContext goToPostDetailPage(${postId} ${hideTarget})`), + goToPostDetailPage: (postId, hideTarget, category) => + console.log(`NavigationContext goToPostDetailPage(${postId} ${hideTarget} ${category})`), goToCommunityProfilePage: (communityId) => console.log(`NavigationContext goToCommunityProfilePage(${communityId})`), goToSocialGlobalSearchPage: (tab) => @@ -510,12 +512,13 @@ export default function NavigationProvider({ ); const goToPostDetailPage = useCallback( - (postId, hideTarget) => { + (postId, hideTarget, category) => { const next = { type: PageTypes.PostDetailPage, context: { postId, hideTarget, + category, }, }; diff --git a/src/v4/core/providers/PageBehaviorProvider.tsx b/src/v4/core/providers/PageBehaviorProvider.tsx index 01d293b91..49190d0ef 100644 --- a/src/v4/core/providers/PageBehaviorProvider.tsx +++ b/src/v4/core/providers/PageBehaviorProvider.tsx @@ -1,5 +1,6 @@ import React, { useContext } from 'react'; import { PageTypes, useNavigation } from '~/v4/core/providers/NavigationProvider'; +import { AmityPostCategory } from '~/v4/social/components/PostContent/PostContent'; import { Mode } from '~/v4/social/pages/PostComposerPage/PostComposerPage'; export interface PageBehavior { @@ -62,7 +63,11 @@ export interface PageBehavior { community?: Amity.Community; post?: Amity.Post; }): void; - goToPostDetailPage?(context: { postId: string; hideTarget?: boolean }): void; + goToPostDetailPage?(context: { + postId: string; + hideTarget?: boolean; + category?: AmityPostCategory; + }): void; goToCreateStoryPage?(context: { communityId: string }): void; // goToCommunitySettingPage?(context: { communityId: string }): void; // goToPendingPostPage?(context: { communityId: string }): void; @@ -239,11 +244,15 @@ export const PageBehaviorProvider: React.FC<PageBehaviorProviderProps> = ({ } goToPostComposerPage(context); }, - goToPostDetailPage(context: { postId: string; hideTarget?: boolean }) { + goToPostDetailPage(context: { + postId: string; + hideTarget?: boolean; + category?: AmityPostCategory; + }) { if (pageBehavior?.AmityCommunityProfilePageBehavior?.goToPostDetailPage) { return pageBehavior.AmityCommunityProfilePageBehavior.goToPostDetailPage(context); } - goToPostDetailPage(context.postId, context.hideTarget); + goToPostDetailPage(context.postId, context.hideTarget, context.category); }, // goToPendingPostPage(context) { // if (pageBehavior?.AmityCommunityProfilePageBehavior?.goToPendingPostPage) { diff --git a/src/v4/icons/Featured/Featured.module.css b/src/v4/icons/Featured/Featured.module.css new file mode 100644 index 000000000..45dbf02a8 --- /dev/null +++ b/src/v4/icons/Featured/Featured.module.css @@ -0,0 +1,7 @@ +.featuredIcon__background { + fill: var(--asc-color-base-shade4); +} + +.featuredIcon__textColor { + fill: var(--asc-color-base-default); +} diff --git a/src/v4/icons/Featured/Featured.tsx b/src/v4/icons/Featured/Featured.tsx new file mode 100644 index 000000000..4ac38e9b4 --- /dev/null +++ b/src/v4/icons/Featured/Featured.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import styles from './Featured.module.css'; + +interface FeaturedIconProps extends React.SVGProps<SVGSVGElement> { + backgroundColor?: string; + textColor?: string; +} + +export const FeaturedIcon = ({ backgroundColor, textColor, ...props }: FeaturedIconProps) => { + return ( + <svg + width="73" + height="26" + viewBox="0 0 73 26" + fill="none" + xmlns="http://www.w3.org/2000/svg" + {...props} + > + <path + d="M0 0H69C71.2091 0 73 1.79086 73 4V22C73 24.2091 71.2091 26 69 26H0V0Z" + className={styles.featuredIcon__background} + fill={backgroundColor} + /> + <path + d="M9.12988 18V8.84033H14.9697V10.2178H10.7676V12.9282H14.6079V14.2612H10.7676V18H9.12988ZM19.5108 18.1396C17.4478 18.1396 16.1973 16.7559 16.1973 14.5469V14.5405C16.1973 12.3569 17.4605 10.9287 19.4346 10.9287C21.4088 10.9287 22.6275 12.3125 22.6275 14.4072V14.9277H17.7779C17.797 16.1719 18.4698 16.8955 19.5426 16.8955C20.3995 16.8955 20.8883 16.4639 21.0406 16.1465L21.0596 16.1021H22.564L22.545 16.1592C22.3228 17.0542 21.3961 18.1396 19.5108 18.1396ZM19.4537 12.1665C18.5714 12.1665 17.9112 12.7632 17.7906 13.8677H21.085C20.9771 12.7314 20.336 12.1665 19.4537 12.1665ZM26.0197 18.1143C24.6994 18.1143 23.7409 17.3018 23.7409 16.0386V16.0259C23.7409 14.7881 24.6867 14.0645 26.3752 13.9629L28.1588 13.855V13.2583C28.1588 12.5664 27.7082 12.1855 26.8576 12.1855C26.1339 12.1855 25.6642 12.4458 25.5055 12.9028L25.4992 12.9282H24.0075L24.0138 12.8711C24.1662 11.7031 25.2834 10.9287 26.9338 10.9287C28.7174 10.9287 29.7204 11.792 29.7204 13.2583V18H28.1588V17.0479H28.0509C27.6701 17.7207 26.9338 18.1143 26.0197 18.1143ZM25.3024 15.9624C25.3024 16.5527 25.8039 16.9019 26.5021 16.9019C27.4543 16.9019 28.1588 16.2798 28.1588 15.4546V14.896L26.5973 14.9976C25.715 15.0547 25.3024 15.3784 25.3024 15.9497V15.9624ZM34.2678 18.0317C32.6746 18.0317 32.0207 17.4985 32.0207 16.1592V12.2871H30.9416V11.062H32.0207V9.36084H33.6267V11.062H35.093V12.2871H33.6267V15.7847C33.6267 16.4766 33.8996 16.7686 34.5598 16.7686C34.782 16.7686 34.9026 16.7622 35.093 16.7432V17.9619C34.8645 18.0063 34.5725 18.0317 34.2678 18.0317ZM38.8851 18.1396C37.3236 18.1396 36.5238 17.2002 36.5238 15.5498V11.062H38.1043V15.188C38.1043 16.2607 38.5042 16.8066 39.4564 16.8066C40.4783 16.8066 41.0242 16.1465 41.0242 15.1055V11.062H42.6048V18H41.0242V16.9463H40.9163C40.5926 17.689 39.888 18.1396 38.8851 18.1396ZM44.4545 18V11.062H46.0351V12.1284H46.143C46.3778 11.3857 47.0126 10.9478 47.9267 10.9478C48.1679 10.9478 48.4154 10.9795 48.5678 11.0239V12.4395C48.3139 12.3887 48.0536 12.3506 47.7807 12.3506C46.727 12.3506 46.0351 12.979 46.0351 13.9692V18H44.4545ZM52.5757 18.1396C50.5127 18.1396 49.2622 16.7559 49.2622 14.5469V14.5405C49.2622 12.3569 50.5254 10.9287 52.4995 10.9287C54.4736 10.9287 55.6924 12.3125 55.6924 14.4072V14.9277H50.8428C50.8618 16.1719 51.5346 16.8955 52.6074 16.8955C53.4643 16.8955 53.9531 16.4639 54.1054 16.1465L54.1245 16.1021H55.6289L55.6098 16.1592C55.3877 17.0542 54.4609 18.1396 52.5757 18.1396ZM52.5185 12.1665C51.6362 12.1665 50.9761 12.7632 50.8554 13.8677H54.1499C54.042 12.7314 53.4009 12.1665 52.5185 12.1665ZM59.6939 18.1143C57.9546 18.1143 56.8502 16.7368 56.8502 14.5405V14.5278C56.8502 12.3188 57.9356 10.9478 59.6939 10.9478C60.6461 10.9478 61.4395 11.4175 61.8013 12.1665H61.9092V8.37695H63.4962V18H61.9092V16.9146H61.8013C61.4205 17.6699 60.6714 18.1143 59.6939 18.1143ZM60.189 16.7812C61.2681 16.7812 61.9346 15.9307 61.9346 14.5405V14.5278C61.9346 13.1377 61.2618 12.2808 60.189 12.2808C59.1163 12.2808 58.4561 13.1313 58.4561 14.5278V14.5405C58.4561 15.937 59.1099 16.7812 60.189 16.7812Z" + className={styles.featuredIcon__textColor} + fill={textColor} + /> + </svg> + ); +}; diff --git a/src/v4/social/components/CommunityFeed/CommunityFeed.module.css b/src/v4/social/components/CommunityFeed/CommunityFeed.module.css index de4f60faa..b0445ac85 100644 --- a/src/v4/social/components/CommunityFeed/CommunityFeed.module.css +++ b/src/v4/social/components/CommunityFeed/CommunityFeed.module.css @@ -138,9 +138,14 @@ } .communityFeed__emptyPostText { - color: var(--asc-color-base-shade3); + color: var(--asc-color-base-shade4); font-size: 1rem; font-weight: 600; line-height: 1.375rem; margin-top: 0.5rem; } + +.communityFeed__announcePost { + padding-bottom: 0.75rem; + background-color: var(--asc-color-base-shade4); +} diff --git a/src/v4/social/components/CommunityFeed/CommunityFeed.tsx b/src/v4/social/components/CommunityFeed/CommunityFeed.tsx index 62cfdb202..05f756ed9 100644 --- a/src/v4/social/components/CommunityFeed/CommunityFeed.tsx +++ b/src/v4/social/components/CommunityFeed/CommunityFeed.tsx @@ -14,6 +14,7 @@ import { SubscriptionLevels } from '@amityco/ts-sdk'; import useCommunitySubscription from '~/v4/core/hooks/subscriptions/useCommunitySubscription'; import { Button } from '~/v4/core/natives/Button'; import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; +import usePinnedPostsCollection from '~/v4/social/hooks/collections/usePinnedPostCollection'; const CommunityFeedPostContentSkeleton = () => { return ( @@ -57,6 +58,12 @@ export const CommunityFeed = ({ pageId = '*', communityId }: CommunityFeedProps) limit: 10, }); + const { pinnedPost: announcementPosts, isLoading: isLoadingAnnouncementPosts } = + usePinnedPostsCollection({ + communityId, + placement: 'announcement', + }); + const { AmityCommunityProfilePageBehavior } = usePageBehavior(); useCommunitySubscription({ communityId, level: SubscriptionLevels.POST }); @@ -86,47 +93,93 @@ export const CommunityFeed = ({ pageId = '*', communityId }: CommunityFeedProps) if (isExcluded) return null; - const renderPublicCommunityFeed = () => ( - <> - {posts && - posts.map((post) => ( - <Button - className={styles.communityFeed__postContent} - onPress={() => - AmityCommunityProfilePageBehavior?.goToPostDetailPage?.({ - postId: post.postId, - hideTarget: true, - }) - } - > - <PostContent - key={post.postId} - post={post} - category={AmityPostCategory.GENERAL} - style={AmityPostContentComponentStyle.FEED} - hideTarget - onClick={() => + const renderPublicCommunityFeed = () => { + //TODO : Change any type to be Amity.PinnedPost after SDK deploy to PROD + const filteredPosts = posts.filter( + (post) => + !announcementPosts.some( + (announcementPost: any) => announcementPost.post.postId === post.postId, + ), + ); + + return ( + <> + {isLoading + ? Array.from({ length: 2 }).map((_, index) => ( + <CommunityFeedPostContentSkeleton key={index} /> + )) + : filteredPosts && + filteredPosts.map((post) => ( + <Button + className={styles.communityFeed__postContent} + onPress={() => + AmityCommunityProfilePageBehavior?.goToPostDetailPage?.({ + postId: post.postId, + hideTarget: true, + }) + } + > + <PostContent + key={post.postId} + post={post} + category={AmityPostCategory.GENERAL} + style={AmityPostContentComponentStyle.FEED} + hideTarget + onClick={() => + AmityCommunityProfilePageBehavior?.goToPostDetailPage?.({ + postId: post.postId, + hideTarget: true, + }) + } + /> + </Button> + ))} + {posts?.length === 0 && !isLoading && ( + <div className={styles.communityFeed__emptyPost}> + <EmptyPost className={styles.communityFeed__emptyPostIcon} /> + <p className={styles.communityFeed__emptyPostText}>No post yet</p> + </div> + )} + <div ref={observerTarget} className={styles.communityFeed__observerTarget} /> + </> + ); + }; + + const renderAnnouncementPost = () => { + return isLoadingAnnouncementPosts ? ( + <CommunityFeedPostContentSkeleton /> + ) : ( + announcementPosts && + announcementPosts.map(({ post }: Amity.Post) => { + return ( + <Button + onPress={() => { AmityCommunityProfilePageBehavior?.goToPostDetailPage?.({ postId: post.postId, hideTarget: true, - }) - } - /> - </Button> - ))} - {isLoading && - Array.from({ length: 2 }).map((_, index) => ( - <CommunityFeedPostContentSkeleton key={index} /> - ))} - {posts?.length === 0 && !isLoading && ( - <div className={styles.communityFeed__emptyPost}> - <EmptyPost className={styles.communityFeed__emptyPostIcon} /> - <p className={styles.communityFeed__emptyPostText}>No post yet</p> - </div> - )} - <div ref={observerTarget} className={styles.communityFeed__observerTarget} /> - </> - ); + category: AmityPostCategory.ANNOUNCEMENT, + }); + }} + className={styles.communityFeed__announcePost} + > + <PostContent + key={post.postId} + post={post} + category={AmityPostCategory.ANNOUNCEMENT} + style={AmityPostContentComponentStyle.FEED} + hideTarget + onClick={() => + AmityCommunityProfilePageBehavior?.goToPostDetailPage?.({ + postId: post.postId, + hideTarget: true, + }) + } + /> + </Button> + ); + }) + ); + }; return ( <div @@ -135,7 +188,10 @@ export const CommunityFeed = ({ pageId = '*', communityId }: CommunityFeedProps) style={themeStyles} > {isMemberPrivateCommunity || community?.isPublic ? ( - renderPublicCommunityFeed() + <> + {renderAnnouncementPost()} + {renderPublicCommunityFeed()} + </> ) : ( <LockPrivateContent /> )} diff --git a/src/v4/social/components/CommunityHeader/CommunityHeader.module.css b/src/v4/social/components/CommunityHeader/CommunityHeader.module.css index fb6850695..480e40050 100644 --- a/src/v4/social/components/CommunityHeader/CommunityHeader.module.css +++ b/src/v4/social/components/CommunityHeader/CommunityHeader.module.css @@ -47,7 +47,7 @@ width: 100%; display: flex; flex-direction: column; - padding: 1rem 1rem 0.75rem; + padding: 1rem 1rem 0; align-items: flex-start; gap: 0.625rem; overflow: hidden; @@ -132,3 +132,10 @@ height: 1rem; fill: var(--asc-color-base-default); } + +.communityProfile__divider { + width: 100%; + height: 1px; + background-color: var(--asc-color-base-shade4); + margin-bottom: 0.75rem; +} diff --git a/src/v4/social/components/CommunityHeader/CommunityHeader.tsx b/src/v4/social/components/CommunityHeader/CommunityHeader.tsx index 7a9d66035..967a3faa5 100644 --- a/src/v4/social/components/CommunityHeader/CommunityHeader.tsx +++ b/src/v4/social/components/CommunityHeader/CommunityHeader.tsx @@ -86,6 +86,7 @@ export const CommunityHeader: React.FC<CommunityProfileHeaderProps> = ({ )} <CommunityProfileTab pageId={pageId} activeTab={activeTab} onTabChange={handleTabChange} /> </div> + <div className={styles.communityProfile__divider} /> </div> ); }; diff --git a/src/v4/social/components/PostContent/PostContent.tsx b/src/v4/social/components/PostContent/PostContent.tsx index b87617589..31618cb6c 100644 --- a/src/v4/social/components/PostContent/PostContent.tsx +++ b/src/v4/social/components/PostContent/PostContent.tsx @@ -38,6 +38,7 @@ import { Button } from '~/v4/core/natives/Button'; import { PageTypes, useNavigation } from '~/v4/core/providers/NavigationProvider'; import dayjs from 'dayjs'; import { useVisibilitySensor } from '~/v4/social/hooks/useVisibilitySensor'; +import { AnnouncementBadge } from '~/v4/social/elements/AnnouncementBadge'; export enum AmityPostContentComponentStyle { FEED = 'feed', @@ -176,7 +177,7 @@ export const PostContent = ({ onPostDeleted, category, hideMenu = false, - hideTarget, + hideTarget = false, style, }: PostContentProps) => { const componentId = 'post_content'; @@ -328,6 +329,9 @@ export const PostContent = ({ return ( <div ref={elementRef} className={styles.postContent} style={themeStyles}> + {category === AmityPostCategory.ANNOUNCEMENT && ( + <AnnouncementBadge pageId={pageId} componentId={componentId} /> + )} <div className={styles.postContent__bar} data-type={style}> <div className={styles.postContent__bar__userAvatar}> <UserAvatar userId={post?.postedUserId} /> diff --git a/src/v4/social/elements/AnnouncementBadge/AnnouncementBadge.module.css b/src/v4/social/elements/AnnouncementBadge/AnnouncementBadge.module.css new file mode 100644 index 000000000..c2b62c9bb --- /dev/null +++ b/src/v4/social/elements/AnnouncementBadge/AnnouncementBadge.module.css @@ -0,0 +1,5 @@ +.announcementBadge { + position: relative; + left: -1rem; + margin-bottom: 0.5rem; +} diff --git a/src/v4/social/elements/AnnouncementBadge/AnnouncementBadge.tsx b/src/v4/social/elements/AnnouncementBadge/AnnouncementBadge.tsx new file mode 100644 index 000000000..a0d0b6830 --- /dev/null +++ b/src/v4/social/elements/AnnouncementBadge/AnnouncementBadge.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import styles from './AnnouncementBadge.module.css'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; +import { IconComponent } from '~/v4/core/IconComponent'; +import { FeaturedIcon } from '~/v4/icons/Featured/Featured'; + +interface AnnouncementBadge { + pageId?: string; + componentId?: string; +} + +export const AnnouncementBadge = ({ pageId = '*', componentId = '*' }: AnnouncementBadge) => { + const elementId = 'announcement_badge'; + const { config, isExcluded, accessibilityId, uiReference, defaultConfig } = useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + return ( + <IconComponent + data-qa-anchor={accessibilityId} + defaultIcon={() => <FeaturedIcon className={styles.announcementBadge} />} + imgIcon={() => <img src={config.icon} alt={uiReference} />} + configIconName={config.icon} + defaultIconName={defaultConfig.icon} + /> + ); +}; diff --git a/src/v4/social/elements/AnnouncementBadge/index.ts b/src/v4/social/elements/AnnouncementBadge/index.ts new file mode 100644 index 000000000..a4eb96d09 --- /dev/null +++ b/src/v4/social/elements/AnnouncementBadge/index.ts @@ -0,0 +1 @@ +export { AnnouncementBadge } from './AnnouncementBadge'; diff --git a/src/v4/social/elements/CommunityFeedTabButton/CommunityFeedTabButton.tsx b/src/v4/social/elements/CommunityFeedTabButton/CommunityFeedTabButton.tsx new file mode 100644 index 000000000..0c98b7ef8 --- /dev/null +++ b/src/v4/social/elements/CommunityFeedTabButton/CommunityFeedTabButton.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { useAmityElement } from '~/v4/core/hooks/uikit'; + +interface CommunityFeedTabButtonProps { + pageId?: string; + componentId?: string; +} + +export const CommunityFeedTabButton = ({ + pageId = '*', + componentId = '*', +}: CommunityFeedTabButtonProps) => { + const elementId = 'community_feed_tab_button'; + const { config, accessibilityId, themeStyles, isExcluded } = useAmityElement({ + pageId, + componentId, + elementId, + }); + + if (isExcluded) return null; + + return ( + <div data-qa-anchor={accessibilityId} style={themeStyles}> + CommunityFeedTabButton + </div> + ); +}; diff --git a/src/v4/social/elements/CommunityFeedTabButton/index.ts b/src/v4/social/elements/CommunityFeedTabButton/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/v4/social/elements/CommunityName/CommunityName.tsx b/src/v4/social/elements/CommunityName/CommunityName.tsx index 6a67cfa08..551e26707 100644 --- a/src/v4/social/elements/CommunityName/CommunityName.tsx +++ b/src/v4/social/elements/CommunityName/CommunityName.tsx @@ -1,7 +1,7 @@ import React from 'react'; +import styles from './CommunityName.module.css'; import { Typography } from '~/v4/core/components'; import { useAmityElement } from '~/v4/core/hooks/uikit'; -import styles from './CommunityName.module.css'; interface CommunityNameProps { name: string; diff --git a/src/v4/social/elements/CommunityProfileTab/CommunityProfileTab.module.css b/src/v4/social/elements/CommunityProfileTab/CommunityProfileTab.module.css index 29698c649..c9596344a 100644 --- a/src/v4/social/elements/CommunityProfileTab/CommunityProfileTab.module.css +++ b/src/v4/social/elements/CommunityProfileTab/CommunityProfileTab.module.css @@ -7,11 +7,12 @@ .communityTabs__tab { display: flex; - align-items: flex-start; justify-content: center; padding: 1rem; flex-grow: 1; width: 4.5rem; + height: 1rem; + align-items: center; --asc-icon-color: var(--asc-color-base-shade3); diff --git a/src/v4/social/hooks/collections/usePinnedPostCollection.ts b/src/v4/social/hooks/collections/usePinnedPostCollection.ts new file mode 100644 index 000000000..5c125def4 --- /dev/null +++ b/src/v4/social/hooks/collections/usePinnedPostCollection.ts @@ -0,0 +1,25 @@ +import { PostRepository } from '@amityco/ts-sdk'; +import useLiveCollection from '~/v4/core/hooks/useLiveCollection'; + +const QUERY_LIMIT = 10; + +export default function usePinnedPostsCollection({ + communityId, + placement, + limit = QUERY_LIMIT, +}: Partial<Parameters<typeof PostRepository.getPinnedPosts>[0]>) { + const { items, ...rest } = useLiveCollection({ + fetcher: PostRepository.getPinnedPosts, + params: { + communityId: communityId, + placement: placement, + sortBy: 'lastPinned', + }, + shouldCall: !!communityId, + }); + + return { + pinnedPost: items, + ...rest, + }; +} diff --git a/src/v4/social/pages/Application/index.tsx b/src/v4/social/pages/Application/index.tsx index a268d62c8..ce8ed9141 100644 --- a/src/v4/social/pages/Application/index.tsx +++ b/src/v4/social/pages/Application/index.tsx @@ -36,7 +36,11 @@ const Application = () => { {page.type === PageTypes.SocialHomePage && <SocialHomePage />} {page.type === PageTypes.SocialGlobalSearchPage && <SocialGlobalSearchPage />} {page.type === PageTypes.PostDetailPage && ( - <PostDetailPage id={page.context?.postId} hideTarget={page.context?.hideTarget} /> + <PostDetailPage + id={page.context?.postId} + hideTarget={page.context?.hideTarget} + category={page.context?.category} + /> )} {page.type === PageTypes.StoryTargetSelectionPage && <StoryTargetSelectionPage />} {page.type === PageTypes.CommunityProfilePage && ( diff --git a/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx b/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx index da4c64c49..952fcbf17 100644 --- a/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx +++ b/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx @@ -22,9 +22,10 @@ import useCommunity from '~/v4/core/hooks/collections/useCommunity'; interface PostDetailPageProps { id: string; hideTarget?: boolean; + category?: AmityPostCategory; } -export function PostDetailPage({ id, hideTarget }: PostDetailPageProps) { +export function PostDetailPage({ id, hideTarget, category }: PostDetailPageProps) { const pageId = 'post_detail_page'; const { post, isLoading: isPostLoading } = usePost(id); const { themeStyles } = useAmityPage({ @@ -54,7 +55,7 @@ export function PostDetailPage({ id, hideTarget }: PostDetailPageProps) { <PostContent pageId={pageId} post={post} - category={AmityPostCategory.GENERAL} + category={category ?? AmityPostCategory.GENERAL} style={AmityPostContentComponentStyle.DETAIL} hideTarget={hideTarget} /> From cbc72fd5ddd6e7c646af689fccf9373d3f4cb0d9 Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Wed, 28 Aug 2024 13:36:09 +0700 Subject: [PATCH 289/300] fix: ASC-25190 - delete announcement post (#612) * fix: refresh live collection * fix: type * fix: SDK version stg * fix: add dot * fix: add dot * fix: comments demo * fix: update permission to create story * fix: story permission * fix: story permission * refactor: typo --- package.json | 2 +- pnpm-lock.yaml | 22 +++++++++--------- src/v4/core/hooks/useLiveCollection.ts | 21 +++++++++++++++++ .../SDKConnectorLiveCollectionProvider.tsx | 9 ++++++++ .../CommunityFeed/CommunityFeed.tsx | 18 +++++++++++---- .../CommunityHeader.module.css | 4 ++-- .../CommunityHeader/CommunityHeader.tsx | 4 ++-- .../components/StoryTab/StoryTabCommunity.tsx | 6 ++--- .../CommunityProfileTab.module.css | 3 ++- .../PostMenu/PostMenu.module.css | 1 - .../internal-components/PostMenu/PostMenu.tsx | 23 +++++++++++-------- .../CommunityProfilePage.tsx | 7 ++++-- 12 files changed, 81 insertions(+), 39 deletions(-) diff --git a/package.json b/package.json index fc1c0d326..836dc5cfd 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "react-dom": ">=17.0.2" }, "devDependencies": { - "@amityco/ts-sdk": "^6.29.2", + "@amityco/ts-sdk": "6.29.3-1b05de6.0", "@eslint/js": "^9.4.0", "@storybook/addon-a11y": "^7.6.7", "@storybook/addon-actions": "^7.6.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d8c5b121e..1168d06ba 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -133,8 +133,8 @@ importers: version: 3.23.8 devDependencies: '@amityco/ts-sdk': - specifier: ^6.29.2 - version: 6.29.2 + specifier: 6.29.3-1b05de6.0 + version: 6.29.3-1b05de6.0 '@eslint/js': specifier: ^9.4.0 version: 9.7.0 @@ -315,8 +315,8 @@ packages: '@adobe/css-tools@4.3.3': resolution: {integrity: sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==} - '@amityco/ts-sdk@6.29.2': - resolution: {integrity: sha512-g6+5gWmOKKgbQeWj8wC+zNEeTmYIMjOFqCSc+X/kog5Cs2EjlWgZzL3QrIb7ulnjQDZ/h8CC8ZepkGcEBJIkjQ==} + '@amityco/ts-sdk@6.29.3-1b05de6.0': + resolution: {integrity: sha512-mG6foz1TktBuj80uQMhIn969zR8McC+nZCsQ7gjL76q39UyHNF3zSiBYP14wDz9COVJkuklLBzGRPMHUNOeyBQ==} engines: {node: '>=12', npm: '>=6'} '@ampproject/remapping@2.3.0': @@ -7690,7 +7690,7 @@ snapshots: '@adobe/css-tools@4.3.3': {} - '@amityco/ts-sdk@6.29.2': + '@amityco/ts-sdk@6.29.3-1b05de6.0': dependencies: agentkeepalive: 4.5.0 axios: 1.7.4(debug@4.3.6) @@ -8550,7 +8550,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.24.7 '@babel/parser': 7.24.7 '@babel/types': 7.24.7 - debug: 4.3.5 + debug: 4.3.6 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -11622,7 +11622,7 @@ snapshots: dependencies: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/visitor-keys': 5.62.0 - debug: 4.3.5 + debug: 4.3.6 globby: 11.1.0 is-glob: 4.0.3 semver: 7.6.2 @@ -11651,7 +11651,7 @@ snapshots: dependencies: '@typescript-eslint/types': 7.16.1 '@typescript-eslint/visitor-keys': 7.16.1 - debug: 4.3.5 + debug: 4.3.6 globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.4 @@ -12888,7 +12888,7 @@ snapshots: esbuild-register@3.5.0(esbuild@0.18.20): dependencies: - debug: 4.3.5 + debug: 4.3.6 esbuild: 0.18.20 transitivePeerDependencies: - supports-color @@ -13602,7 +13602,7 @@ snapshots: https-proxy-agent@4.0.0: dependencies: agent-base: 5.1.1 - debug: 4.3.5 + debug: 4.3.6 transitivePeerDependencies: - supports-color @@ -15063,7 +15063,7 @@ snapshots: puppeteer-core@2.1.1: dependencies: '@types/mime-types': 2.1.4 - debug: 4.3.5 + debug: 4.3.6 extract-zip: 1.7.0 https-proxy-agent: 4.0.0 mime: 2.6.0 diff --git a/src/v4/core/hooks/useLiveCollection.ts b/src/v4/core/hooks/useLiveCollection.ts index 586e25bcc..23de3dab5 100644 --- a/src/v4/core/hooks/useLiveCollection.ts +++ b/src/v4/core/hooks/useLiveCollection.ts @@ -24,6 +24,7 @@ function useLiveCollection<TCallback, TParams>({ loadMore: () => void; error: Error | null; loadMoreHasBeenCalled: boolean; + refresh: () => void; } { const { subscribe } = useSDKLiveCollectionConnector(); const [loadMoreHasBeenCalled, setLoadMoreHasBeenCalled] = useState(false); @@ -32,6 +33,7 @@ function useLiveCollection<TCallback, TParams>({ const [error, setError] = useState<Error | null>(null); const [hasMore, setHasMore] = useState(false); const loadMoreFnRef = useRef<(() => void) | null>(null); + const unsubscribeRef = useRef<Amity.Unsubscriber | null>(null); const loadMore = useCallback(() => { if (loadMoreFnRef.current) { @@ -60,12 +62,30 @@ function useLiveCollection<TCallback, TParams>({ params, callback: callbackFn, }); + unsubscribeRef.current = unsubscribe; return () => { unsubscribe(); }; }, [params, shouldCall]); + const refresh = useCallback(() => { + if (unsubscribeRef.current) { + unsubscribeRef.current(); + } + + const { unsubscribe } = subscribe({ + fetcher, + params, + callback: callbackFn, + refresh: true, + }); + + unsubscribeRef.current = unsubscribe; + + return () => unsubscribe(); + }, []); + return { items, hasMore, @@ -73,6 +93,7 @@ function useLiveCollection<TCallback, TParams>({ loadMore, error, loadMoreHasBeenCalled, + refresh, }; } diff --git a/src/v4/core/providers/SDKConnectorProvider/SDKConnectorLiveCollectionProvider.tsx b/src/v4/core/providers/SDKConnectorProvider/SDKConnectorLiveCollectionProvider.tsx index 9fee6b430..30dadfbdc 100644 --- a/src/v4/core/providers/SDKConnectorProvider/SDKConnectorLiveCollectionProvider.tsx +++ b/src/v4/core/providers/SDKConnectorProvider/SDKConnectorLiveCollectionProvider.tsx @@ -6,6 +6,7 @@ const SDKConnectorLiveCollectionContext = createContext({ params, callback, config, + refresh = false, }: { fetcher: ( params: Amity.LiveCollectionParams<TParams>, @@ -15,6 +16,7 @@ const SDKConnectorLiveCollectionContext = createContext({ params: Amity.LiveCollectionParams<TParams>; callback: Amity.LiveCollectionCallback<TCallback>; config?: Amity.LiveCollectionConfig; + refresh?: boolean; }) => { return { unsubscribe: () => {} }; }, @@ -47,6 +49,7 @@ export default function SDKConnectorLiveCollectionProvider({ params, callback, config, + refresh = false, }: { fetcher: ( params: Amity.LiveCollectionParams<TParams>, @@ -56,10 +59,16 @@ export default function SDKConnectorLiveCollectionProvider({ params: Amity.LiveCollectionParams<TParams>; callback: Amity.LiveCollectionCallback<TCallback>; config?: Amity.LiveCollectionConfig; + refresh?: boolean; }) => { if (currentUserId == null) return { unsubscribe() {} }; const key = getSubscriberKey(fetcher.name, params); + if (refresh) { + delete responseMap.current[key]; + delete subscriberMap.current[key]; + } + if (subscriberMap.current[key] && responseMap.current[key]) { callback?.(responseMap.current[key] as Amity.LiveCollection<TCallback>); subscriberMap.current[key].push(callback as Amity.LiveCollectionCallback<unknown>); diff --git a/src/v4/social/components/CommunityFeed/CommunityFeed.tsx b/src/v4/social/components/CommunityFeed/CommunityFeed.tsx index 05f756ed9..665b15e25 100644 --- a/src/v4/social/components/CommunityFeed/CommunityFeed.tsx +++ b/src/v4/social/components/CommunityFeed/CommunityFeed.tsx @@ -58,11 +58,14 @@ export const CommunityFeed = ({ pageId = '*', communityId }: CommunityFeedProps) limit: 10, }); - const { pinnedPost: announcementPosts, isLoading: isLoadingAnnouncementPosts } = - usePinnedPostsCollection({ - communityId, - placement: 'announcement', - }); + const { + pinnedPost: announcementPosts, + isLoading: isLoadingAnnouncementPosts, + refresh, + } = usePinnedPostsCollection({ + communityId, + placement: 'announcement', + }); const { AmityCommunityProfilePageBehavior } = usePageBehavior(); @@ -91,6 +94,10 @@ export const CommunityFeed = ({ pageId = '*', communityId }: CommunityFeedProps) }; }, [hasMore, loadMore]); + useEffect(() => { + refresh(); + }, []); + if (isExcluded) return null; const renderPublicCommunityFeed = () => { @@ -174,6 +181,7 @@ export const CommunityFeed = ({ pageId = '*', communityId }: CommunityFeedProps) hideTarget: true, }) } + onPostDeleted={() => refresh()} /> </Button> ); diff --git a/src/v4/social/components/CommunityHeader/CommunityHeader.module.css b/src/v4/social/components/CommunityHeader/CommunityHeader.module.css index 480e40050..13066334e 100644 --- a/src/v4/social/components/CommunityHeader/CommunityHeader.module.css +++ b/src/v4/social/components/CommunityHeader/CommunityHeader.module.css @@ -49,7 +49,7 @@ flex-direction: column; padding: 1rem 1rem 0; align-items: flex-start; - gap: 0.625rem; + gap: 0.5rem; overflow: hidden; } @@ -137,5 +137,5 @@ width: 100%; height: 1px; background-color: var(--asc-color-base-shade4); - margin-bottom: 0.75rem; + margin-bottom: 0.25rem; } diff --git a/src/v4/social/components/CommunityHeader/CommunityHeader.tsx b/src/v4/social/components/CommunityHeader/CommunityHeader.tsx index 967a3faa5..ae034c8bf 100644 --- a/src/v4/social/components/CommunityHeader/CommunityHeader.tsx +++ b/src/v4/social/components/CommunityHeader/CommunityHeader.tsx @@ -79,11 +79,11 @@ export const CommunityHeader: React.FC<CommunityProfileHeaderProps> = ({ <div> <StoryTab type="communityFeed" communityId={community.communityId} /> </div> - {isShowPendingPost && ( + {/* {isShowPendingPost && ( <div className={styles.communityProfile__pendingPost__container}> <CommunityPendingPost /> </div> - )} + )} */} <CommunityProfileTab pageId={pageId} activeTab={activeTab} onTabChange={handleTabChange} /> </div> <div className={styles.communityProfile__divider} /> diff --git a/src/v4/social/components/StoryTab/StoryTabCommunity.tsx b/src/v4/social/components/StoryTab/StoryTabCommunity.tsx index 7d446a0be..38ad2fbe2 100644 --- a/src/v4/social/components/StoryTab/StoryTabCommunity.tsx +++ b/src/v4/social/components/StoryTab/StoryTabCommunity.tsx @@ -65,9 +65,7 @@ export const StoryTabCommunityFeed: React.FC<StoryTabCommunityFeedProps> = ({ const { currentUserId, client } = useSDK(); const { user } = useUser(currentUserId); const isGlobalAdmin = isAdmin(user?.roles); - const isCommunityModerator = isModerator(user?.roles); - const hasStoryPermission = - isGlobalAdmin || isCommunityModerator || checkStoryPermission(client, communityId); + const hasStoryPermission = isGlobalAdmin || checkStoryPermission(client, communityId); const hasStories = stories?.length > 0; const hasUnSeen = stories.some((story) => !story?.isSeen); const uploading = stories.some((story) => story?.syncState === 'syncing'); @@ -80,7 +78,7 @@ export const StoryTabCommunityFeed: React.FC<StoryTabCommunityFeedProps> = ({ if (isExcluded) return null; - if (!hasStories && !hasStoryPermission) return null; + if (!hasStories && !hasStoryPermission && !community?.isJoined) return null; return ( <div diff --git a/src/v4/social/elements/CommunityProfileTab/CommunityProfileTab.module.css b/src/v4/social/elements/CommunityProfileTab/CommunityProfileTab.module.css index c9596344a..65c37cc0a 100644 --- a/src/v4/social/elements/CommunityProfileTab/CommunityProfileTab.module.css +++ b/src/v4/social/elements/CommunityProfileTab/CommunityProfileTab.module.css @@ -3,6 +3,7 @@ border-radius: 8px; display: flex; align-items: center; + width: 100%; } .communityTabs__tab { @@ -10,7 +11,7 @@ justify-content: center; padding: 1rem; flex-grow: 1; - width: 4.5rem; + width: 100%; height: 1rem; align-items: center; diff --git a/src/v4/social/internal-components/PostMenu/PostMenu.module.css b/src/v4/social/internal-components/PostMenu/PostMenu.module.css index 8939c1549..6cf49a542 100644 --- a/src/v4/social/internal-components/PostMenu/PostMenu.module.css +++ b/src/v4/social/internal-components/PostMenu/PostMenu.module.css @@ -30,7 +30,6 @@ .postMenu__editPost__text { color: var(--asc-color-base-default); - font-weight: 600; } .postMenu__editPost__icon { diff --git a/src/v4/social/internal-components/PostMenu/PostMenu.tsx b/src/v4/social/internal-components/PostMenu/PostMenu.tsx index 034af1774..c732d0384 100644 --- a/src/v4/social/internal-components/PostMenu/PostMenu.tsx +++ b/src/v4/social/internal-components/PostMenu/PostMenu.tsx @@ -12,6 +12,7 @@ import useCommunity from '~/v4/core/hooks/collections/useCommunity'; import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; import { Mode } from '~/v4/social/pages/PostComposerPage'; import { useDrawer } from '~/v4/core/providers/DrawerProvider'; +import { Typography } from '~/v4/core/components'; const PenSvg = (props: React.SVGProps<SVGSVGElement>) => ( <svg @@ -118,16 +119,16 @@ export const PostMenu = ({ post, isFlaggable: showReportPostButton, onReportSuccess: () => { - success({ content: 'Post reported' }); + success({ content: 'Post reported.' }); }, onReportError: () => { - error({ content: 'Failed to report post' }); + error({ content: 'Failed to report post.' }); }, onUnreportSuccess: () => { - success({ content: 'Post unreported' }); + success({ content: 'Post unreported.' }); }, onUnreportError: () => { - error({ content: 'Failed to unreport post' }); + error({ content: 'Failed to unreport post.' }); }, }); @@ -139,11 +140,11 @@ export const PostMenu = ({ return PostRepository.hardDeletePost(post.postId); }, onSuccess: () => { - success({ content: 'Post deleted' }); + success({ content: 'Post deleted.' }); onPostDeleted?.(post); }, onError: () => { - error({ content: 'Failed to delete post' }); + error({ content: 'Failed to delete post.' }); }, }); @@ -174,9 +175,9 @@ export const PostMenu = ({ }} > <FlagSvg className={styles.postMenu__reportPost__icon} /> - <span className={styles.postMenu__reportPost__text}> + <Typography.Title className={styles.postMenu__reportPost__text}> {isFlaggedByMe ? 'Unreport post' : 'Report post'} - </span> + </Typography.Title> </Button> ) : null} {showEditPostButton ? ( @@ -191,13 +192,15 @@ export const PostMenu = ({ }} > <PenSvg className={styles.postMenu__editPost__icon} /> - <span className={styles.postMenu__editPost__text}>Edit post</span> + <Typography.Title className={styles.postMenu__editPost__text}>Edit post</Typography.Title> </Button> ) : null} {showDeletePostButton ? ( <Button className={styles.postMenu__item} onPress={() => onDeleteClick()}> <TrashSvg className={styles.postMenu__deletePost__icon} /> - <span className={styles.postMenu__deletePost__text}>Delete post</span> + <Typography.Title className={styles.postMenu__deletePost__text}> + Delete post + </Typography.Title> </Button> ) : null} </div> diff --git a/src/v4/social/pages/CommunityProfilePage/CommunityProfilePage.tsx b/src/v4/social/pages/CommunityProfilePage/CommunityProfilePage.tsx index b7fd48644..c7b863cde 100644 --- a/src/v4/social/pages/CommunityProfilePage/CommunityProfilePage.tsx +++ b/src/v4/social/pages/CommunityProfilePage/CommunityProfilePage.tsx @@ -27,6 +27,7 @@ export const CommunityProfilePage: React.FC<CommunityProfileProps> = ({ communit const touchStartY = useRef(0); const [touchDiff, setTouchDiff] = useState(0); + const [refreshKey, setRefreshKey] = useState(0); const renderTabContent = () => { switch (activeTab) { @@ -39,7 +40,9 @@ export const CommunityProfilePage: React.FC<CommunityProfileProps> = ({ communit } }; - const handleRefresh = async () => {}; + const handleRefresh = async () => { + setRefreshKey((prevKey) => prevKey + 1); + }; return ( <div @@ -81,7 +84,7 @@ export const CommunityProfilePage: React.FC<CommunityProfileProps> = ({ communit {community ? <CommunityHeader community={community} /> : <CommunityProfileSkeleton />} - {renderTabContent()} + <div key={refreshKey}>{renderTabContent()}</div> </div> ); }; From 9767c6e93dddd3fff3bd42367230f33a0895f30b Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Wed, 28 Aug 2024 15:04:03 +0700 Subject: [PATCH 290/300] fix: ASC-25195 - hide chat input for normal user (#617) * feat: hide chat input for normal user * fix: condition to hide compose bar --- src/chat/components/Chat/index.tsx | 14 +++++++++++++- src/chat/hooks/useChannelPermission.ts | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 src/chat/hooks/useChannelPermission.ts diff --git a/src/chat/components/Chat/index.tsx b/src/chat/components/Chat/index.tsx index 917502530..b8df3a044 100644 --- a/src/chat/components/Chat/index.tsx +++ b/src/chat/components/Chat/index.tsx @@ -13,6 +13,8 @@ import ChatHeader from '~/chat/components/ChatHeader'; import { ChannelContainer } from './styles'; import { useCustomComponent } from '~/core/providers/CustomComponentsProvider'; +import { useChannelPermission } from '~/chat/hooks/useChannelPermission'; +import useChannel from '~/chat/hooks/useChannel'; interface ChatProps { channelId: string; @@ -27,6 +29,9 @@ const Chat = ({ channelId, onChatDetailsClick, shouldShowChatDetails }: ChatProp }; }, [channelId]); + const { isModerator } = useChannelPermission(channelId); + const channel = useChannel(channelId); + const sendMessage = async (text: string) => { return MessageRepository.createMessage({ subChannelId: channelId, @@ -35,6 +40,13 @@ const Chat = ({ channelId, onChatDetailsClick, shouldShowChatDetails }: ChatProp }); }; + const renderMessageComposeBar = () => { + if (channel?.type !== 'broadcast' || (channel?.type === 'broadcast' && isModerator)) { + return <MessageComposeBar onSubmit={sendMessage} />; + } + return null; + }; + return ( <ChannelContainer> <ChatHeader @@ -43,7 +55,7 @@ const Chat = ({ channelId, onChatDetailsClick, shouldShowChatDetails }: ChatProp onChatDetailsClick={onChatDetailsClick} /> <MessageList channelId={channelId} /> - <MessageComposeBar onSubmit={sendMessage} /> + {renderMessageComposeBar()} </ChannelContainer> ); }; diff --git a/src/chat/hooks/useChannelPermission.ts b/src/chat/hooks/useChannelPermission.ts new file mode 100644 index 000000000..a1ae6bf54 --- /dev/null +++ b/src/chat/hooks/useChannelPermission.ts @@ -0,0 +1,18 @@ +import { useMemo } from 'react'; +import useSDK from '~/core/hooks/useSDK'; + +export const useChannelPermission = (subChannelId?: Amity.SubChannel['subChannelId']) => { + const { client } = useSDK(); + + const isModerator = useMemo(() => { + if (!subChannelId) return false; + const currentUser = client?.hasPermission('MUTE_CHANNEL').currentUser() || false; + const currentUserInChannel = + client?.hasPermission('MUTE_CHANNEL').channel(subChannelId) || false; + return currentUser || currentUserInChannel; + }, [subChannelId]); + + return { + isModerator, + }; +}; From 868724fd4fb46e79f26baf8e091ffeeb79501bbd Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Wed, 28 Aug 2024 15:17:54 +0700 Subject: [PATCH 291/300] fix: ASC-00000 - sdk version prod (#623) * fix: sdk version * fix: version sdk --- package.json | 2 +- pnpm-lock.yaml | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 836dc5cfd..fc1c0d326 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "react-dom": ">=17.0.2" }, "devDependencies": { - "@amityco/ts-sdk": "6.29.3-1b05de6.0", + "@amityco/ts-sdk": "^6.29.2", "@eslint/js": "^9.4.0", "@storybook/addon-a11y": "^7.6.7", "@storybook/addon-actions": "^7.6.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1168d06ba..18b5aea44 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -133,8 +133,8 @@ importers: version: 3.23.8 devDependencies: '@amityco/ts-sdk': - specifier: 6.29.3-1b05de6.0 - version: 6.29.3-1b05de6.0 + specifier: ^6.29.2 + version: 6.29.2 '@eslint/js': specifier: ^9.4.0 version: 9.7.0 @@ -315,8 +315,8 @@ packages: '@adobe/css-tools@4.3.3': resolution: {integrity: sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==} - '@amityco/ts-sdk@6.29.3-1b05de6.0': - resolution: {integrity: sha512-mG6foz1TktBuj80uQMhIn969zR8McC+nZCsQ7gjL76q39UyHNF3zSiBYP14wDz9COVJkuklLBzGRPMHUNOeyBQ==} + '@amityco/ts-sdk@6.29.2': + resolution: {integrity: sha512-g6+5gWmOKKgbQeWj8wC+zNEeTmYIMjOFqCSc+X/kog5Cs2EjlWgZzL3QrIb7ulnjQDZ/h8CC8ZepkGcEBJIkjQ==} engines: {node: '>=12', npm: '>=6'} '@ampproject/remapping@2.3.0': @@ -7690,7 +7690,7 @@ snapshots: '@adobe/css-tools@4.3.3': {} - '@amityco/ts-sdk@6.29.3-1b05de6.0': + '@amityco/ts-sdk@6.29.2': dependencies: agentkeepalive: 4.5.0 axios: 1.7.4(debug@4.3.6) @@ -11566,7 +11566,7 @@ snapshots: '@typescript-eslint/types': 7.16.1 '@typescript-eslint/typescript-estree': 7.16.1(typescript@4.9.5) '@typescript-eslint/visitor-keys': 7.16.1 - debug: 4.3.5 + debug: 4.3.6 eslint: 9.7.0 optionalDependencies: typescript: 4.9.5 @@ -16031,7 +16031,7 @@ snapshots: stylus@0.62.0: dependencies: '@adobe/css-tools': 4.3.3 - debug: 4.3.5 + debug: 4.3.6 glob: 7.2.3 sax: 1.3.0 source-map: 0.7.4 From 09bb2d2bc53966ac03317ca184944eff83cce7ec Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Thu, 29 Aug 2024 13:42:51 +0700 Subject: [PATCH 292/300] fix: ASC-235231 - video thumbnail (#626) * fix: video thumbanil * fix: position toast * chore: update sdk * refactor: upload from camera * chore: revert sdk --- pnpm-lock.yaml | 4 +- .../elements/CameraButton/CameraButton.tsx | 77 +++++++------------ .../CreatePost/CreatePost.module.css | 7 +- .../CreatePost/CreatePost.tsx | 5 +- src/v4/social/utils/generateThumbnailVideo.ts | 27 +++++-- 5 files changed, 60 insertions(+), 60 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 18b5aea44..5a45d7c15 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11592,7 +11592,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 6.21.0(typescript@4.9.5) '@typescript-eslint/utils': 6.21.0(eslint@9.7.0)(typescript@4.9.5) - debug: 4.3.5 + debug: 4.3.6 eslint: 9.7.0 ts-api-utils: 1.3.0(typescript@4.9.5) optionalDependencies: @@ -11636,7 +11636,7 @@ snapshots: dependencies: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.3.5 + debug: 4.3.6 globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 diff --git a/src/v4/social/elements/CameraButton/CameraButton.tsx b/src/v4/social/elements/CameraButton/CameraButton.tsx index 8784cc592..c9ad831c7 100644 --- a/src/v4/social/elements/CameraButton/CameraButton.tsx +++ b/src/v4/social/elements/CameraButton/CameraButton.tsx @@ -66,27 +66,22 @@ export function CameraButton({ fileInput.click(); }; - const onLoadMedia: React.ChangeEventHandler<HTMLInputElement> = useCallback((e) => { - e.preventDefault(); - e.stopPropagation(); - const targetFiles = e.target.files ? [...e.target.files] : []; - const isImage = targetFiles.some((file) => file.type.startsWith('image/')); - const isVideo = targetFiles.some((file) => file.type.startsWith('video/')); + const onLoadMedia: React.ChangeEventHandler<HTMLInputElement> = useCallback( + (e) => { + e.preventDefault(); + e.stopPropagation(); + const targetFiles = e.target.files ? [...e.target.files] : []; + const isImage = targetFiles.some((file) => file.type.startsWith('image/')); + const isVideo = targetFiles.some((file) => file.type.startsWith('video/')); - if (isImage) { - onImageFileChange?.(e.target.files ? [...e.target.files] : []); - } else if (isVideo) { - onVideoFileChange?.(e.target.files ? [...e.target.files] : []); - } - }, []); - - const onImageChange: React.ChangeEventHandler<HTMLInputElement> = useCallback((e) => { - onImageFileChange?.(e.target.files ? [...e.target.files] : []); - }, []); - - const onVideoChange: React.ChangeEventHandler<HTMLInputElement> = useCallback((e) => { - onImageFileChange?.(e.target.files ? [...e.target.files] : []); - }, []); + if (isImage) { + onImageFileChange?.(targetFiles); + } else if (isVideo) { + onVideoFileChange?.(targetFiles); + } + }, + [onImageFileChange, onVideoFileChange], + ); return ( <Button @@ -105,35 +100,19 @@ export function CameraButton({ /> {config.text && <Typography.BodyBold>{config.text}</Typography.BodyBold>} - {isVisibleImage && !isVisibleVideo && ( - <input - type="file" - onChange={onImageChange} - id="upload" - accept="image/*" - className={styles.cameraButton_input} - /> - )} - - {!isVisibleImage && isVisibleVideo && ( - <input - type="file" - onChange={onVideoChange} - id="upload" - accept="video/*" - className={styles.cameraButton_input} - /> - )} - - {isVisibleVideo && isVisibleVideo && ( - <input - type="file" - onChange={onLoadMedia} - id="upload" - accept="image/*,video/*" - className={styles.cameraButton_input} - /> - )} + <input + type="file" + onChange={onLoadMedia} + id="upload" + accept={ + isVisibleImage && isVisibleVideo + ? 'image/*,video/*' + : isVisibleImage + ? 'image/*' + : 'video/*' + } + className={styles.cameraButton_input} + /> </Button> ); } diff --git a/src/v4/social/internal-components/CreatePost/CreatePost.module.css b/src/v4/social/internal-components/CreatePost/CreatePost.module.css index 93ea3856b..5102fe5b0 100644 --- a/src/v4/social/internal-components/CreatePost/CreatePost.module.css +++ b/src/v4/social/internal-components/CreatePost/CreatePost.module.css @@ -38,7 +38,12 @@ .createPost__notiWrap { position: relative; - top: 0; + bottom: 6rem; +} + +.createPost__notiWrap[data-item-position='false'] { + position: relative; + bottom: 11rem; } .createPost__formMediaAttachment { diff --git a/src/v4/social/internal-components/CreatePost/CreatePost.tsx b/src/v4/social/internal-components/CreatePost/CreatePost.tsx index ea0cf8ef3..001ba6e9a 100644 --- a/src/v4/social/internal-components/CreatePost/CreatePost.tsx +++ b/src/v4/social/internal-components/CreatePost/CreatePost.tsx @@ -330,7 +330,10 @@ export function CreatePost({ community, targetType, targetId }: AmityPostCompose > <Drawer.Portal container={drawerRef.current}> <Drawer.Content className={styles.drawer__content}> - <div className={styles.createPost__notiWrap}> + <div + data-item-position={snap === HEIGHT_MEDIA_ATTACHMENT_MENU} + className={styles.createPost__notiWrap} + > {isPending && ( <Notification content="Posting..." diff --git a/src/v4/social/utils/generateThumbnailVideo.ts b/src/v4/social/utils/generateThumbnailVideo.ts index 68638054d..e747769a2 100644 --- a/src/v4/social/utils/generateThumbnailVideo.ts +++ b/src/v4/social/utils/generateThumbnailVideo.ts @@ -5,11 +5,13 @@ export const generateThumbnailVideo = (file: File): Promise<string> => { const context = canvasElement.getContext('2d'); videoElement.src = URL.createObjectURL(file); - videoElement.currentTime = 10; - videoElement.addEventListener('loadeddata', () => { + const handleCanPlay = () => { + videoElement.currentTime = 10; + }; + + const handleSeeked = () => { try { - videoElement.pause(); canvasElement.width = videoElement.videoWidth; canvasElement.height = videoElement.videoHeight; context?.drawImage(videoElement, 0, 0, canvasElement.width, canvasElement.height); @@ -17,11 +19,22 @@ export const generateThumbnailVideo = (file: File): Promise<string> => { resolve(thumbnail); } catch (error) { reject(error); + } finally { + videoElement.removeEventListener('canplay', handleCanPlay); + videoElement.removeEventListener('seeked', handleSeeked); + videoElement.removeEventListener('error', handleError); + URL.revokeObjectURL(videoElement.src); } - }); + }; + + const handleError = (event: Event) => { + reject(new Error(`Error loading video: ${event}`)); + }; + + videoElement.addEventListener('canplay', handleCanPlay); + videoElement.addEventListener('seeked', handleSeeked); + videoElement.addEventListener('error', handleError); - videoElement.addEventListener('error', (event) => { - reject(new Error(`Error loading video: ${event.message}`)); - }); + videoElement.load(); }); }; From 184fb7c2f4ac81e982c9c18f8895bea7744848ca Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Thu, 29 Aug 2024 15:51:13 +0700 Subject: [PATCH 293/300] fix: add remove drawer (#627) --- src/v4/social/internal-components/PostMenu/PostMenu.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/v4/social/internal-components/PostMenu/PostMenu.tsx b/src/v4/social/internal-components/PostMenu/PostMenu.tsx index c732d0384..99862ac55 100644 --- a/src/v4/social/internal-components/PostMenu/PostMenu.tsx +++ b/src/v4/social/internal-components/PostMenu/PostMenu.tsx @@ -172,6 +172,7 @@ export const PostMenu = ({ } else { mutateReportPost(); } + removeDrawerData(); }} > <FlagSvg className={styles.postMenu__reportPost__icon} /> From 5ea60ae7add0755fc1fbb8ad27666b87b9c9e3c8 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Thu, 29 Aug 2024 19:08:22 +0700 Subject: [PATCH 294/300] fix: ASC-25204 - post mention (#630) * chore: update PostTextField * fix: mention layout * fix: layout and removed unused code * chore: remove console.log * fix: intersection node --- .../MessageComposer/MessageComposer.tsx | 56 +--- .../hooks/collections/useSearchChannelUser.ts | 2 +- src/v4/core/hooks/useIntersectionObserver.ts | 12 +- .../components/CommentList/CommentList.tsx | 11 +- .../CommunitySearchResult.tsx | 8 +- .../DetailedMediaAttachment.module.css | 8 - .../components/GlobalFeed/GlobalFeed.tsx | 13 +- .../MediaAttachment.module.css | 8 - .../UserSearchResult/UserSearchResult.tsx | 8 +- .../PostTextField/PostTextField.module.css | 24 +- .../elements/PostTextField/PostTextField.tsx | 298 ++++++++++++------ .../elements/VideoButton/VideoButton.tsx | 6 +- .../hooks/useMemberQueryByDisplayName.ts | 1 + .../CreatePost/CreatePost.module.css | 39 +++ .../CreatePost/CreatePost.tsx | 121 ++++--- .../EditPost/EditPost.module.css | 14 + .../internal-components/EditPost/EditPost.tsx | 26 +- .../Lexical/AllMentionItem.module.css | 49 +++ .../Lexical/AllMentionItem.tsx | 31 ++ .../Lexical/MentionItem.module.css | 49 +++ .../Lexical/MentionItem.tsx | 34 ++ .../internal-components/Lexical/utils.ts | 74 ++--- .../MentionTextInput/MentionTextInput.tsx | 21 +- .../internal-components/StoryAd/UIStoryAd.tsx | 1 - .../PostComposerPage/PostComposerPage.tsx | 7 +- .../SelectPostTargetPage.tsx | 8 +- .../StoryTargetSelectionPage.tsx | 6 +- src/v4/social/utils/textToEditorState.ts | 110 ------- 28 files changed, 626 insertions(+), 419 deletions(-) create mode 100644 src/v4/social/internal-components/Lexical/AllMentionItem.module.css create mode 100644 src/v4/social/internal-components/Lexical/AllMentionItem.tsx create mode 100644 src/v4/social/internal-components/Lexical/MentionItem.module.css create mode 100644 src/v4/social/internal-components/Lexical/MentionItem.tsx delete mode 100644 src/v4/social/utils/textToEditorState.ts diff --git a/src/v4/chat/components/MessageComposer/MessageComposer.tsx b/src/v4/chat/components/MessageComposer/MessageComposer.tsx index a2994ada8..9d92de65b 100644 --- a/src/v4/chat/components/MessageComposer/MessageComposer.tsx +++ b/src/v4/chat/components/MessageComposer/MessageComposer.tsx @@ -19,11 +19,8 @@ import { LexicalNode, } from 'lexical'; import { AutoLinkNode, LinkNode } from '@lexical/link'; -import { - MentionPlugin, - MentionTypeaheadOption, -} from '~/v4/social/internal-components/Lexical/plugins/MentionPlugin'; -import { UserAvatar } from '~/v4/social/internal-components/UserAvatar/UserAvatar'; +import { MentionPlugin } from '~/v4/social/internal-components/Lexical/plugins/MentionPlugin'; + import { useMutation } from '@tanstack/react-query'; import { editorStateToText, @@ -44,6 +41,8 @@ import { LinkPlugin } from '~/v4/social/internal-components/Lexical/plugins/Link import { AutoLinkPlugin } from '~/v4/social/internal-components/Lexical/plugins/AutoLinkPlugin'; import { EditorRefPlugin } from '@lexical/react/LexicalEditorRefPlugin'; import { EnterKeyInterceptorPlugin } from '~/v4/social/internal-components/Lexical/plugins/EnterKeyInterceptorPlugin'; +import { AllMentionItem } from '~/v4/social/internal-components/Lexical/AllMentionItem'; +import { MentionItem } from '~/v4/social/internal-components/Lexical/MentionItem'; const COMPOSEBAR_MAX_CHARACTER_LIMIT = 200; @@ -92,53 +91,6 @@ const useSuggestions = (channelId?: string | null) => { return { suggestions, queryString, onQueryChange }; }; -interface MentionItemProps { - isSelected: boolean; - onClick: () => void; - onMouseEnter: () => void; - option: MentionTypeaheadOption<MentionData>; -} - -function MentionItem({ option, isSelected, onClick, onMouseEnter }: MentionItemProps) { - return ( - <li - key={option.key} - tabIndex={-1} - data-is-selected={isSelected} - className={styles.userMentionItem__item} - ref={option.setRefElement} - role="option" - aria-selected={isSelected} - onMouseEnter={onMouseEnter} - onClick={onClick} - > - <div> - <UserAvatar className={styles.userMentionItem__avatar} userId={option.data.userId} /> - </div> - <p className={styles.userMentionItem__displayName}>{option.data.displayName}</p> - </li> - ); -} - -function AllMentionItem({ option, isSelected, onClick, onMouseEnter }: MentionItemProps) { - return ( - <li - key={option.key} - tabIndex={-1} - data-is-selected={isSelected} - className={styles.allMentionItem__item} - ref={option.setRefElement} - role="option" - aria-selected={isSelected} - onMouseEnter={onMouseEnter} - onClick={onClick} - > - <div className={styles.allMentionItem__atSign}>@</div> - <p className={styles.allMentionItem__displayName}>{option.data.displayName}</p> - </li> - ); -} - const nodes = [AutoLinkNode, LinkNode, MentionNode] as Array<Klass<LexicalNode>>; export const MessageComposer = ({ diff --git a/src/v4/chat/hooks/collections/useSearchChannelUser.ts b/src/v4/chat/hooks/collections/useSearchChannelUser.ts index 429bacb9c..8fc9d283c 100644 --- a/src/v4/chat/hooks/collections/useSearchChannelUser.ts +++ b/src/v4/chat/hooks/collections/useSearchChannelUser.ts @@ -16,7 +16,7 @@ export const useSearchChannelUser = ({ }) => { const { items, ...rest } = useLiveCollection({ fetcher: ChannelRepository.Membership.searchMembers, - params: { channelId, search: search || '', memberships, limit }, + params: { channelId, search: search || '', memberships, limit, includeDeleted: false }, shouldCall: !!channelId && shouldCall, }); diff --git a/src/v4/core/hooks/useIntersectionObserver.ts b/src/v4/core/hooks/useIntersectionObserver.ts index 5dc33f156..4e3dce2ce 100644 --- a/src/v4/core/hooks/useIntersectionObserver.ts +++ b/src/v4/core/hooks/useIntersectionObserver.ts @@ -1,25 +1,25 @@ -import { MutableRefObject, useEffect } from 'react'; +import { useEffect } from 'react'; const useIntersectionObserver = ({ - ref, + node, onIntersect, options, }: { - ref: MutableRefObject<HTMLElement | null>; + node?: HTMLElement | null; onIntersect: () => void; options?: IntersectionObserverInit; }) => { useEffect(() => { - if (!ref?.current) return; + if (node == null) return; const observer = new IntersectionObserver( (entries) => entries[0]?.isIntersecting && onIntersect(), options, ); - observer.observe(ref.current); + observer.observe(node); return () => observer.disconnect(); - }, [ref, onIntersect, options]); + }, [node, onIntersect, options]); }; export default useIntersectionObserver; diff --git a/src/v4/social/components/CommentList/CommentList.tsx b/src/v4/social/components/CommentList/CommentList.tsx index 1452ef087..92a1976c8 100644 --- a/src/v4/social/components/CommentList/CommentList.tsx +++ b/src/v4/social/components/CommentList/CommentList.tsx @@ -1,4 +1,4 @@ -import React, { useRef } from 'react'; +import React, { useRef, useState } from 'react'; import { Comment } from '~/v4/social/components/Comment/Comment'; import useIntersectionObserver from '~/v4/core/hooks/useIntersectionObserver'; @@ -49,7 +49,7 @@ export const CommentList = ({ }); const containerRef = useRef<HTMLDivElement>(null); - const intersectionRef = useRef<HTMLDivElement>(null); + const [intersectionNode, setIntersectionNode] = useState<HTMLDivElement | null>(null); const { items, loadMore, hasMore, isLoading } = usePaginator({ fetcher: CommentRepository.getComments, @@ -73,7 +73,7 @@ export const CommentList = ({ const { post } = usePost(referenceType === 'post' ? referenceId : undefined); useIntersectionObserver({ - ref: intersectionRef, + node: intersectionNode, options: { threshold: 0.8, }, @@ -136,7 +136,10 @@ export const CommentList = ({ <CommentSkeleton pageId={pageId} componentId={componentId} numberOfSkeletons={3} /> )} - <div ref={intersectionRef} className={styles.commentList__container_intersection} /> + <div + ref={(node) => setIntersectionNode(node)} + className={styles.commentList__container_intersection} + /> </div> ); }; diff --git a/src/v4/social/components/CommunitySearchResult/CommunitySearchResult.tsx b/src/v4/social/components/CommunitySearchResult/CommunitySearchResult.tsx index 7a85c146e..04936b802 100644 --- a/src/v4/social/components/CommunitySearchResult/CommunitySearchResult.tsx +++ b/src/v4/social/components/CommunitySearchResult/CommunitySearchResult.tsx @@ -1,4 +1,4 @@ -import React, { useRef } from 'react'; +import React, { useRef, useState } from 'react'; import styles from './CommunitySearchResult.module.css'; import useIntersectionObserver from '~/v4/core/hooks/useIntersectionObserver'; import { useAmityComponent } from '~/v4/core/hooks/uikit'; @@ -24,9 +24,9 @@ export const CommunitySearchResult = ({ componentId, }); - const intersectionRef = useRef<HTMLDivElement>(null); + const [intersectionNode, setIntersectionNode] = useState<HTMLDivElement | null>(null); - useIntersectionObserver({ onIntersect: () => onLoadMore(), ref: intersectionRef }); + useIntersectionObserver({ onIntersect: () => onLoadMore(), node: intersectionNode }); return ( <div className={styles.communitySearchResult} style={themeStyles}> @@ -43,7 +43,7 @@ export const CommunitySearchResult = ({ <CommunityItemSkeleton key={index} pageId={pageId} componentId={componentId} /> )) : null} - <div ref={intersectionRef} /> + <div ref={(node) => setIntersectionNode(node)} /> </div> ); }; diff --git a/src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.module.css b/src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.module.css index a93c9f236..40ed8ac49 100644 --- a/src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.module.css +++ b/src/v4/social/components/DetailedMediaAttachment/DetailedMediaAttachment.module.css @@ -2,14 +2,6 @@ display: block; width: 100%; background-color: var(--asc-color-background-default); - border-top-left-radius: 1.25rem; - border-top-right-radius: 1.25rem; - box-shadow: var(--asc-box-shadow-04); - padding-bottom: 2rem; - padding-top: 0.75rem; - margin-top: -1rem; - position: absolute; - bottom: -0.9rem; } .detailedMediaAttachment__swipeDown { diff --git a/src/v4/social/components/GlobalFeed/GlobalFeed.tsx b/src/v4/social/components/GlobalFeed/GlobalFeed.tsx index 5f6921911..c6ccaf817 100644 --- a/src/v4/social/components/GlobalFeed/GlobalFeed.tsx +++ b/src/v4/social/components/GlobalFeed/GlobalFeed.tsx @@ -1,4 +1,4 @@ -import React, { useRef } from 'react'; +import React, { useRef, useState } from 'react'; import { PostContent, PostContentSkeleton } from '~/v4/social/components/PostContent'; import { EmptyNewsfeed } from '~/v4/social/components/EmptyNewsFeed/EmptyNewsFeed'; import useIntersectionObserver from '~/v4/core/hooks/useIntersectionObserver'; @@ -39,12 +39,12 @@ export const GlobalFeed = ({ componentId, }); - const intersectionRef = useRef<HTMLDivElement>(null); + const [intersectionNode, setIntersectionNode] = useState<HTMLDivElement | null>(null); const { AmityGlobalFeedComponentBehavior } = usePageBehavior(); useIntersectionObserver({ - ref: intersectionRef, + node: intersectionNode, onIntersect: () => { onFeedReachBottom(); }, @@ -103,7 +103,12 @@ export const GlobalFeed = ({ </div> )) : null} - {!isLoading && <div ref={intersectionRef} className={styles.global_feed__intersection} />} + {!isLoading && ( + <div + ref={(node) => setIntersectionNode(node)} + className={styles.global_feed__intersection} + /> + )} </div> ); }; diff --git a/src/v4/social/components/MediaAttachment/MediaAttachment.module.css b/src/v4/social/components/MediaAttachment/MediaAttachment.module.css index a10cdf8c8..462944adf 100644 --- a/src/v4/social/components/MediaAttachment/MediaAttachment.module.css +++ b/src/v4/social/components/MediaAttachment/MediaAttachment.module.css @@ -3,14 +3,6 @@ align-items: center; justify-content: space-between; width: 100%; - padding: 0.5rem 1rem; - background-color: var(--asc-color-background-default); - border-top-left-radius: 1.25rem; - border-top-right-radius: 1.25rem; - box-shadow: var(--asc-box-shadow-04); - margin-top: 0.75rem; - position: absolute; - bottom: -0.4rem; } .mediaAttachment__swipeDown { diff --git a/src/v4/social/components/UserSearchResult/UserSearchResult.tsx b/src/v4/social/components/UserSearchResult/UserSearchResult.tsx index 3856b1074..120460b3e 100644 --- a/src/v4/social/components/UserSearchResult/UserSearchResult.tsx +++ b/src/v4/social/components/UserSearchResult/UserSearchResult.tsx @@ -1,4 +1,4 @@ -import React, { useRef } from 'react'; +import React, { useRef, useState } from 'react'; import styles from './UserSearchResult.module.css'; import useIntersectionObserver from '~/v4/core/hooks/useIntersectionObserver'; import { useAmityComponent } from '~/v4/core/hooks/uikit'; @@ -25,9 +25,9 @@ export const UserSearchResult = ({ componentId, }); - const intersectionRef = useRef<HTMLDivElement>(null); + const [intersectionNode, setIntersectionNode] = useState<HTMLDivElement | null>(null); - useIntersectionObserver({ onIntersect: () => onLoadMore(), ref: intersectionRef }); + useIntersectionObserver({ onIntersect: () => onLoadMore(), node: intersectionNode }); return ( <div className={styles.userSearchResult} style={themeStyles}> @@ -39,7 +39,7 @@ export const UserSearchResult = ({ <UserSearchItemSkeleton key={index} pageId={pageId} componentId={componentId} /> )) : null} - <div ref={intersectionRef} /> + <div ref={(node) => setIntersectionNode(node)} /> </div> ); }; diff --git a/src/v4/social/elements/PostTextField/PostTextField.module.css b/src/v4/social/elements/PostTextField/PostTextField.module.css index 5554cf35f..8be851b0b 100644 --- a/src/v4/social/elements/PostTextField/PostTextField.module.css +++ b/src/v4/social/elements/PostTextField/PostTextField.module.css @@ -15,11 +15,6 @@ background-color: var(--asc-color-background-default); } -.editorParagraph span { - color: var(--asc-color-base-default); - background-color: var(--asc-color-background-default); -} - .editorContainer { height: 100%; font-size: var(--asc-text-font-size-md); @@ -32,3 +27,22 @@ border: none; outline: none; } + +.editorRoot { + width: 100%; + background-color: var(--asc-color-secondary-shade4); + border-radius: 1.25rem; + min-height: 2.5rem; + padding: 0.625rem 0.75rem; + border: 0 solid var(--asc-color-background-default); + outline: 0; +} + +.editorLink { + color: var(--asc-color-primary-shade1); + text-decoration: none; +} + +.editorLink:hover { + text-decoration: underline; +} diff --git a/src/v4/social/elements/PostTextField/PostTextField.tsx b/src/v4/social/elements/PostTextField/PostTextField.tsx index 947204adc..4d4d8fcc8 100644 --- a/src/v4/social/elements/PostTextField/PostTextField.tsx +++ b/src/v4/social/elements/PostTextField/PostTextField.tsx @@ -1,34 +1,38 @@ -import React, { forwardRef } from 'react'; +import React, { useMemo, useRef, useState } from 'react'; import { LexicalComposer } from '@lexical/react/LexicalComposer'; import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin'; import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'; import { ContentEditable } from '@lexical/react/LexicalContentEditable'; -import { LexicalEditor, SerializedLexicalNode } from 'lexical'; +import { COMMAND_PRIORITY_HIGH, Klass, LexicalNode } from 'lexical'; import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary'; import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'; import { AutoFocusPlugin } from '@lexical/react/LexicalAutoFocusPlugin'; -import { MentionNode } from '~/v4/social/internal-components/MentionTextInput/MentionNodes'; -import { MentionTextInputPlugin } from '~/v4/social/internal-components/MentionTextInput/MentionTextInput'; -import { CreatePostParams, MetaData } from '~/v4/social/pages/PostComposerPage/PostComposerPage'; +import { + $createMentionNode, + MentionNode, +} from '~/v4/social/internal-components/Lexical/nodes/MentionNode'; +import { MentionPlugin } from '~/v4/social/internal-components/Lexical/plugins/MentionPlugin'; import styles from './PostTextField.module.css'; import { Mentioned, Mentionees } from '~/v4/helpers/utils'; -import { textToEditorState } from '~/v4/social/utils/textToEditorState'; - -const theme = { - ltr: 'ltr', - rtl: 'rtl', - placeholder: styles.editorPlaceholder, - paragraph: styles.editorParagraph, -}; - -interface EditorStateJson extends SerializedLexicalNode { - children: []; -} +import { LinkPlugin } from '~/v4/social/internal-components/Lexical/plugins/LinkPlugin'; +import { AutoLinkPlugin } from '~/v4/social/internal-components/Lexical/plugins/AutoLinkPlugin'; +import { + editorStateToText, + getEditorConfig, + MentionData, + textToEditorState, +} from '~/v4/social/internal-components/Lexical/utils'; +import ReactDOM from 'react-dom'; +import { useMemberQueryByDisplayName } from '~/v4/social/hooks/useMemberQueryByDisplayName'; +import { useUserQueryByDisplayName } from '~/v4/core/hooks/collections/useUsersCollection'; +import { AutoLinkNode, LinkNode } from '@lexical/link'; +import useIntersectionObserver from '~/v4/core/hooks/useIntersectionObserver'; +import useCommunity from '~/v4/core/hooks/collections/useCommunity'; +import { MentionItem } from '~/v4/social/internal-components/Lexical/MentionItem'; interface PostTextFieldProps { - onChange: (data: CreatePostParams) => void; communityId?: string | null; - onChangeSnap?: number; + attachmentAmount?: number; mentionContainer: HTMLElement | null; dataValue: { data: { text: string }; @@ -37,86 +41,192 @@ interface PostTextFieldProps { }; mentionees?: Mentionees; }; + onChange: (data: { mentioned: Mentioned[]; mentionees: Mentionees; text: string }) => void; } -function editorStateToText(editor: LexicalEditor) { - const editorStateTextString: string[] = []; - const paragraphs = editor.getEditorState().toJSON().root.children as EditorStateJson[]; +const useSuggestions = (communityId?: string | null) => { + const [queryString, setQueryString] = useState<string | null>(null); - const mentioned: MetaData[] = []; - const mentionees: { - type: string; - userIds: string[]; - }[] = [{ type: 'user', userIds: [] }]; - let runningIndex = 0; + const { community, isLoading: isCommunityLoading } = useCommunity({ communityId }); - paragraphs.forEach((paragraph) => { - const children = paragraph.children; - const paragraphText: string[] = []; + const isSearchCommunityMembers = useMemo( + () => !!communityId && !isCommunityLoading && !community?.isPublic, + [communityId, isCommunityLoading, community], + ); - children.forEach((child: { type: string; text: string; userId: string }) => { - if (child.text) { - paragraphText.push(child.text); - } - if (child.type === 'mention') { - mentioned.push({ - index: runningIndex, - length: child.text.length, - type: 'user', - userId: child.userId, - }); - - mentionees.length && mentionees[0].userIds.push(child.userId); + const { + members, + hasMore: hasMoreMember, + isLoading: isLoadingMember, + loadMore: loadMoreMember, + } = useMemberQueryByDisplayName({ + communityId: communityId || '', + displayName: queryString || '', + limit: 10, + enabled: isSearchCommunityMembers, + }); + const { + users, + hasMore: hasMoreUser, + loadMore: loadMoreUser, + isLoading: isLoadingUser, + } = useUserQueryByDisplayName({ + displayName: queryString || '', + limit: 10, + enabled: !communityId, + }); + + const onQueryChange = (newQuery: string | null) => { + setQueryString(newQuery); + }; + + const suggestions = useMemo(() => { + if (!!communityId && isCommunityLoading) return []; + + if (isSearchCommunityMembers) { + return members.map(({ user, userId }) => ({ + userId: user?.userId || userId, + displayName: user?.displayName, + })); + } + + return users.map(({ displayName, userId }) => ({ + userId: userId, + displayName: displayName, + })); + }, [users, members, isSearchCommunityMembers, isCommunityLoading]); + + const hasMore = useMemo(() => { + if (communityId) { + return hasMoreMember; + } else { + return hasMoreUser; + } + }, [communityId, hasMoreMember, hasMoreUser]); + + const loadMore = () => { + if (isLoading || !hasMore) return; + if (communityId) { + loadMoreMember(); + } else { + loadMoreUser(); + } + }; + + const isLoading = useMemo(() => { + if (communityId) { + return isLoadingMember; + } else { + return isLoadingUser; + } + }, [isLoadingMember, isLoadingUser, communityId]); + + return { suggestions, queryString, onQueryChange, loadMore, hasMore, isLoading }; +}; + +const nodes = [AutoLinkNode, LinkNode, MentionNode] as Array<Klass<LexicalNode>>; + +export const PostTextField = ({ + onChange, + communityId, + mentionContainer, + dataValue, +}: PostTextFieldProps) => { + const [intersectionNode, setIntersectionNode] = useState<HTMLDivElement | null>(null); + + const editorConfig = getEditorConfig({ + namespace: 'PostTextField', + theme: { + link: styles.editorLink, + placeholder: styles.editorPlaceholder, + paragraph: styles.editorParagraph, + }, + nodes, + }); + + const { onQueryChange, suggestions, isLoading, loadMore } = useSuggestions(communityId); + + useIntersectionObserver({ + onIntersect: () => { + if (isLoading === false) { + loadMore(); } - runningIndex += child.text.length; - }); - runningIndex += 1; - editorStateTextString.push(paragraphText.join('')); + }, + node: intersectionNode, + options: { + threshold: 0.7, + }, }); - return { mentioned, text: editorStateTextString.join('\n'), mentionees }; -} -export const PostTextField = forwardRef<LexicalEditor, PostTextFieldProps>( - ({ onChange, communityId, onChangeSnap, mentionContainer, dataValue }) => { - const editorConfig = { - namespace: 'PostTextField', - theme: theme, - onError(error: Error) { - throw error; - }, - nodes: [MentionNode], - }; - return ( - <> - <LexicalComposer - initialConfig={{ - ...editorConfig, - ...(dataValue?.data.text - ? { editorState: JSON.stringify(textToEditorState(dataValue)) } - : {}), - }} - > - <div className={styles.editorContainer}> - <RichTextPlugin - contentEditable={<ContentEditable />} - placeholder={<div className={styles.editorPlaceholder}>What’s going on...</div>} - ErrorBoundary={LexicalErrorBoundary} - /> - <OnChangePlugin - onChange={(editorState, editor) => { - onChange(editorStateToText(editor)); - }} - /> - <HistoryPlugin /> - <AutoFocusPlugin /> - <MentionTextInputPlugin - communityId={communityId} - onChangeSnap={onChangeSnap} - mentionContainer={mentionContainer} - /> - </div> - </LexicalComposer> - </> - ); - }, -); + return ( + <> + <LexicalComposer + initialConfig={{ + ...editorConfig, + ...(dataValue?.data.text + ? { editorState: JSON.stringify(textToEditorState(dataValue)) } + : {}), + }} + > + <div className={styles.editorContainer}> + <RichTextPlugin + contentEditable={<ContentEditable />} + placeholder={<div className={styles.editorPlaceholder}>What’s going on...</div>} + ErrorBoundary={LexicalErrorBoundary} + /> + <OnChangePlugin + onChange={(_, editor) => { + onChange(editorStateToText(editor)); + }} + /> + <HistoryPlugin /> + <AutoFocusPlugin /> + <LinkPlugin /> + <AutoLinkPlugin /> + <MentionPlugin<MentionData, MentionNode<MentionData>> + suggestions={suggestions} + getSuggestionId={(suggestion) => suggestion.userId} + onQueryChange={onQueryChange} + $createNode={(data) => + $createMentionNode({ + text: `@${data?.displayName || ''}`, + data, + }) + } + menuRenderFn={( + _, + { options, selectedIndex, setHighlightedIndex, selectOptionAndCleanUp }, + ) => { + if (!mentionContainer || options.length === 0) { + return null; + } + return ReactDOM.createPortal( + <> + {options.map((option, i: number) => { + return ( + <MentionItem + isSelected={selectedIndex === i} + onClick={() => { + setHighlightedIndex(i); + selectOptionAndCleanUp(option); + }} + onMouseEnter={() => { + setHighlightedIndex(i); + }} + key={option.key} + option={option} + /> + ); + })} + <div ref={(node) => setIntersectionNode(node)} style={{ height: '4px' }} /> + </>, + mentionContainer, + ); + }} + commandPriority={COMMAND_PRIORITY_HIGH} + /> + </div> + </LexicalComposer> + </> + ); +}; diff --git a/src/v4/social/elements/VideoButton/VideoButton.tsx b/src/v4/social/elements/VideoButton/VideoButton.tsx index a6a7f56d4..44cb998b4 100644 --- a/src/v4/social/elements/VideoButton/VideoButton.tsx +++ b/src/v4/social/elements/VideoButton/VideoButton.tsx @@ -29,14 +29,14 @@ const VideoButtonSvg = (props: React.SVGProps<SVGSVGElement>) => { d="M12 21C16.9706 21 21 16.9706 21 12C21 7.02944 16.9706 3 12 3C7.02944 3 3 7.02944 3 12C3 16.9706 7.02944 21 12 21Z" stroke={props.stroke} strokeWidth="1.3" - stroke-miterlimit="10" + strokeMiterlimit="10" /> <path d="M16 12L10 8V16L16 12Z" stroke={props.stroke} strokeWidth="1.3" - stroke-linecap="round" - stroke-linejoin="round" + strokeLinecap="round" + strokeLinejoin="round" /> </svg> ); diff --git a/src/v4/social/hooks/useMemberQueryByDisplayName.ts b/src/v4/social/hooks/useMemberQueryByDisplayName.ts index bbbbb3191..28a89bf4f 100644 --- a/src/v4/social/hooks/useMemberQueryByDisplayName.ts +++ b/src/v4/social/hooks/useMemberQueryByDisplayName.ts @@ -40,6 +40,7 @@ export const useMemberQueryByDisplayName = ({ search: displayName, limit, sortBy: 'displayName', + includeDeleted: false, }, (response) => { setHasMore(response.hasNextPage || false); diff --git a/src/v4/social/internal-components/CreatePost/CreatePost.module.css b/src/v4/social/internal-components/CreatePost/CreatePost.module.css index 5102fe5b0..2aabcba38 100644 --- a/src/v4/social/internal-components/CreatePost/CreatePost.module.css +++ b/src/v4/social/internal-components/CreatePost/CreatePost.module.css @@ -68,3 +68,42 @@ left: 0; outline: none; } + +.mentionTextInput_item { + position: absolute; + left: 0; + width: 100%; + max-height: 12.5rem; + overflow-y: scroll; + background-color: var(--asc-color-background-default); + bottom: var(--asc-mention-bottom, 0); +} + +.drawerContentContainer { + position: absolute; + box-shadow: var(--asc-box-shadow-04); + border-top-left-radius: 1.25rem; + border-top-right-radius: 1.25rem; + display: block; + align-items: center; + justify-content: space-between; + width: 100%; + background-color: var(--asc-color-background-default); +} + +.drawerContentContainer[data-show-detail-media-attachment='false'] { + padding: 0.5rem 1rem; + bottom: -0.4rem; + margin-top: 0.75rem; +} + +.drawerContentContainer[data-show-detail-media-attachment='true'] { + bottom: -0.9rem; + margin-top: -1rem; + padding-bottom: 2rem; + padding-top: 0.75rem; +} + +.mentionTextInput_item::-webkit-scrollbar { + display: none; +} diff --git a/src/v4/social/internal-components/CreatePost/CreatePost.tsx b/src/v4/social/internal-components/CreatePost/CreatePost.tsx index 001ba6e9a..9984902b4 100644 --- a/src/v4/social/internal-components/CreatePost/CreatePost.tsx +++ b/src/v4/social/internal-components/CreatePost/CreatePost.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { RefObject, useEffect, useRef, useState } from 'react'; import styles from './CreatePost.module.css'; import { PostRepository } from '@amityco/ts-sdk'; import { useMutation } from '@tanstack/react-query'; @@ -27,6 +27,30 @@ import { MediaAttachment } from '~/v4/social/components/MediaAttachment'; import { DetailedMediaAttachment } from '~/v4/social/components/DetailedMediaAttachment'; import { CloseButton } from '~/v4/social/elements/CloseButton/CloseButton'; import { Notification } from '~/v4/core/components/Notification'; +import { Mentioned, Mentionees } from '~/v4/helpers/utils'; + +const useResizeObserver = ({ ref }: { ref: RefObject<HTMLDivElement> }) => { + const [height, setHeight] = useState<number | undefined>(undefined); + + useEffect(() => { + if (ref.current == null) return; + const observer = new ResizeObserver((entries) => { + for (const entry of entries) { + setHeight(entry.target.clientHeight); + } + }); + + if (ref.current) { + observer.observe(ref.current); + } + + return () => { + observer.disconnect(); + }; + }, [ref.current]); + + return height; +}; export function CreatePost({ community, targetType, targetId }: AmityPostComposerCreateOptions) { const pageId = 'post_composer_page'; @@ -40,14 +64,18 @@ export function CreatePost({ community, targetType, targetId }: AmityPostCompose const HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_2 = '11rem'; //Show 2 menus const HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_3 = '14.5rem'; //Not including file button - const editorRef = useRef<LexicalEditor | null>(null); const { AmityPostComposerPageBehavior } = usePageBehavior(); const { confirm } = useConfirmContext(); - const [snap, setSnap] = useState<number | string | null>(HEIGHT_MEDIA_ATTACHMENT_MENU); + const [snap, setSnap] = useState<string>(HEIGHT_MEDIA_ATTACHMENT_MENU); const [isShowBottomMenu] = useState<boolean>(true); const drawerRef = useRef<HTMLDivElement>(null); + const drawerContentRef = useRef<HTMLDivElement>(null); const { prependItem } = useGlobalFeedContext(); + const drawerHeight = useResizeObserver({ ref: drawerContentRef }); + + const { handleSubmit } = useForm(); + const [textValue, setTextValue] = useState<CreatePostParams>({ text: '', mentioned: [], @@ -104,8 +132,6 @@ export function CreatePost({ community, targetType, targetId }: AmityPostCompose const { mutateAsync: mutateCreatePostAsync, isPending, isError } = useMutateCreatePost(); - const { handleSubmit } = useForm(); - const onSubmit = () => { const attachmentsImage = postImages.map((file) => ({ type: 'image', @@ -129,11 +155,16 @@ export function CreatePost({ community, targetType, targetId }: AmityPostCompose }); }; - const onChange = (val: CreatePostParams) => { - setTextValue(val); + const onChange = (val: { mentioned: Mentioned[]; mentionees: Mentionees; text: string }) => { + setTextValue((prev) => ({ + ...prev, + mentioned: val.mentioned, + text: val.text, + mentionees: val.mentionees, + })); }; - const handleSnapChange = (newSnap: string | number | null) => { + const handleSnapChange = (newSnap: string) => { if (snap === HEIGHT_MEDIA_ATTACHMENT_MENU && newSnap === '0px') { return; } @@ -252,6 +283,11 @@ export function CreatePost({ community, targetType, targetId }: AmityPostCompose } }; + const isShowDetailMediaAttachmentMenu = + snap == HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_1 || + snap == HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_2 || + snap == HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_3; + return ( <div className={styles.createPost} style={themeStyles}> <form @@ -269,16 +305,8 @@ export function CreatePost({ community, targetType, targetId }: AmityPostCompose /> </div> <PostTextField - ref={editorRef} onChange={onChange} communityId={targetId} - onChangeSnap={ - snap == HEIGHT_MEDIA_ATTACHMENT_MENU - ? 0 - : snap == HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_3 - ? 2 - : 1 - } mentionContainer={mentionRef.current} dataValue={{ data: { text: textValue.text }, @@ -296,7 +324,6 @@ export function CreatePost({ community, targetType, targetId }: AmityPostCompose onError={setIsErrorUpload} isErrorUpload={isErrorUpload} /> - <div ref={mentionRef} /> <VideoThumbnail files={incomingVideos} uploadedFiles={postVideos} @@ -311,7 +338,15 @@ export function CreatePost({ community, targetType, targetId }: AmityPostCompose videoThumbnail={videoThumbnail} removeThumbnail={handleRemoveThumbnail} /> - <div ref={mentionRef} /> + <div + ref={mentionRef} + style={ + { + '--asc-mention-bottom': `${drawerHeight ?? 0}px`, + } as React.CSSProperties + } + className={styles.mentionTextInput_item} + /> </form> <div ref={drawerRef}></div> {drawerRef.current @@ -324,7 +359,9 @@ export function CreatePost({ community, targetType, targetId }: AmityPostCompose HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_3, ]} activeSnapPoint={snap} - setActiveSnapPoint={handleSnapChange} + setActiveSnapPoint={(newSnapPoint) => { + typeof newSnapPoint === 'string' && handleSnapChange(newSnapPoint); + }} open={isShowBottomMenu} modal={false} > @@ -350,27 +387,31 @@ export function CreatePost({ community, targetType, targetId }: AmityPostCompose /> )} </div> - {snap == HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_1 || - snap == HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_2 || - snap == HEIGHT_DETAIL_MEDIA_ATTACHMENT__MENU_3 ? ( - <DetailedMediaAttachment - pageId={pageId} - isVisibleCamera={isVisibleCamera} - isVisibleImage={isVisibleImage} - isVisibleVideo={isVisibleVideo} - onVideoFileChange={handleVideoFileChange} - onImageFileChange={handleImageFileChange} - /> - ) : ( - <MediaAttachment - pageId={pageId} - isVisibleCamera={isVisibleCamera} - isVisibleImage={isVisibleImage} - isVisibleVideo={isVisibleVideo} - onVideoFileChange={handleVideoFileChange} - onImageFileChange={handleImageFileChange} - /> - )} + <div + ref={drawerContentRef} + className={styles.drawerContentContainer} + data-show-detail-media-attachment={isShowDetailMediaAttachmentMenu} + > + {isShowDetailMediaAttachmentMenu ? ( + <DetailedMediaAttachment + pageId={pageId} + isVisibleCamera={isVisibleCamera} + isVisibleImage={isVisibleImage} + isVisibleVideo={isVisibleVideo} + onVideoFileChange={handleVideoFileChange} + onImageFileChange={handleImageFileChange} + /> + ) : ( + <MediaAttachment + pageId={pageId} + isVisibleCamera={isVisibleCamera} + isVisibleImage={isVisibleImage} + isVisibleVideo={isVisibleVideo} + onVideoFileChange={handleVideoFileChange} + onImageFileChange={handleImageFileChange} + /> + )} + </div> </Drawer.Content> </Drawer.Portal> </Drawer.Root>, diff --git a/src/v4/social/internal-components/EditPost/EditPost.module.css b/src/v4/social/internal-components/EditPost/EditPost.module.css index 3e4a12fb9..a89b5dec8 100644 --- a/src/v4/social/internal-components/EditPost/EditPost.module.css +++ b/src/v4/social/internal-components/EditPost/EditPost.module.css @@ -60,3 +60,17 @@ left: 0; outline: none; } + +.mentionTextInput_item { + position: absolute; + left: 0; + width: 100%; + max-height: 12.5rem; + overflow-y: scroll; + background-color: var(--asc-color-background-default); + bottom: 0; +} + +.mentionTextInput_item::-webkit-scrollbar { + display: none; +} diff --git a/src/v4/social/internal-components/EditPost/EditPost.tsx b/src/v4/social/internal-components/EditPost/EditPost.tsx index 87824c29c..eedd0bfef 100644 --- a/src/v4/social/internal-components/EditPost/EditPost.tsx +++ b/src/v4/social/internal-components/EditPost/EditPost.tsx @@ -22,6 +22,7 @@ import { Thumbnail } from './Thumbnail'; import { useGlobalFeedContext } from '~/v4/social/providers/GlobalFeedProvider'; import { arraysContainSameElements } from '~/v4/social/utils/arraysContainSameElements'; import { useNavigation } from '~/v4/core/providers/NavigationProvider'; +import { Mentioned, Mentionees } from '~/v4/helpers/utils'; export function EditPost({ post }: AmityPostComposerEditOptions) { const pageId = 'post_composer_page'; @@ -29,20 +30,14 @@ export function EditPost({ post }: AmityPostComposerEditOptions) { pageId, }); - const editorRef = useRef<LexicalEditor | null>(null); const { onBack } = useNavigation(); const { confirm } = useConfirmContext(); const { updateItem } = useGlobalFeedContext(); const [textValue, setTextValue] = useState<CreatePostParams>({ - text: '', - mentioned: [], - mentionees: [ - { - type: 'user', - userIds: [''], - }, - ], + text: post.data.text ?? '', + mentioned: post.metadata?.mentioned || [], + mentionees: post.mentionees, attachments: [ { fileId: '', @@ -123,8 +118,13 @@ export function EditPost({ post }: AmityPostComposerEditOptions) { } }; - const onChange = (val: CreatePostParams) => { - setTextValue(val); + const onChange = (val: { mentioned: Mentioned[]; mentionees: Mentionees; text: string }) => { + setTextValue((prev) => ({ + ...prev, + mentioned: val.mentioned, + text: val.text, + mentionees: val.mentionees, + })); }; const onClickClose = () => { @@ -161,7 +161,7 @@ export function EditPost({ post }: AmityPostComposerEditOptions) { /> </div> <PostTextField - ref={editorRef} + communityId={post.targetType === 'community' ? post.targetId : undefined} onChange={onChange} mentionContainer={mentionRef.current} dataValue={{ @@ -176,7 +176,7 @@ export function EditPost({ post }: AmityPostComposerEditOptions) { <Thumbnail postMedia={postImages} onRemove={handleRemoveThumbnailImage} /> <Thumbnail postMedia={postVideos} onRemove={handleRemoveThumbnailVideo} /> - <div ref={mentionRef} /> + <div ref={mentionRef} className={styles.mentionTextInput_item} /> <div className={styles.editPost__notiWrap}> {isPending && ( <Notification diff --git a/src/v4/social/internal-components/Lexical/AllMentionItem.module.css b/src/v4/social/internal-components/Lexical/AllMentionItem.module.css new file mode 100644 index 000000000..a060ebb2a --- /dev/null +++ b/src/v4/social/internal-components/Lexical/AllMentionItem.module.css @@ -0,0 +1,49 @@ +.userMentionItem__item, +.allMentionItem__item { + width: 100%; + display: flex; + align-items: center; + padding: 0.5rem 1rem; + background-color: var(--asc-color-background-default); + color: var(--asc-color-base-default); +} + +.userMentionItem__item[data-is-selected='true'], +.allMentionItem__item[data-is-selected='true'] { + background-color: var(--asc-color-base-shade4); +} + +.userMentionItem__item:focus, +.allMentionItem__item:focus { + background-color: var(--asc-color-base-shade4); +} + +.userMentionItem__avatar { + width: 2.5rem; + height: 2.5rem; +} + +.allMentionItem__atSign { + width: 2rem; + height: 2rem; + background-color: var(--asc-color-primary-default); + color: var(--asc-color-white); + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + text-align: center; + line-height: 1.2rem; + font-size: 1rem; +} + +.userMentionItem__displayName, +.allMentionItem__displayName { + margin-left: 0.5rem; + font-size: 1rem; + display: block; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + color: var(--asc-color-base-default); +} diff --git a/src/v4/social/internal-components/Lexical/AllMentionItem.tsx b/src/v4/social/internal-components/Lexical/AllMentionItem.tsx new file mode 100644 index 000000000..8980aeadd --- /dev/null +++ b/src/v4/social/internal-components/Lexical/AllMentionItem.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { MentionTypeaheadOption } from './plugins/MentionPlugin'; +import { MentionData } from './utils'; + +import styles from './MentionItem.module.css'; + +interface MentionItemProps { + isSelected: boolean; + onClick: () => void; + onMouseEnter: () => void; + option: MentionTypeaheadOption<MentionData>; +} + +export function AllMentionItem({ option, isSelected, onClick, onMouseEnter }: MentionItemProps) { + return ( + <li + key={option.key} + tabIndex={-1} + data-is-selected={isSelected} + className={styles.allMentionItem__item} + ref={option.setRefElement} + role="option" + aria-selected={isSelected} + onMouseEnter={onMouseEnter} + onClick={onClick} + > + <div className={styles.allMentionItem__atSign}>@</div> + <p className={styles.allMentionItem__displayName}>{option.data.displayName}</p> + </li> + ); +} diff --git a/src/v4/social/internal-components/Lexical/MentionItem.module.css b/src/v4/social/internal-components/Lexical/MentionItem.module.css new file mode 100644 index 000000000..a060ebb2a --- /dev/null +++ b/src/v4/social/internal-components/Lexical/MentionItem.module.css @@ -0,0 +1,49 @@ +.userMentionItem__item, +.allMentionItem__item { + width: 100%; + display: flex; + align-items: center; + padding: 0.5rem 1rem; + background-color: var(--asc-color-background-default); + color: var(--asc-color-base-default); +} + +.userMentionItem__item[data-is-selected='true'], +.allMentionItem__item[data-is-selected='true'] { + background-color: var(--asc-color-base-shade4); +} + +.userMentionItem__item:focus, +.allMentionItem__item:focus { + background-color: var(--asc-color-base-shade4); +} + +.userMentionItem__avatar { + width: 2.5rem; + height: 2.5rem; +} + +.allMentionItem__atSign { + width: 2rem; + height: 2rem; + background-color: var(--asc-color-primary-default); + color: var(--asc-color-white); + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + text-align: center; + line-height: 1.2rem; + font-size: 1rem; +} + +.userMentionItem__displayName, +.allMentionItem__displayName { + margin-left: 0.5rem; + font-size: 1rem; + display: block; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + color: var(--asc-color-base-default); +} diff --git a/src/v4/social/internal-components/Lexical/MentionItem.tsx b/src/v4/social/internal-components/Lexical/MentionItem.tsx new file mode 100644 index 000000000..98059c869 --- /dev/null +++ b/src/v4/social/internal-components/Lexical/MentionItem.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { UserAvatar } from '~/v4/social/internal-components/UserAvatar/UserAvatar'; +import { MentionTypeaheadOption } from './plugins/MentionPlugin'; +import { MentionData } from './utils'; + +import styles from './MentionItem.module.css'; + +interface MentionItemProps { + isSelected: boolean; + onClick: () => void; + onMouseEnter: () => void; + option: MentionTypeaheadOption<MentionData>; +} + +export function MentionItem({ option, isSelected, onClick, onMouseEnter }: MentionItemProps) { + return ( + <li + key={option.key} + tabIndex={-1} + data-is-selected={isSelected} + className={styles.userMentionItem__item} + ref={option.setRefElement} + role="option" + aria-selected={isSelected} + onMouseEnter={onMouseEnter} + onClick={onClick} + > + <div> + <UserAvatar className={styles.userMentionItem__avatar} userId={option.data.userId} /> + </div> + <p className={styles.userMentionItem__displayName}>{option.data.displayName}</p> + </li> + ); +} diff --git a/src/v4/social/internal-components/Lexical/utils.ts b/src/v4/social/internal-components/Lexical/utils.ts index 257b9bf4e..8c37f7b4d 100644 --- a/src/v4/social/internal-components/Lexical/utils.ts +++ b/src/v4/social/internal-components/Lexical/utils.ts @@ -100,56 +100,48 @@ export function textToEditorState(value: { const textArray = value.data.text.split('\n'); - const mentions = value.metadata?.mentioned; + const mentions = value.metadata?.mentioned || []; - let start = 0; - let stop = -1; - let mentionRunningIndex = 0; - - for (let i = 0; i < textArray.length; i++) { - start = stop + 1; - stop = start + textArray[i].length; + let mentionIndex = 0; + let globalIndex = 0; + for (let i = 0; i < textArray.length; i += 1) { const paragraph = createParagraphNode(); - - if (Array.isArray(mentions) && mentions?.length > 0) { - let runningIndex = 0; - - while (runningIndex < textArray[i].length) { - if (mentionRunningIndex >= mentions.length) { - paragraph.children.push(createSerializeTextNode(textArray[i].slice(runningIndex))); - runningIndex = textArray[i].length; - break; - } - - if (mentions[mentionRunningIndex].index >= stop) { - paragraph.children.push(createSerializeTextNode(textArray[i])); - runningIndex = textArray[i].length; - } else { - const text = textArray[i].slice( - runningIndex, - runningIndex + mentions[mentionRunningIndex]?.index - start, - ); - - if (text) { - paragraph.children.push(createSerializeTextNode(text)); - } - - paragraph.children.push(createSerializeMentionNode(mentions[mentionRunningIndex])); - - runningIndex += - mentions[mentionRunningIndex].index + mentions[mentionRunningIndex].length - start; - - mentionRunningIndex++; + let runningIndex = 0; + + while (runningIndex < textArray[i].length) { + if (mentionIndex < mentions.length && mentions[mentionIndex].index === globalIndex) { + paragraph.children.push(createSerializeMentionNode(mentions[mentionIndex])); + runningIndex += mentions[mentionIndex].length; + globalIndex += mentions[mentionIndex].length; + mentionIndex += 1; + } else { + const nextMentionIndex = + mentionIndex < mentions.length + ? mentions[mentionIndex].index + : globalIndex + textArray[i].length; + const textSegment = textArray[i].slice( + runningIndex, + nextMentionIndex - globalIndex + runningIndex, + ); + if (textSegment) { + paragraph.children.push(createSerializeTextNode(textSegment)); } + runningIndex += textSegment.length; + globalIndex += textSegment.length; } } - if (!mentions || mentions?.length === 0) { - const textNode = createSerializeTextNode(textArray[i]); - paragraph.children.push(textNode); + if (runningIndex < textArray[i].length) { + const textSegment = textArray[i].slice(runningIndex); + if (textSegment) { + paragraph.children.push(createSerializeTextNode(textSegment)); + } + globalIndex += textSegment.length; } + globalIndex += 1; + rootNode.children.push(paragraph); } diff --git a/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx b/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx index 48c681b73..0cd348702 100644 --- a/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx +++ b/src/v4/social/internal-components/MentionTextInput/MentionTextInput.tsx @@ -112,11 +112,11 @@ export class MentionTypeaheadOption extends MenuOption { export const MentionTextInputPlugin = ({ communityId, - onChangeSnap, + attachmentAmount, mentionContainer, }: { communityId?: string | null; - onChangeSnap?: number; + attachmentAmount?: number; mentionContainer: HTMLElement | null; }) => { return ( @@ -124,7 +124,7 @@ export const MentionTextInputPlugin = ({ <Mention anchorElement={mentionContainer} communityId={communityId} - onChangeSnap={onChangeSnap} + attachmentAmount={attachmentAmount} /> </div> ); @@ -133,17 +133,17 @@ export const MentionTextInputPlugin = ({ function Mention({ anchorElement, communityId, - onChangeSnap, + attachmentAmount, }: { anchorElement: HTMLElement | null; communityId?: string | null; - onChangeSnap?: number; + attachmentAmount?: number; }) { const [editor] = useLexicalComposerContext(); const [queryString, setQueryString] = useState<string | null>(null); let options: MentionTypeaheadOption[] = []; - const intersectionRef = useRef<HTMLDivElement>(null); + const [intersectionNode, setIntersectionNode] = useState<HTMLDivElement | null>(null); const { members, hasMore: hasMoreMember, @@ -171,7 +171,7 @@ function Mention({ } } }, - ref: intersectionRef, + node: intersectionNode, }); const community = useCommunity({ communityId, shouldCall: !!communityId }).community; @@ -228,7 +228,10 @@ function Mention({ anchorElement && options.length > 0 ? ReactDOM.createPortal( <> - <div data-media-attachement={onChangeSnap} className={styles.mentionTextInput_item}> + <div + data-media-attachment={attachmentAmount} + className={styles.mentionTextInput_item} + > {options.map((option, i: number) => ( <CommunityMember isSelected={selectedIndex === i} @@ -244,7 +247,7 @@ function Mention({ /> ))} </div> - <div ref={intersectionRef} /> + <div ref={(node) => setIntersectionNode(node)} /> </>, anchorElement, ) diff --git a/src/v4/social/internal-components/StoryAd/UIStoryAd.tsx b/src/v4/social/internal-components/StoryAd/UIStoryAd.tsx index 86b194024..ed841bf02 100644 --- a/src/v4/social/internal-components/StoryAd/UIStoryAd.tsx +++ b/src/v4/social/internal-components/StoryAd/UIStoryAd.tsx @@ -143,7 +143,6 @@ export const UIStoryAd = ({ <Button className={styles.infoIcon__button} onPress={() => { - console.log('openAdvertisementInfo'); openAdvertisementInfo(); }} > diff --git a/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx b/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx index 7d7b94109..e76930732 100644 --- a/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx +++ b/src/v4/social/pages/PostComposerPage/PostComposerPage.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Mentioned } from '~/v4/helpers/utils'; +import { Mentioned, Mentionees } from '~/v4/helpers/utils'; import { CreatePost } from '~/v4/social/internal-components/CreatePost'; import { EditPost } from '~/v4/social/internal-components/EditPost'; @@ -52,10 +52,7 @@ export function PostComposerPage(props: PostComposerPageProps) { export type CreatePostParams = { text: string; mentioned: Mentioned[]; - mentionees: { - type: string; - userIds: string[]; - }[]; + mentionees: Mentionees; attachments?: { fileId: string; type: string; diff --git a/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.tsx b/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.tsx index 7019a8f36..e9614d730 100644 --- a/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.tsx +++ b/src/v4/social/pages/SelectPostTargetPage/SelectPostTargetPage.tsx @@ -1,4 +1,4 @@ -import React, { useRef } from 'react'; +import React, { useRef, useState } from 'react'; import styles from './SelectPostTargetPage.module.css'; import { useAmityPage } from '~/v4/core/hooks/uikit'; import { CloseButton } from '~/v4/social/elements/CloseButton/CloseButton'; @@ -28,7 +28,7 @@ export function SelectPostTargetPage() { queryParams: { sortBy: 'displayName', limit: 20, membership: 'member' }, }); const { AmityPostTargetSelectionPage } = usePageBehavior(); - const intersectionRef = useRef<HTMLDivElement>(null); + const [intersectionNode, setIntersectionNode] = useState<HTMLDivElement | null>(null); const { currentUserId } = useSDK(); const { user } = useUser(currentUserId); useIntersectionObserver({ @@ -37,7 +37,7 @@ export function SelectPostTargetPage() { loadMore(); } }, - ref: intersectionRef, + node: intersectionNode, }); const renderCommunity = communities.map((community) => { @@ -94,7 +94,7 @@ export function SelectPostTargetPage() { <div className={styles.selectPostTargetPage__line} /> <div className={styles.selectPostTargetPage__myCommunities}>My Communities</div> {renderCommunity} - <div ref={intersectionRef} /> + <div ref={(node) => setIntersectionNode(node)} /> </div> ); } diff --git a/src/v4/social/pages/StoryTargetSelectionPage/StoryTargetSelectionPage.tsx b/src/v4/social/pages/StoryTargetSelectionPage/StoryTargetSelectionPage.tsx index 6cb517036..488f7113e 100644 --- a/src/v4/social/pages/StoryTargetSelectionPage/StoryTargetSelectionPage.tsx +++ b/src/v4/social/pages/StoryTargetSelectionPage/StoryTargetSelectionPage.tsx @@ -27,7 +27,7 @@ export function StoryTargetSelectionPage() { }); const { AmityStoryTargetSelectionPage } = usePageBehavior(); const { file, setFile } = useStoryContext(); - const intersectionRef = useRef<HTMLDivElement>(null); + const [intersectionNode, setIntersectionNode] = useState<HTMLDivElement | null>(null); const [selectedCommunityId, setSelectedCommunityId] = useState<string | null>(null); const handleOnClickCommunity = (communityId: string) => { @@ -65,7 +65,7 @@ export function StoryTargetSelectionPage() { loadMore(); } }, - ref: intersectionRef, + node: intersectionNode, }); useEffect(() => { @@ -98,7 +98,7 @@ export function StoryTargetSelectionPage() { <div className={styles.selectStoryTargetPage__myCommunities__container}> {renderCommunity} </div> - <div ref={intersectionRef} /> + <div ref={(node) => setIntersectionNode(node)} /> </div> ); } diff --git a/src/v4/social/utils/textToEditorState.ts b/src/v4/social/utils/textToEditorState.ts deleted file mode 100644 index 93c3b3740..000000000 --- a/src/v4/social/utils/textToEditorState.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { SerializedParagraphNode, SerializedRootNode, SerializedTextNode } from 'lexical'; -import { Mentioned, Mentionees } from '~/v4/helpers/utils'; -import { SerializedMentionNode } from '~/v4/social/internal-components/MentionTextInput/MentionNodes'; - -function createRootNode(): SerializedRootNode<SerializedParagraphNode> { - return { - children: [], - direction: 'ltr', - format: '', - indent: 0, - type: 'root', - version: 1, - }; -} -function createParagraphNode(): SerializedParagraphNode { - return { - children: [], - direction: 'ltr', - format: '', - indent: 0, - type: 'paragraph', - version: 1, - textFormat: 0, - }; -} - -function createSerializeTextNode(text: string): SerializedTextNode { - return { - detail: 0, - format: 0, - mode: 'normal', - style: '', - text, - type: 'text', - version: 1, - }; -} - -function createSerializeMentionNode(mention: Mentioned): SerializedMentionNode { - return { - detail: 0, - format: 0, - mode: 'normal', - style: '', - text: ('@' + mention.userId) as string, - type: 'mention', - version: 1, - mentionName: mention.userId as string, - displayName: mention.userId as string, - userId: mention.userId as string, - userInternalId: mention.userId as string, - userPublicId: mention.userId as string, - }; -} - -export function textToEditorState(value: { - data: { text: string }; - metadata?: { - mentioned?: Mentioned[]; - }; - mentionees?: Mentionees; -}) { - const rootNode = createRootNode(); - const textArray = value.data.text.split('\n'); - const mentions = value.metadata?.mentioned || []; - let mentionIndex = 0; - let globalIndex = 0; - - for (let i = 0; i < textArray.length; i++) { - const paragraph = createParagraphNode(); - let runningIndex = 0; - - while (runningIndex < textArray[i].length) { - if (mentionIndex < mentions.length && mentions[mentionIndex].index === globalIndex) { - paragraph.children.push(createSerializeMentionNode(mentions[mentionIndex])); - runningIndex += mentions[mentionIndex].length; - globalIndex += mentions[mentionIndex].length; - mentionIndex++; - } else { - const nextMentionIndex = - mentionIndex < mentions.length - ? mentions[mentionIndex].index - : globalIndex + textArray[i].length; - const textSegment = textArray[i].slice( - runningIndex, - nextMentionIndex - globalIndex + runningIndex, - ); - if (textSegment) { - paragraph.children.push(createSerializeTextNode(textSegment)); - } - runningIndex += textSegment.length; - globalIndex += textSegment.length; - } - } - - if (runningIndex < textArray[i].length) { - const textSegment = textArray[i].slice(runningIndex); - if (textSegment) { - paragraph.children.push(createSerializeTextNode(textSegment)); - } - globalIndex += textSegment.length; - } - - globalIndex += 1; - - rootNode.children.push(paragraph); - } - - return { root: rootNode }; -} From b1e278584c16f627f4040579550f886241d62bc7 Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Thu, 29 Aug 2024 19:33:40 +0700 Subject: [PATCH 295/300] fix: condition show story (#631) --- src/v4/social/components/StoryTab/StoryTabCommunity.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/v4/social/components/StoryTab/StoryTabCommunity.tsx b/src/v4/social/components/StoryTab/StoryTabCommunity.tsx index 38ad2fbe2..bcde21eeb 100644 --- a/src/v4/social/components/StoryTab/StoryTabCommunity.tsx +++ b/src/v4/social/components/StoryTab/StoryTabCommunity.tsx @@ -6,7 +6,7 @@ import clsx from 'clsx'; import { useGetActiveStoriesByTarget } from '~/v4/social/hooks/useGetActiveStories'; import useSDK from '~/v4/core/hooks/useSDK'; import { useUser } from '~/v4/core/hooks/objects/useUser'; -import { isAdmin, isModerator } from '~/v4/utils/permissions'; +import { isAdmin } from '~/v4/utils/permissions'; import { checkStoryPermission } from '~/v4/social/utils'; import { useCommunityInfo } from '~/v4/social/hooks/useCommunityInfo'; import { CreateNewStoryButton } from '~/v4/social/elements/CreateNewStoryButton'; @@ -78,7 +78,7 @@ export const StoryTabCommunityFeed: React.FC<StoryTabCommunityFeedProps> = ({ if (isExcluded) return null; - if (!hasStories && !hasStoryPermission && !community?.isJoined) return null; + if (!hasStories && !hasStoryPermission) return null; return ( <div From cb345f7264a21ae7d38175314dd14dcef999ebc7 Mon Sep 17 00:00:00 2001 From: ChayanitBm <chayanit@amity.co> Date: Thu, 29 Aug 2024 19:33:51 +0700 Subject: [PATCH 296/300] fix: ASC-24623 - add redirect user feed v3[Repeat PR to DEV] (#632) * fix: add redirect user feed * fix: remove code not use --- src/v4/core/providers/NavigationProvider.tsx | 2 +- src/v4/core/providers/PageBehaviorProvider.tsx | 11 +++++++++++ .../components/UserSearchResult/UserSearchItem.tsx | 8 +++++--- .../components/UserSearchResult/UserSearchResult.tsx | 10 +++++++++- 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/v4/core/providers/NavigationProvider.tsx b/src/v4/core/providers/NavigationProvider.tsx index f9748acf5..3608cdcfd 100644 --- a/src/v4/core/providers/NavigationProvider.tsx +++ b/src/v4/core/providers/NavigationProvider.tsx @@ -500,7 +500,7 @@ export default function NavigationProvider({ const goToUserProfilePage = useCallback( (userId) => { const next = { - type: PageTypes.UserProfilePage, + type: PageTypes.UserFeed, context: { userId, }, diff --git a/src/v4/core/providers/PageBehaviorProvider.tsx b/src/v4/core/providers/PageBehaviorProvider.tsx index 49190d0ef..d9921ea59 100644 --- a/src/v4/core/providers/PageBehaviorProvider.tsx +++ b/src/v4/core/providers/PageBehaviorProvider.tsx @@ -32,6 +32,9 @@ export interface PageBehavior { AmityCommunitySearchResultComponentBehavior?: { goToCommunityProfilePage?: (context: { communityId: string }) => void; }; + AmityUserSearchResultComponentBehavior?: { + goToUserProfilePage?: (context: { userId: string }) => void; + }; AmityCreatePostMenuComponentBehavior?: { goToSelectPostTargetPage?(): void; goToStoryTargetSelectionPage?(): void; @@ -183,6 +186,14 @@ export const PageBehaviorProvider: React.FC<PageBehaviorProviderProps> = ({ goToCommunityProfilePage(context.communityId); }, }, + AmityUserSearchResultComponentBehavior: { + goToUserProfilePage: (context: { userId: string }) => { + if (pageBehavior?.AmityUserSearchResultComponentBehavior?.goToUserProfilePage) { + return pageBehavior.AmityUserSearchResultComponentBehavior.goToUserProfilePage(context); + } + goToUserProfilePage(context.userId); + }, + }, AmityCreatePostMenuComponentBehavior: { goToSelectPostTargetPage() { if (pageBehavior?.AmityCreatePostMenuComponentBehavior?.goToSelectPostTargetPage) { diff --git a/src/v4/social/components/UserSearchResult/UserSearchItem.tsx b/src/v4/social/components/UserSearchResult/UserSearchItem.tsx index 70572f40c..a4c39aede 100644 --- a/src/v4/social/components/UserSearchResult/UserSearchItem.tsx +++ b/src/v4/social/components/UserSearchResult/UserSearchItem.tsx @@ -2,14 +2,16 @@ import React from 'react'; import { UserAvatar } from '~/v4/social/internal-components/UserAvatar/UserAvatar'; import { Typography } from '~/v4/core/components'; import styles from './UserSearchItem.module.css'; +import { Button } from '~/v4/core/natives/Button'; interface UserSearchItemProps { user: Amity.User; + onClick?: () => void; } -export const UserSearchItem = ({ user }: UserSearchItemProps) => { +export const UserSearchItem = ({ user, onClick }: UserSearchItemProps) => { return ( - <div key={user.userId} className={styles.userItem}> + <Button key={user.userId} className={styles.userItem} onPress={onClick}> <div className={styles.userItem__leftPane}> <UserAvatar userId={user.userId} className={styles.userItem__avatar} /> </div> @@ -20,6 +22,6 @@ export const UserSearchItem = ({ user }: UserSearchItemProps) => { </Typography.BodyBold> </div> </div> - </div> + </Button> ); }; diff --git a/src/v4/social/components/UserSearchResult/UserSearchResult.tsx b/src/v4/social/components/UserSearchResult/UserSearchResult.tsx index 120460b3e..40ef53d7c 100644 --- a/src/v4/social/components/UserSearchResult/UserSearchResult.tsx +++ b/src/v4/social/components/UserSearchResult/UserSearchResult.tsx @@ -4,6 +4,7 @@ import useIntersectionObserver from '~/v4/core/hooks/useIntersectionObserver'; import { useAmityComponent } from '~/v4/core/hooks/uikit'; import { UserSearchItem } from './UserSearchItem'; import { UserSearchItemSkeleton } from './UserSearchItemSkeleton'; +import { usePageBehavior } from '~/v4/core/providers/PageBehaviorProvider'; interface UserSearchResultProps { pageId?: string; @@ -26,13 +27,20 @@ export const UserSearchResult = ({ }); const [intersectionNode, setIntersectionNode] = useState<HTMLDivElement | null>(null); + const { AmityUserSearchResultComponentBehavior } = usePageBehavior(); useIntersectionObserver({ onIntersect: () => onLoadMore(), node: intersectionNode }); return ( <div className={styles.userSearchResult} style={themeStyles}> {userCollection.map((user) => ( - <UserSearchItem key={user.userId} user={user} /> + <UserSearchItem + key={user.userId} + user={user} + onClick={() => + AmityUserSearchResultComponentBehavior?.goToUserProfilePage?.({ userId: user.userId }) + } + /> ))} {isLoading ? Array.from({ length: 5 }).map((_, index) => ( From a79babbfe0f2ca9053aa5c8e35d94fcc80f253c4 Mon Sep 17 00:00:00 2001 From: "Pitchaya T." <33589608+ptchayap@users.noreply.github.com> Date: Fri, 30 Aug 2024 17:32:28 +0700 Subject: [PATCH 297/300] fix: ASC-24486 - All users show in comment's mention list (#622) * chore: rattata * chore: applied includeDeleted * chore: update tssdk version * fix: update SDK version * chore: revert sdk version * fix: sdk version * fix: ci * fix: ci and upgrade pnpm to 9.9 * chore: update ci * fix: force ci to install specific sdk version * fix: user navigation * fix: pass community to CommentComposer * feat: add includeDeleted filter * fix: background coloer * fix: use searchMembers instead of getMembers * fix: sdk version on devDependencies * fix: installed SDK version * chore: rattata * chore: applied includeDeleted * chore: update tssdk version * fix: update SDK version * chore: revert sdk version * fix: sdk version * fix: ci * fix: ci and upgrade pnpm to 9.9 * chore: update ci * fix: force ci to install specific sdk version * fix: user navigation * fix: pass community to CommentComposer * feat: add includeDeleted filter * fix: background coloer * fix: use searchMembers instead of getMembers * fix: sdk version on devDependencies * fix: installed SDK version * fix: revert change --------- Co-authored-by: Bonn <pittawat@amity.co> Co-authored-by: Chayanit Manop <chayanit@amity.co> --- package.json | 2 +- pnpm-lock.yaml | 12 ++++++------ .../hooks/collections/useChannelMembersCollection.ts | 2 +- .../collections/useCommunityMembersCollection.ts | 7 ++++++- .../components/CommentComposer/CommentComposer.tsx | 3 +++ .../components/CommentComposer/CommentInput.tsx | 2 +- .../CommentMentionInput.module.css | 1 + .../UserSearchResult/UserSearchItem.module.css | 1 + .../collections/useCommunityMembersCollection.ts | 5 ++++- src/v4/social/hooks/useMentionUser.ts | 7 ++++--- .../social/pages/PostDetailPage/PostDetailPage.tsx | 12 ++++-------- 11 files changed, 32 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index fc1c0d326..e78c2a95e 100644 --- a/package.json +++ b/package.json @@ -158,5 +158,5 @@ } }, "license": "LGPL-2.1-only", - "packageManager": "pnpm@9.5.0" + "packageManager": "pnpm@9.9.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5a45d7c15..6d29c1702 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -134,7 +134,7 @@ importers: devDependencies: '@amityco/ts-sdk': specifier: ^6.29.2 - version: 6.29.2 + version: 6.30.0 '@eslint/js': specifier: ^9.4.0 version: 9.7.0 @@ -315,8 +315,8 @@ packages: '@adobe/css-tools@4.3.3': resolution: {integrity: sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==} - '@amityco/ts-sdk@6.29.2': - resolution: {integrity: sha512-g6+5gWmOKKgbQeWj8wC+zNEeTmYIMjOFqCSc+X/kog5Cs2EjlWgZzL3QrIb7ulnjQDZ/h8CC8ZepkGcEBJIkjQ==} + '@amityco/ts-sdk@6.30.0': + resolution: {integrity: sha512-kyW0ytV6Go3tAZ5EFmzeVLm2PE2jzgmlKnKygpK9iJPgY46wsGCVkhRFMCLMXGvpNPz3D1Tnmq+83wL/xNTJKg==} engines: {node: '>=12', npm: '>=6'} '@ampproject/remapping@2.3.0': @@ -7690,7 +7690,7 @@ snapshots: '@adobe/css-tools@4.3.3': {} - '@amityco/ts-sdk@6.29.2': + '@amityco/ts-sdk@6.30.0': dependencies: agentkeepalive: 4.5.0 axios: 1.7.4(debug@4.3.6) @@ -8746,7 +8746,7 @@ snapshots: '@eslint/config-array@0.17.0': dependencies: '@eslint/object-schema': 2.1.4 - debug: 4.3.5 + debug: 4.3.6 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -8754,7 +8754,7 @@ snapshots: '@eslint/eslintrc@3.1.0': dependencies: ajv: 6.12.6 - debug: 4.3.5 + debug: 4.3.6 espree: 10.1.0 globals: 14.0.0 ignore: 5.3.1 diff --git a/src/chat/hooks/collections/useChannelMembersCollection.ts b/src/chat/hooks/collections/useChannelMembersCollection.ts index 7042aa42c..80e4288d0 100644 --- a/src/chat/hooks/collections/useChannelMembersCollection.ts +++ b/src/chat/hooks/collections/useChannelMembersCollection.ts @@ -7,7 +7,7 @@ import useLiveCollection from '~/core/hooks/useLiveCollection'; export default function useChannelMembersCollection(channelId?: string) { const { items, ...rest } = useLiveCollection({ fetcher: ChannelRepository.Membership.getMembers, - params: { channelId: channelId as string }, + params: { channelId: channelId as string, includeDeleted: false }, shouldCall: () => !!channelId, }); diff --git a/src/social/hooks/collections/useCommunityMembersCollection.ts b/src/social/hooks/collections/useCommunityMembersCollection.ts index be5668978..9e1b08403 100644 --- a/src/social/hooks/collections/useCommunityMembersCollection.ts +++ b/src/social/hooks/collections/useCommunityMembersCollection.ts @@ -4,7 +4,12 @@ import useLiveCollection from '~/core/hooks/useLiveCollection'; export default function useCommunityMembersCollection(communityId?: string, limit: number = 5) { const { items, ...rest } = useLiveCollection({ fetcher: CommunityRepository.Membership.getMembers, - params: { communityId: communityId as string, limit, memberships: ['member'] }, + params: { + communityId: communityId as string, + limit, + memberships: ['member'], + includeDeleted: false, + }, shouldCall: () => !!communityId, }); diff --git a/src/v4/social/components/CommentComposer/CommentComposer.tsx b/src/v4/social/components/CommentComposer/CommentComposer.tsx index 2a64a2990..182d150c4 100644 --- a/src/v4/social/components/CommentComposer/CommentComposer.tsx +++ b/src/v4/social/components/CommentComposer/CommentComposer.tsx @@ -38,6 +38,7 @@ interface CommentComposerProps { replyTo?: Amity.Comment; onCancelReply: () => void; shouldAllowCreation?: boolean; + community?: Amity.Community | null; } export const CommentComposer = ({ @@ -46,6 +47,7 @@ export const CommentComposer = ({ replyTo, onCancelReply, shouldAllowCreation = true, + community, }: CommentComposerProps) => { const userId = useSDK().currentUserId; const { user } = useUser(userId); @@ -136,6 +138,7 @@ export const CommentComposer = ({ mentionOffsetBottom={-mentionOffsetBottom} value={textValue} placehoder="Say something nice..." + community={community} /> </div> <Button diff --git a/src/v4/social/components/CommentComposer/CommentInput.tsx b/src/v4/social/components/CommentComposer/CommentInput.tsx index a7f5d0b10..2fc479b3f 100644 --- a/src/v4/social/components/CommentComposer/CommentInput.tsx +++ b/src/v4/social/components/CommentComposer/CommentInput.tsx @@ -46,7 +46,7 @@ interface EditorStateJson extends SerializedLexicalNode { } interface CommentInputProps { - community?: Amity.Community; + community?: Amity.Community | null; value?: CreateCommentParams; mentionOffsetBottom?: number; maxLines?: number; diff --git a/src/v4/social/components/CommentMentionInput/CommentMentionInput.module.css b/src/v4/social/components/CommentMentionInput/CommentMentionInput.module.css index e78db3267..defdf275b 100644 --- a/src/v4/social/components/CommentMentionInput/CommentMentionInput.module.css +++ b/src/v4/social/components/CommentMentionInput/CommentMentionInput.module.css @@ -6,6 +6,7 @@ height: 12.5rem; overflow-y: scroll; transform: translateY(var(--asc-mention-offset-bottom)); + background-color: var(--asc-color-background-default); } .mentionTextInput_item::-webkit-scrollbar { diff --git a/src/v4/social/components/UserSearchResult/UserSearchItem.module.css b/src/v4/social/components/UserSearchResult/UserSearchItem.module.css index 57a641301..7b55aa592 100644 --- a/src/v4/social/components/UserSearchResult/UserSearchItem.module.css +++ b/src/v4/social/components/UserSearchResult/UserSearchItem.module.css @@ -4,6 +4,7 @@ padding-top: 0.5rem; padding-bottom: 0.5rem; width: 100%; + cursor: pointer; } .userItem__leftPane { diff --git a/src/v4/social/hooks/collections/useCommunityMembersCollection.ts b/src/v4/social/hooks/collections/useCommunityMembersCollection.ts index d1de0f488..9886a5aea 100644 --- a/src/v4/social/hooks/collections/useCommunityMembersCollection.ts +++ b/src/v4/social/hooks/collections/useCommunityMembersCollection.ts @@ -10,7 +10,10 @@ export default function useCommunityMembersCollection({ }) { const { items, ...rest } = useLiveCollection({ fetcher: CommunityRepository.Membership.getMembers, - params: queryParams as Parameters<typeof CommunityRepository.Membership.getMembers>[0], + params: { + ...(queryParams as Parameters<typeof CommunityRepository.Membership.getMembers>[0]), + includeDeleted: false, + }, shouldCall: !!queryParams?.communityId && shouldCall, }); diff --git a/src/v4/social/hooks/useMentionUser.ts b/src/v4/social/hooks/useMentionUser.ts index c6f00d6e7..bcb36e03d 100644 --- a/src/v4/social/hooks/useMentionUser.ts +++ b/src/v4/social/hooks/useMentionUser.ts @@ -10,19 +10,20 @@ export const useMentionUsers = ({ }: { displayName: string; limit?: number; - community?: Amity.Community; + community?: Amity.Community | null; }) => { const fetcher = community && !community.isPublic - ? CommunityRepository.Membership.getMembers + ? CommunityRepository.Membership.searchMembers : UserRepository.getUsers; const params = community && !community.isPublic ? ({ communityId: community.communityId, - displayName, + search: displayName, limit, + includeDeleted: false, } as Amity.SearchCommunityMemberLiveCollection) : ({ displayName, limit } as Amity.UserLiveCollection); diff --git a/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx b/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx index 952fcbf17..a14861963 100644 --- a/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx +++ b/src/v4/social/pages/PostDetailPage/PostDetailPage.tsx @@ -35,15 +35,10 @@ export function PostDetailPage({ id, hideTarget, category }: PostDetailPageProps const [replyComment, setReplyComment] = useState<Amity.Comment | undefined>(); const { setDrawerData, removeDrawerData } = useDrawer(); - const [communityId, setCommunityId] = useState<string | null | undefined>(null); - useEffect(() => { - if (post?.targetId) { - setCommunityId(post.targetId); - } - }, [post?.targetId]); - - const { community } = useCommunity({ communityId }); + const { community } = useCommunity({ + communityId: post?.targetType === 'community' ? post.targetId : null, + }); return ( <div className={styles.postDetailPage} style={themeStyles}> @@ -106,6 +101,7 @@ export function PostDetailPage({ id, hideTarget, category }: PostDetailPageProps referenceType={'post'} replyTo={replyComment} onCancelReply={() => setReplyComment(undefined)} + community={community} /> ) )} From 3c00379b692f85cd8d69cf852d0e6597e685d802 Mon Sep 17 00:00:00 2001 From: Bonn <pittawat@amity.co> Date: Mon, 2 Sep 2024 11:21:31 +0700 Subject: [PATCH 298/300] chore(sdk): rattata (#611) * chore: rattata * chore: applied includeDeleted * chore: update tssdk version * fix: update SDK version * chore: revert sdk version * fix: ASC-24486 - All users show in comment's mention list (#622) * chore: rattata * chore: applied includeDeleted * chore: update tssdk version * fix: update SDK version * chore: revert sdk version * fix: sdk version * fix: ci * fix: ci and upgrade pnpm to 9.9 * chore: update ci * fix: force ci to install specific sdk version * fix: user navigation * fix: pass community to CommentComposer * feat: add includeDeleted filter * fix: background coloer * fix: use searchMembers instead of getMembers * fix: sdk version on devDependencies * fix: installed SDK version * chore: rattata * chore: applied includeDeleted * chore: update tssdk version * fix: update SDK version * chore: revert sdk version * fix: sdk version * fix: ci * fix: ci and upgrade pnpm to 9.9 * chore: update ci * fix: force ci to install specific sdk version * fix: user navigation * fix: pass community to CommentComposer * feat: add includeDeleted filter * fix: background coloer * fix: use searchMembers instead of getMembers * fix: sdk version on devDependencies * fix: installed SDK version * fix: revert change --------- Co-authored-by: Bonn <pittawat@amity.co> Co-authored-by: Chayanit Manop <chayanit@amity.co> * chore: rattata * chore: update tssdk version * fix: update SDK version * chore: revert sdk version * fix: sdk version * fix: ci * fix: ci and upgrade pnpm to 9.9 * chore: update ci * fix: force ci to install specific sdk version * fix: user navigation * fix: ASC-25204 - post mention (#618) * chore: update PostTextField * fix: mention layout * fix: layout and removed unused code * chore: remove console.log * chore: update tssdk version * fix: update SDK version * chore: revert sdk version * chore: upgrade sdk version * chore: update tssdk version * fix: update SDK version * chore: revert sdk version * chore: upgrade sdk version * fix: revert workflow and readme --------- Co-authored-by: Chayanit Manop <chayanit@amity.co> Co-authored-by: Pitchaya T. <33589608+ptchayap@users.noreply.github.com> Co-authored-by: ptchaya_p <pitchaya@amity.co> --- package.json | 4 ++-- pnpm-lock.yaml | 2 +- .../components/UserSearchResult/UserSearchItem.tsx | 7 +++++-- .../components/UserSearchResult/UserSearchResult.tsx | 9 +-------- 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index e78c2a95e..030a909da 100644 --- a/package.json +++ b/package.json @@ -39,12 +39,12 @@ "tsc": "tsc" }, "peerDependencies": { - "@amityco/ts-sdk": "^6.29.2", + "@amityco/ts-sdk": "^6.30.0", "react": ">=17.0.2", "react-dom": ">=17.0.2" }, "devDependencies": { - "@amityco/ts-sdk": "^6.29.2", + "@amityco/ts-sdk": "^6.30.0", "@eslint/js": "^9.4.0", "@storybook/addon-a11y": "^7.6.7", "@storybook/addon-actions": "^7.6.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6d29c1702..4aa999d5b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -133,7 +133,7 @@ importers: version: 3.23.8 devDependencies: '@amityco/ts-sdk': - specifier: ^6.29.2 + specifier: ^6.30.0 version: 6.30.0 '@eslint/js': specifier: ^9.4.0 diff --git a/src/v4/social/components/UserSearchResult/UserSearchItem.tsx b/src/v4/social/components/UserSearchResult/UserSearchItem.tsx index a4c39aede..a67d5c0ea 100644 --- a/src/v4/social/components/UserSearchResult/UserSearchItem.tsx +++ b/src/v4/social/components/UserSearchResult/UserSearchItem.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { UserAvatar } from '~/v4/social/internal-components/UserAvatar/UserAvatar'; import { Typography } from '~/v4/core/components'; import styles from './UserSearchItem.module.css'; +import { useNavigation } from '~/v4/core/providers/NavigationProvider'; import { Button } from '~/v4/core/natives/Button'; interface UserSearchItemProps { @@ -9,9 +10,11 @@ interface UserSearchItemProps { onClick?: () => void; } -export const UserSearchItem = ({ user, onClick }: UserSearchItemProps) => { +export const UserSearchItem = ({ user }: UserSearchItemProps) => { + const { onClickUser } = useNavigation(); + return ( - <Button key={user.userId} className={styles.userItem} onPress={onClick}> + <Button key={user.userId} className={styles.userItem} onPress={() => onClickUser(user.userId)}> <div className={styles.userItem__leftPane}> <UserAvatar userId={user.userId} className={styles.userItem__avatar} /> </div> diff --git a/src/v4/social/components/UserSearchResult/UserSearchResult.tsx b/src/v4/social/components/UserSearchResult/UserSearchResult.tsx index 40ef53d7c..f419ccb6e 100644 --- a/src/v4/social/components/UserSearchResult/UserSearchResult.tsx +++ b/src/v4/social/components/UserSearchResult/UserSearchResult.tsx @@ -27,20 +27,13 @@ export const UserSearchResult = ({ }); const [intersectionNode, setIntersectionNode] = useState<HTMLDivElement | null>(null); - const { AmityUserSearchResultComponentBehavior } = usePageBehavior(); useIntersectionObserver({ onIntersect: () => onLoadMore(), node: intersectionNode }); return ( <div className={styles.userSearchResult} style={themeStyles}> {userCollection.map((user) => ( - <UserSearchItem - key={user.userId} - user={user} - onClick={() => - AmityUserSearchResultComponentBehavior?.goToUserProfilePage?.({ userId: user.userId }) - } - /> + <UserSearchItem key={user.userId} user={user} /> ))} {isLoading ? Array.from({ length: 5 }).map((_, index) => ( From e3cf90dce822c9688ce297c646e7281c00b765e0 Mon Sep 17 00:00:00 2001 From: "Pitchaya T." <33589608+ptchayap@users.noreply.github.com> Date: Mon, 2 Sep 2024 13:45:30 +0700 Subject: [PATCH 299/300] chore(release): 4.0.0-beta.12 (#638) Co-authored-by: bmo-amity-bot <developers@amity.co> --- CHANGELOG.md | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9a0af1b6..7abc27af8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## 4.0.0-beta.12 (2024-09-02) + ## 4.0.0-beta.11 (2024-08-16) diff --git a/package.json b/package.json index 030a909da..e815f0742 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@amityco/ui-kit", - "version": "4.0.0-beta.11", + "version": "4.0.0-beta.12", "engines": { "node": ">=20", "pnpm": "9" From 300cd1bec00537d9a592168fb128c9c62f650ce2 Mon Sep 17 00:00:00 2001 From: bmo-amity-bot <developers@amity.co> Date: Mon, 2 Sep 2024 07:30:38 +0000 Subject: [PATCH 300/300] chore(release): 4.0.0-beta.12 --- CHANGELOG.md | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5c348ea7..70df6d687 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## 4.0.0-beta.12 (2024-09-02) + ## 4.0.0-beta.11 (2024-08-16) ## 4.0.0-beta.10 (2024-07-24) diff --git a/package.json b/package.json index 97d4c06d8..384dac6d9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@amityco/ui-kit-open-source", - "version": "4.0.0-beta.11", + "version": "4.0.0-beta.12", "engines": { "node": ">=20", "pnpm": "9"