diff --git a/package-lock.json b/package-lock.json index 4dec05f..b7bc597 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,16 +10,17 @@ "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", - "@mui/icons-material": "^6.3.0", - "@mui/lab": "^6.0.0-beta.21", - "@mui/material": "^6.3.0", + "@mui/icons-material": "^7.1.0", + "@mui/lab": "7.0.0-beta.12", + "@mui/material": "^7.1.0", "@preact/signals-react": "^2.3.0", "@reduxjs/toolkit": "^2.5.0", "compressorjs": "^1.2.1", "dompurify": "^3.2.3", + "jotai": "^2.12.4", "localforage": "^1.10.0", "moment": "^2.30.1", - "qapp-core": "^1.0.17", + "qapp-core": "^1.0.29", "quill": "^2.0.2", "quill-image-resize-module-react": "^3.0.0", "react": "^19.0.0", @@ -291,12 +292,10 @@ } }, "node_modules/@babel/runtime": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", - "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz", + "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -1063,40 +1062,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@floating-ui/core": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.8.tgz", - "integrity": "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==", - "dependencies": { - "@floating-ui/utils": "^0.2.8" - } - }, - "node_modules/@floating-ui/dom": { - "version": "1.6.12", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.12.tgz", - "integrity": "sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==", - "dependencies": { - "@floating-ui/core": "^1.6.0", - "@floating-ui/utils": "^0.2.8" - } - }, - "node_modules/@floating-ui/react-dom": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", - "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", - "dependencies": { - "@floating-ui/dom": "^1.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@floating-ui/utils": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz", - "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==" - }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1201,52 +1166,23 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@mui/base": { - "version": "5.0.0-beta.68", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.68.tgz", - "integrity": "sha512-F1JMNeLS9Qhjj3wN86JUQYBtJoXyQvknxlzwNl6eS0ZABo1MiohMONj3/WQzYPSXIKC2bS/ZbyBzdHhi2GnEpA==", - "dependencies": { - "@babel/runtime": "^7.26.0", - "@floating-ui/react-dom": "^2.1.1", - "@mui/types": "^7.2.20", - "@mui/utils": "^6.3.0", - "@popperjs/core": "^2.11.8", - "clsx": "^2.1.1", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@mui/core-downloads-tracker": { - "version": "6.4.10", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.10.tgz", - "integrity": "sha512-cblGjlM6+xsptwyaALw8RbRIUoqmKxOqLxlk2LkTDhxqUuql1YSOKKLH3w+Yd2QLz28b7MR65sx1OjsRZUfOSQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.1.0.tgz", + "integrity": "sha512-E0OqhZv548Qdc0PwWhLVA2zmjJZSTvaL4ZhoswmI8NJEC1tpW2js6LLP827jrW9MEiXYdz3QS6+hask83w74yQ==", + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/mui-org" } }, "node_modules/@mui/icons-material": { - "version": "6.4.10", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.10.tgz", - "integrity": "sha512-c2KdFl4KZ0QYC+JSDTMCNjcuOL2rVSdIx/beo7FwJDh2e9XqC1MoLCjw6L1Jo40zbArkgJyg3oFORbXcRfgZOA==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.1.0.tgz", + "integrity": "sha512-1mUPMAZ+Qk3jfgL5ftRR06ATH/Esi0izHl1z56H+df6cwIlCWG66RXciUqeJCttbOXOQ5y2DCjLZI/4t3Yg3LA==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.26.0" + "@babel/runtime": "^7.27.1" }, "engines": { "node": ">=14.0.0" @@ -1256,7 +1192,7 @@ "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "@mui/material": "^6.4.10", + "@mui/material": "^7.1.0", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, @@ -1267,15 +1203,15 @@ } }, "node_modules/@mui/lab": { - "version": "6.0.0-beta.21", - "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-6.0.0-beta.21.tgz", - "integrity": "sha512-hiFZgTwBNhJMUlEhmqfW4+5wy3C8UF9KFuzSOux6x4kgc9hsC0l+motXcF1Vyh+jhJYGeZ6yUoImqCf9RWzEvw==", + "version": "7.0.0-beta.12", + "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-7.0.0-beta.12.tgz", + "integrity": "sha512-685MmzByCy3Vmb7xI6J8qOQm4l7yqfVTOLwxVmNV1EHBKuJiMuoX4/2vAAEGfNbDeEfWQsp7aBWanYpSWe1iRA==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.26.0", - "@mui/base": "5.0.0-beta.68", - "@mui/system": "^6.3.0", - "@mui/types": "^7.2.20", - "@mui/utils": "^6.3.0", + "@babel/runtime": "^7.27.1", + "@mui/system": "^7.1.0", + "@mui/types": "^7.4.2", + "@mui/utils": "^7.1.0", "clsx": "^2.1.1", "prop-types": "^15.8.1" }, @@ -1289,8 +1225,8 @@ "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@mui/material": "^6.3.0", - "@mui/material-pigment-css": "^6.3.0", + "@mui/material": "^7.1.0", + "@mui/material-pigment-css": "^7.1.0", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -1311,21 +1247,22 @@ } }, "node_modules/@mui/material": { - "version": "6.4.10", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.10.tgz", - "integrity": "sha512-L1B0+Vg9NFjo3NcfODH3bohl6fIkzjyDBHBHb3Al4QI7owaJrFm2sSDyfz++iatzICug6U6q5tHLQrCLO71xkg==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.1.0.tgz", + "integrity": "sha512-ahUJdrhEv+mCp4XHW+tHIEYzZMSRLg8z4AjUOsj44QpD1ZaMxQoVOG2xiHvLFdcsIPbgSRx1bg1eQSheHBgvtg==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.26.0", - "@mui/core-downloads-tracker": "^6.4.10", - "@mui/system": "^6.4.10", - "@mui/types": "~7.2.24", - "@mui/utils": "^6.4.9", + "@babel/runtime": "^7.27.1", + "@mui/core-downloads-tracker": "^7.1.0", + "@mui/system": "^7.1.0", + "@mui/types": "^7.4.2", + "@mui/utils": "^7.1.0", "@popperjs/core": "^2.11.8", "@types/react-transition-group": "^4.4.12", "clsx": "^2.1.1", "csstype": "^3.1.3", "prop-types": "^15.8.1", - "react-is": "^19.0.0", + "react-is": "^19.1.0", "react-transition-group": "^4.4.5" }, "engines": { @@ -1338,7 +1275,7 @@ "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@mui/material-pigment-css": "^6.4.10", + "@mui/material-pigment-css": "^7.1.0", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -1359,12 +1296,13 @@ } }, "node_modules/@mui/private-theming": { - "version": "6.4.9", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.9.tgz", - "integrity": "sha512-LktcVmI5X17/Q5SkwjCcdOLBzt1hXuc14jYa7NPShog0GBDCDvKtcnP0V7a2s6EiVRlv7BzbWEJzH6+l/zaCxw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.1.0.tgz", + "integrity": "sha512-4Kck4jxhqF6YxNwJdSae1WgDfXVg0lIH6JVJ7gtuFfuKcQCgomJxPvUEOySTFRPz1IZzwz5OAcToskRdffElDA==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.26.0", - "@mui/utils": "^6.4.9", + "@babel/runtime": "^7.27.1", + "@mui/utils": "^7.1.0", "prop-types": "^15.8.1" }, "engines": { @@ -1385,11 +1323,12 @@ } }, "node_modules/@mui/styled-engine": { - "version": "6.4.9", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.4.9.tgz", - "integrity": "sha512-qZRWO0cT407NI4ZRjZcH+1SOu8f3JzLHqdMlg52GyEufM9pkSZFnf7xjpwnlvkixcGjco6wLlMD0VB43KRcBuA==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.1.0.tgz", + "integrity": "sha512-m0mJ0c6iRC+f9hMeRe0W7zZX1wme3oUX0+XTVHjPG7DJz6OdQ6K/ggEOq7ZdwilcpdsDUwwMfOmvO71qDkYd2w==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.26.0", + "@babel/runtime": "^7.27.1", "@emotion/cache": "^11.13.5", "@emotion/serialize": "^1.3.3", "@emotion/sheet": "^1.4.0", @@ -1418,15 +1357,16 @@ } }, "node_modules/@mui/system": { - "version": "6.4.10", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.10.tgz", - "integrity": "sha512-RyBGQwP3tgo4JEibK+RwVu1a6nQ6y8urMCNsb2aiN/nvTxxumq6P26aoG4GTUf8L4O1sthC4lMXlP4r8ixDkMg==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.1.0.tgz", + "integrity": "sha512-iedAWgRJMCxeMHvkEhsDlbvkK+qKf9me6ofsf7twk/jfT4P1ImVf7Rwb5VubEA0sikrVL+1SkoZM41M4+LNAVA==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.26.0", - "@mui/private-theming": "^6.4.9", - "@mui/styled-engine": "^6.4.9", - "@mui/types": "~7.2.24", - "@mui/utils": "^6.4.9", + "@babel/runtime": "^7.27.1", + "@mui/private-theming": "^7.1.0", + "@mui/styled-engine": "^7.1.0", + "@mui/types": "^7.4.2", + "@mui/utils": "^7.1.0", "clsx": "^2.1.1", "csstype": "^3.1.3", "prop-types": "^15.8.1" @@ -1457,9 +1397,13 @@ } }, "node_modules/@mui/types": { - "version": "7.2.24", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.24.tgz", - "integrity": "sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==", + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.2.tgz", + "integrity": "sha512-edRc5JcLPsrlNFYyTPxds+d5oUovuUxnnDtpJUbP6WMeV4+6eaX/mqai1ZIWT62lCOe0nlrON0s9HDiv5en5bA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.1" + }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, @@ -1470,16 +1414,17 @@ } }, "node_modules/@mui/utils": { - "version": "6.4.9", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.9.tgz", - "integrity": "sha512-Y12Q9hbK9g+ZY0T3Rxrx9m2m10gaphDuUMgWxyV5kNJevVxXYCLclYUCC9vXaIk1/NdNDTcW2Yfr2OGvNFNmHg==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.1.0.tgz", + "integrity": "sha512-/OM3S8kSHHmWNOP+NH9xEtpYSG10upXeQ0wLZnfDgmgadTAk5F4MQfFLyZ5FCRJENB3eRzltMmaNl6UtDnPovw==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.26.0", - "@mui/types": "~7.2.24", + "@babel/runtime": "^7.27.1", + "@mui/types": "^7.4.2", "@types/prop-types": "^15.7.14", "clsx": "^2.1.1", "prop-types": "^15.8.1", - "react-is": "^19.0.0" + "react-is": "^19.1.0" }, "engines": { "node": ">=14.0.0" @@ -1962,7 +1907,8 @@ "node_modules/@types/prop-types": { "version": "15.7.14", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", - "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==" + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", + "license": "MIT" }, "node_modules/@types/react": { "version": "19.0.2", @@ -2700,6 +2646,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/dexie": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/dexie/-/dexie-4.0.11.tgz", + "integrity": "sha512-SOKO002EqlvBYYKQSew3iymBoN2EQ4BDw/3yprjh7kAfFzjBYkaMNa/pZvcA7HSWlcKSQb9XhPe3wKyQ0x4A8A==", + "license": "Apache-2.0" + }, "node_modules/dom-helpers": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", @@ -3568,6 +3520,27 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/jotai": { + "version": "2.12.4", + "resolved": "https://registry.npmjs.org/jotai/-/jotai-2.12.4.tgz", + "integrity": "sha512-eFXLJol4oOLM8BS1+QV+XwaYQITG8n1tatBCFl4F5HE3zR5j2WIK8QpMt7VJIYmlogNUZfvB7wjwLoVk+umB9Q==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=17.0.0", + "react": ">=17.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + } + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4066,9 +4039,10 @@ } }, "node_modules/qapp-core": { - "version": "1.0.17", - "resolved": "https://registry.npmjs.org/qapp-core/-/qapp-core-1.0.17.tgz", - "integrity": "sha512-c++yhwAIlg4HhJcp7J2UNBSo/+va7I8GbtmqZ91HNHN2uk5qrwnxIQvTdWXynLvaTVrq16pW7Ry92lTncumGHg==", + "version": "1.0.29", + "resolved": "https://registry.npmjs.org/qapp-core/-/qapp-core-1.0.29.tgz", + "integrity": "sha512-w1tBniw6yFxmOc+1H9584kCwL3mpHpIEtU31vv9FIAwltfRMF0hAidTC2tVcvFqNNYKn7rfvDuhhbgayI/elBQ==", + "license": "MIT", "dependencies": { "@tanstack/react-virtual": "^3.13.2", "bloom-filters": "^3.0.4", @@ -4076,6 +4050,7 @@ "compressorjs": "^1.2.1", "crypto-js": "^4.2.0", "dayjs": "^1.11.13", + "dexie": "^4.0.11", "dompurify": "^3.2.4", "react-dropzone": "^14.3.8", "react-hot-toast": "^2.5.2", @@ -4086,8 +4061,8 @@ "peerDependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", - "@mui/icons-material": "^6.4.7", - "@mui/material": "^6.4.7", + "@mui/icons-material": "^7.0.1", + "@mui/material": "^7.0.1", "react": "^19.0.0" } }, @@ -4300,9 +4275,10 @@ } }, "node_modules/react-is": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.0.0.tgz", - "integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==" + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz", + "integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==", + "license": "MIT" }, "node_modules/react-quill-new": { "version": "3.3.3", @@ -4460,11 +4436,6 @@ "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", "integrity": "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==" }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" - }, "node_modules/regexp.prototype.flags": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", diff --git a/package.json b/package.json index d48fc3b..bf319a6 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@reduxjs/toolkit": "^2.5.0", "compressorjs": "^1.2.1", "dompurify": "^3.2.3", + "jotai": "^2.12.4", "localforage": "^1.10.0", "moment": "^2.30.1", "qapp-core": "^1.0.29", diff --git a/src/App.tsx b/src/App.tsx index 35531d1..1d46fa6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,7 +3,6 @@ import { ThemeProvider } from "@mui/material/styles"; import { useEffect, useState } from "react"; import { Provider, useSelector } from "react-redux"; import { Route, Routes } from "react-router-dom"; -import { persistStore } from "redux-persist"; import { PersistGate } from "redux-persist/integration/react"; import { subscriptionListFilter } from "./App-Functions.ts"; import Notification from "./components/common/Notification/Notification"; diff --git a/src/QappCoreWrapper.tsx b/src/QappCoreWrapper.tsx index 7f0ba6d..05f5fd7 100644 --- a/src/QappCoreWrapper.tsx +++ b/src/QappCoreWrapper.tsx @@ -1,5 +1,4 @@ import { GlobalProvider } from 'qapp-core' -import React from 'react' import { useSelector } from 'react-redux'; import { RootState } from './state/store'; @@ -7,16 +6,16 @@ export const QappCoreWrapper = ({children}) => { const { user } = useSelector((state: RootState) => state.auth); return ( {children} diff --git a/src/components/Publish/MultiplePublish/MultiplePublishAll.tsx b/src/components/Publish/MultiplePublish/MultiplePublishAll.tsx index 6dbcdc4..4a4fc7f 100644 --- a/src/components/Publish/MultiplePublish/MultiplePublishAll.tsx +++ b/src/components/Publish/MultiplePublish/MultiplePublishAll.tsx @@ -40,7 +40,7 @@ export const MultiplePublish = ({ const hasStarted = useRef(false); const publish = useCallback(async (pub: any) => { const lengthOfResources = pub?.resources?.length; - const lengthOfTimeout = lengthOfResources * 30000; + const lengthOfTimeout = lengthOfResources * 1200000; // Time out in QR, Seconds = 20 Minutes return await qortalRequestWithTimeout(pub, lengthOfTimeout); }, []); const [isPublishing, setIsPublishing] = useState(true); diff --git a/src/components/UserDropDown.tsx b/src/components/UserDropDown.tsx new file mode 100644 index 0000000..f7c33f0 --- /dev/null +++ b/src/components/UserDropDown.tsx @@ -0,0 +1,34 @@ +import { Avatar, useTheme } from "@mui/material"; +import { AccountCircleSVG } from "../assets/svgs/AccountCircleSVG"; +import { menuIconSize } from "../constants/Misc"; +import { + DropdownContainer, + DropdownText, + AvatarContainer +} from "./layout/Navbar/Navbar-styles"; + +interface UserDropDownProps { + userName: string; + handleMyChannelLink: (username: string) => void; + popMenuRef: React.RefObject<{ closePopover: () => void }>; +} + +export const UserDropDown = ({ userName, handleMyChannelLink, popMenuRef }: UserDropDownProps) => { + const theme = useTheme(); + const userAvatar = `/arbitrary/THUMBNAIL/${userName}/avatar?async=true`; + + return ( + { + handleMyChannelLink(userName); + popMenuRef.current.closePopover(); + }} + > + + {userName?.charAt(0).toUpperCase()} + + {userName} + + ) +} + diff --git a/src/components/common/FileElement.tsx b/src/components/common/FileElement.tsx index 5b6077b..d0f0678 100644 --- a/src/components/common/FileElement.tsx +++ b/src/components/common/FileElement.tsx @@ -125,20 +125,11 @@ export default function FileElement({ try { const { name, service, identifier } = fileInfo; - const url = `/arbitrary/${service}/${name}/${identifier}`; - fetch(url) - .then(response => response.blob()) - .then(async blob => { - await qortalRequest({ - action: "SAVE_FILE", - blob, - filename: filename, - mimeType, - }); - }) - .catch(error => { - console.error("Error fetching the video:", error); - }); + await qortalRequest({ + action: "SAVE_FILE", + location: fileInfo, + filename: filename, + }); } catch (error: any) { let notificationObj: any = null; if (typeof error === "string") { diff --git a/src/components/layout/Navbar/Components/UserMenu.tsx b/src/components/layout/Navbar/Components/UserMenu.tsx index 2bcd97d..649f40a 100644 --- a/src/components/layout/Navbar/Components/UserMenu.tsx +++ b/src/components/layout/Navbar/Components/UserMenu.tsx @@ -1,4 +1,4 @@ -import { Popover, useMediaQuery, useTheme } from "@mui/material"; +import { Popover, useMediaQuery, useTheme, Avatar } from "@mui/material"; import { AccountCircleSVG } from "../../../../assets/svgs/AccountCircleSVG.tsx"; import { headerIconSize, menuIconSize } from "../../../../constants/Misc.ts"; import { BlockedNamesModal } from "../../../common/BlockedNamesModal/BlockedNamesModal.tsx"; @@ -9,21 +9,27 @@ import { NavbarName, } from "../Navbar-styles.tsx"; import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; -import { useRef, useState } from "react"; - +import { useCallback, useRef, useState } from "react"; import PersonOffIcon from "@mui/icons-material/PersonOff"; +import { RootState } from "../../../../state/store"; +import { useDispatch } from "react-redux"; import { useNavigate } from "react-router-dom"; import { PopMenu, PopMenuRefType } from "../../../common/PopMenu.tsx"; - +import { UserDropDown } from "../../../UserDropDown.tsx"; +import { Names } from "../../../../state/global/names.ts"; +import { setName } from "../../../../state/features/authSlice.ts"; export interface NavBarMenuProps { isShowMenu: boolean; userAvatar: string; userName: string | null; + allNames: Names; } + export const UserMenu = ({ isShowMenu, userAvatar, userName, + allNames }: NavBarMenuProps) => { const isScreenSmall = !useMediaQuery(`(min-width:600px)`); const theme = useTheme(); @@ -32,10 +38,12 @@ export const UserMenu = ({ useState(false); const popMenuRef = useRef(null); const navigate = useNavigate(); + const dispatch = useDispatch(); - const handleMyChannelLink = () => { - navigate(`/channel/${userName}`); - }; + const handleMyChannelLink = useCallback((switchToName: string) => { + dispatch(setName(switchToName)); + navigate(`/channel/${switchToName}`); + }, [navigate]); const onCloseBlockedNames = () => { setIsOpenBlockedNamesModal(false); @@ -43,58 +51,30 @@ export const UserMenu = ({ return ( <> - {isShowMenu && ( + {isShowMenu && ( <> {!isScreenSmall && {userName}} - {!userAvatar ? ( - - ) : ( - User Avatar - )} + + {userName?.charAt(0).toUpperCase()} + } > - { - handleMyChannelLink(); - popMenuRef.current.closePopover(); - }} - > - {!userAvatar ? ( - ( + - ) : ( - User Avatar - )} - {userName} - + )) + } + { setIsOpenBlockedNamesModal(true); diff --git a/src/components/layout/Navbar/Navbar.tsx b/src/components/layout/Navbar/Navbar.tsx index ac0e9df..2c34480 100644 --- a/src/components/layout/Navbar/Navbar.tsx +++ b/src/components/layout/Navbar/Navbar.tsx @@ -6,16 +6,18 @@ import { PublishMenu } from "./Components/PublishMenu.tsx"; import { QtubeLogo } from "./Components/QtubeLogo.tsx"; import { UserMenu } from "./Components/UserMenu.tsx"; import { CustomAppBar } from "./Navbar-styles"; +import { Names } from "./../../../state/global/names.ts"; interface Props { isAuthenticated: boolean; userName: string | null; + allNames: Names; userAvatar: string; authenticate: () => void; setTheme: (val: string) => void; } -const NavBar: React.FC = ({ isAuthenticated, userName, userAvatar }) => { +const NavBar: React.FC = ({ isAuthenticated, userName, allNames, userAvatar }) => { const isScreenSmall = !useMediaQuery(`(min-width:600px)`); const isSecure = isAuthenticated && !!userName; const gapSize = 10; @@ -45,6 +47,7 @@ const NavBar: React.FC = ({ isAuthenticated, userName, userAvatar }) => { isShowMenu={isSecure} userAvatar={userAvatar} userName={userName} + allNames={allNames} /> diff --git a/src/constants/Misc.ts b/src/constants/Misc.ts index f469f49..9fd7ee8 100644 --- a/src/constants/Misc.ts +++ b/src/constants/Misc.ts @@ -6,7 +6,7 @@ export const minPriceSuperDislike = 1; export const titleFormatter = /[\r\n]+/g; export const titleFormatterOnSave = /[\r\n/<>:"'\\*|?]+/g; -export const videoMaxSize = 2147; // Size in Megabytes (decimal) +export const videoMaxSize = 2050; // Size in Megabytes (decimal) export const maxSize = videoMaxSize * 1024 * 1024; export const fontSizeExSmall = "60%"; diff --git a/src/global.d.ts b/src/global.d.ts index bb13dfa..d3ebbc2 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -1,4 +1,9 @@ // src/global.d.ts +interface Location { + service: string; + name: string; + identifier?: string; +} interface QortalRequestOptions { action: string; name?: string; @@ -38,6 +43,7 @@ interface QortalRequestOptions { excludeBlocked?: boolean; exactMatchNames?: boolean; nameListFilter?: string[]; + location?: Location; } declare function qortalRequest(options: QortalRequestOptions): Promise; diff --git a/src/hooks/useHandleNameData.tsx b/src/hooks/useHandleNameData.tsx new file mode 100644 index 0000000..6c374ac --- /dev/null +++ b/src/hooks/useHandleNameData.tsx @@ -0,0 +1,36 @@ +import { useSetAtom } from 'jotai'; +import { namesAtom } from '../state/global/names'; +import { useCallback, useEffect } from 'react'; +//import { useGlobal } from 'qapp-core'; +import { useSelector } from 'react-redux'; +import { RootState } from '../state/store'; + +export const useHandleNameData = () => { + const setNames = useSetAtom(namesAtom); + const user = useSelector((state: RootState) => state.auth.user); + + const getMyNames = useCallback(async () => { + if (!user?.address) return; + try { + const res = await qortalRequest({ + action: 'GET_ACCOUNT_NAMES', + address: user.address, + limit: 0, + offset: 0, + reverse: false, + }); + setNames(res); + } catch (error) { + console.error(error); + } + }, [user?.address, setNames]); + + // Initial fetch + interval + useEffect(() => { + getMyNames(); + const interval = setInterval(getMyNames, 120_000); // every 2 minutes + return () => clearInterval(interval); + }, [getMyNames]); + + return null; +}; diff --git a/src/main.tsx b/src/main.tsx index 17b98a1..d98b132 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -2,6 +2,7 @@ import ReactDOM from "react-dom/client"; import App from "./App"; import "./index.css"; import { BrowserRouter } from "react-router-dom"; + interface CustomWindow extends Window { _qdnBase: string; } diff --git a/src/pages/ContentPages/IndividualProfile/IndividualProfile.tsx b/src/pages/ContentPages/IndividualProfile/IndividualProfile.tsx index b8188ff..1a6df6b 100644 --- a/src/pages/ContentPages/IndividualProfile/IndividualProfile.tsx +++ b/src/pages/ContentPages/IndividualProfile/IndividualProfile.tsx @@ -1,13 +1,19 @@ -import { Box } from "@mui/material"; -import React from "react"; +import { Box, Tabs, Tab } from "@mui/material"; +import React, { useState } from "react"; import { useParams } from "react-router-dom"; import { VideoListComponentLevel } from "../../Home/Components/VideoListComponentLevel.tsx"; +import { PlayListComponentLevel } from "../../Home/Components/PlayListComponentLevel.tsx"; import { ChannelActions } from "../VideoContent/ChannelActions.tsx"; import { StyledCardHeaderComment } from "../VideoContent/VideoContent-styles.tsx"; import { HeaderContainer, ProfileContainer } from "./Profile-styles.tsx"; export const IndividualProfile = () => { const { name: channelName } = useParams(); + const [selectedTab, setSelectedTab] = useState(0); + + const handleTabChange = (_event: React.SyntheticEvent, newValue: number) => { + setSelectedTab(newValue); + }; return ( @@ -28,7 +34,19 @@ export const IndividualProfile = () => { - + + {/* Tabs Bar */} + + + + + + + + {/* Tab Content */} + {selectedTab === 0 && } + {selectedTab === 1 && } + ); }; diff --git a/src/pages/ContentPages/VideoContent/ChannelButtons.tsx b/src/pages/ContentPages/VideoContent/ChannelButtons.tsx index 40d021b..83a6336 100644 --- a/src/pages/ContentPages/VideoContent/ChannelButtons.tsx +++ b/src/pages/ContentPages/VideoContent/ChannelButtons.tsx @@ -4,13 +4,19 @@ import { SubscribeButton } from "../../../components/common/ContentButtons/Subsc import { RootState } from "../../../state/store.ts"; import { ChannelParams } from "./ChannelName.tsx"; import { StyledCardColComment } from "./VideoContent-styles.tsx"; +import { namesAtom } from '../../../state/global/names'; +import { useAtom } from "jotai"; export const ChannelButtons = ({ channelName, sx }: ChannelParams) => { - const userName = useSelector((state: RootState) => state.auth.user?.name); + const [names] = useAtom(namesAtom); + const isInNames = names.map((name) => name.name ).includes(channelName); + + //const userName = useSelector((state: RootState) => state.auth.user?.name); + // We need to put a change in here to get the currentUser name, not from the Redux state return ( - {channelName !== userName && ( + {!isInNames && ( <> { + const { name: paramName } = useParams(); + + const firstFetch = useRef(false); + const afterFetch = useRef(false); + const hashMapVideos = useSelector( + (state: RootState) => state.video.hashMapVideos + ); + + const [videos, setVideos] = React.useState([]); + const isLoading = useSignal(true); + const { getVideo, checkAndUpdateVideo } = useFetchVideos(); + // For Pagination + const pageRef = useRef(0); + const [hasMore, setHasMore] = useState(true); + const PAGE_SIZE = 20; + + useEffect(() => { + firstFetch.current = false; + setVideos([]); + pageRef.current = 0; + setHasMore(true); + }, [paramName]); + + // 16-May-2025: Includes Pagination for PlayLists + const getVideos = React.useCallback(async () => { + isLoading.value = true; + try { + + // Query to get a users playlists + //'http://192.168.0.43:12391/arbitrary/resources/search?service=PLAYLIST&name=Ice&exactmatchnames=true&limit=20&reverse=true' \ + + const offset = pageRef.current * PAGE_SIZE; + const url = `/arbitrary/resources/search?mode=ALL&service=PLAYLIST&name=${encodeURIComponent(paramName)}&exactmatchnames=true&limit=20&reverse=true&offset=${offset}`; + + const response = await fetch(url, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + const responseData = await response.json(); + + /* This is the search result for playlists by a specific use + [ + { + "name": "Ice", + "service": "PLAYLIST", + "identifier": "qtube_playlist_the-history-of-qortal_wZ9BtX", + "size": 11824, + "created": 1747327830726 + } + ] + */ + + /* This is the result when getting the JSON infomration about a PLAYLIST + { "title": "The History of Qortal", + "version": 1, + "description": "A timeline of Qortal according to Ice", + "htmlDescription": "

A timeline of Qortal according to Ice

", + "image": "data:image/webp;base64,UklGRugqAABXRUJQVlA4WAoAAAAgAAAASwIAFQAGSJuX2Ws5....YY9umRdt3kC5BWqZoIno3+DIAAA", + "videos": [ + { "identifier": "qtube_vid_q-tube-may-15_32Mf4d_metadata", "name": "Ice", "service": "DOCUMENT", "code": "GLzeC**

New In Qortal? 15-May 2025

Come see new enhancements specific to Q-Tube


" }, + { "identifier": "qtube_vid_11-why-crowetic-dedicated-to-q_nmHmNo_metadata", "name": "QortalNuggets", "service": "DOCUMENT", "code": "Q3fbb**

Ernest asks WHY. Jason answers.

" }, + { "identifier": "qtube_vid_qortal-conscious-soul-festival_55y2u0_metadata", "name": "ThanksToZen", "service": "DOCUMENT", "code": "Xvwjg**Footage from day 1 of 2" }, + { "identifier": "qtube_vid_qortal-at-web3-amsterdamvideo7_1lCJT4_metadata", "name": "igorcoin", "service": "DOCUMENT", "code": "IbRmX**qortal-at-web3-amsterdam_video_720p_eesti keelsete subtiitritega" } + ], + "commentsId": "qtube_playlist__cm_wZ9BtX", "category": 26, "subcategory": "" } + */ + + const structureData = responseData.map((video: any): Video => { + return { + title: video?.metadata?.title, + category: video?.metadata?.category, + categoryName: video?.metadata?.categoryName, + tags: video?.metadata?.tags || [], + description: video?.metadata?.description, + created: video?.created, + updated: video?.updated, + service: `PLAYLIST`, + user: video.name, + videoImage: "", + id: video.identifier, + }; + }); + + // Pre-Pagination + //setVideos((prev) => { + // const copiedVideos: Video[] = [...prev]; + // structureData.forEach((video: Video) => { + // const index = prev.findIndex((p) => p.id === video.id); + // if (index !== -1) { + // copiedVideos[index] = video; + // } else { + // copiedVideos.push(video); + // } + // }); + // return copiedVideos; + //}); + + setVideos(prev => { + const updatedVideos = [...prev]; + + structureData.forEach(video => { + const exists = updatedVideos.some(v => v.id === video.id); + if (!exists) { + updatedVideos.push(video); + } + }); + return updatedVideos; + }); + + // If fewer than PAGE_SIZE results, we've reached the end + if (structureData.length < PAGE_SIZE) { + setHasMore(false); + } else { + pageRef.current += 1; + } + + for (const content of structureData) { + if (content.user && content.id) { + const res = checkAndUpdateVideo(content); + if (res) { + queue.push(() => getVideo(content.user, content.id, content)); + } + } + } + isLoading.value = false; + } catch (error) { + console.log(error); + isLoading.value = false; + } + }, [checkAndUpdateVideo, getVideo, hashMapVideos, paramName]); + + const getVideosHandlerMount = React.useCallback(async () => { + if (firstFetch.current) return; + firstFetch.current = true; + await getVideos(); + afterFetch.current = true; + }, [getVideos]); + + useEffect(() => { + if (!firstFetch.current) { + getVideosHandlerMount(); + } + }, [getVideosHandlerMount]); + + return ( + + + + + + + ); +}; diff --git a/src/pages/Home/Components/PlayListList.tsx b/src/pages/Home/Components/PlayListList.tsx new file mode 100644 index 0000000..41d8013 --- /dev/null +++ b/src/pages/Home/Components/PlayListList.tsx @@ -0,0 +1,311 @@ +import BlockIcon from "@mui/icons-material/Block"; +import EditIcon from "@mui/icons-material/Edit"; +import { Avatar, Box, Tooltip, Typography, useTheme } from "@mui/material"; +import { useState } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { useNavigate } from "react-router-dom"; +import { PlaylistSVG } from "../../../assets/svgs/PlaylistSVG.tsx"; +import ResponsiveImage from "../../../components/ResponsiveImage.tsx"; +import { fontSizeSmall, minDuration } from "../../../constants/Misc.ts"; +import { + blockUser, + setEditPlaylist, + setEditVideo, + Video, +} from "../../../state/features/videoSlice.ts"; +import { RootState } from "../../../state/store.ts"; +import { formatTime } from "../../../utils/numberFunctions.ts"; +import { formatDate } from "../../../utils/time.ts"; +import { VideoCardImageContainer } from "./VideoCardImageContainer.tsx"; +import { + BlockIconContainer, + BottomParent, + IconsBox, + NameContainer, + VideoCard, + VideoCardCol, + VideoCardContainer, + VideoCardName, + VideoCardTitle, + VideoUploadDate, +} from "./VideoList-styles.tsx"; +import ContextMenuResource from '../../../components/common/ContextMenu/ContextMenuResource' + +interface VideoListProps { + videos: Video[]; +} +export const PlayListList = ({ videos }: VideoListProps) => { + const [showIcons, setShowIcons] = useState(null); + + const hashMapVideos = useSelector( + (state: RootState) => state.video.hashMapVideos + ); + + // ToDo: This needs to be updated for names + const username = useSelector((state: RootState) => state.auth?.user?.name); + + const navigate = useNavigate(); + const dispatch = useDispatch(); + const theme = useTheme(); + + const blockUserFunc = async (user: string) => { + if (user === "Q-Tube") return; + + try { + const response = await qortalRequest({ + action: "ADD_LIST_ITEMS", + list_name: "blockedNames", + items: [user], + }); + + if (response === true) { + dispatch(blockUser(user)); + } + } catch (error) { + console.log(error); + } + }; + + return ( + + {videos.map((video: any) => { + const fullId = video ? `${video.id}-${video.user}` : undefined; + const existingVideo = hashMapVideos[fullId]; + let hasHash = false; + let videoObj = video; + if (existingVideo) { + videoObj = existingVideo; + hasHash = true; + } + // nb. this prevents showing metadata for a video which + // belongs to a different user + if ( + videoObj?.user && + videoObj?.videoReference?.name && + videoObj.user != videoObj.videoReference.name + ) { + return null; + } + + if (hasHash && !videoObj?.videoImage && !videoObj?.image) { + return null; + } + const isPlaylist = videoObj?.service === "PLAYLIST"; + + if (isPlaylist) { + return ( + setShowIcons(videoObj.id)} + onMouseLeave={() => setShowIcons(null)} + > + + {videoObj?.user === username && ( + + + { + dispatch(setEditPlaylist(videoObj)); + }} + /> + + + )} + + {videoObj?.user !== username && ( + + + { + blockUserFunc(videoObj?.user); + }} + /> + + + )} + + + { + if (!hasHash) return; + navigate(`/playlist/${videoObj?.user}/${videoObj?.id}`); + }} + > + + {videoObj?.title} + + { + e.stopPropagation(); + navigate(`/channel/${videoObj?.user}`); + }} + > + + + {videoObj?.user} + + + {videoObj?.created && ( + + {formatDate(videoObj.created)} + + )} + + + + + + + + ); + } + + return ( + + setShowIcons(videoObj.id)} + onMouseLeave={() => setShowIcons(null)} + > + + {videoObj?.user === username && ( + + + { + dispatch(setEditVideo(videoObj)); + }} + /> + + + )} + + {videoObj?.user !== username && ( + + + { + blockUserFunc(videoObj?.user); + }} + /> + + + )} + + { + navigate(`/video/${videoObj?.user}/${videoObj?.id}`); + }} + > + {videoObj?.duration > minDuration && ( + + + {formatTime(videoObj.duration)} + + + )} + + + {videoObj.title} + + + { + e.stopPropagation(); + navigate(`/channel/${videoObj?.user}`); + }} + > + + + {videoObj?.user} + + + {videoObj?.created && ( + + + {formatDate(videoObj.created)} + + + )} + + + + + ); + })} + + ); +}; + +export default PlayListList; diff --git a/src/pages/Home/Components/VideoList.tsx b/src/pages/Home/Components/VideoList.tsx index 32f8e6d..e52658d 100644 --- a/src/pages/Home/Components/VideoList.tsx +++ b/src/pages/Home/Components/VideoList.tsx @@ -68,6 +68,7 @@ export const VideoList = ({ videos }: VideoListProps) => { return ( {videos.map((video: any) => { + //key = video.id; const fullId = video ? `${video.id}-${video.user}` : undefined; const existingVideo = hashMapVideos[fullId]; let hasHash = false; diff --git a/src/pages/Home/Components/VideoListComponentLevel.tsx b/src/pages/Home/Components/VideoListComponentLevel.tsx index 4989101..7ddc68b 100644 --- a/src/pages/Home/Components/VideoListComponentLevel.tsx +++ b/src/pages/Home/Components/VideoListComponentLevel.tsx @@ -28,11 +28,23 @@ export const VideoListComponentLevel = ({ mode }: VideoListProps) => { const [videos, setVideos] = React.useState([]); const isLoading = useSignal(true); const { getVideo, checkAndUpdateVideo } = useFetchVideos(); + // For Pagination + const pageRef = useRef(0); + const [hasMore, setHasMore] = useState(true); + const PAGE_SIZE = 20; + + useEffect(() => { + firstFetch.current = false; + setVideos([]); + pageRef.current = 0; + setHasMore(true); + }, [paramName]); const getVideos = React.useCallback(async () => { isLoading.value = true; try { - const offset = videos.length; + const offset = pageRef.current * PAGE_SIZE; + console.log('getVideos ParamName:', paramName); const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${QTUBE_VIDEO_BASE}&limit=20&includemetadata=false&reverse=true&excludeblocked=true&name=${paramName}&exactmatchnames=true&offset=${offset}`; const response = await fetch(url, { method: "GET", @@ -57,16 +69,24 @@ export const VideoListComponentLevel = ({ mode }: VideoListProps) => { }; }); - const copiedVideos: Video[] = [...videos]; - structureData.forEach((video: Video) => { - const index = videos.findIndex(p => p.id === video.id); - if (index !== -1) { - copiedVideos[index] = video; - } else { - copiedVideos.push(video); - } + setVideos(prev => { + const updatedVideos = [...prev]; + + structureData.forEach(video => { + const exists = updatedVideos.some(v => v.id === video.id); + if (!exists) { + updatedVideos.push(video); + } + }); + return updatedVideos; }); - setVideos(copiedVideos); + + // If fewer than PAGE_SIZE results, we've reached the end + if (structureData.length < PAGE_SIZE) { + setHasMore(false); + } else { + pageRef.current += 1; + } for (const content of structureData) { if (content.user && content.id) { @@ -81,20 +101,20 @@ export const VideoListComponentLevel = ({ mode }: VideoListProps) => { console.log(error); isLoading.value = false; } - }, [videos, hashMapVideos]); - - const getVideosHandlerMount = React.useCallback(async () => { - if (firstFetch.current) return; - firstFetch.current = true; - await getVideos(); - afterFetch.current = true; - }, [getVideos]); + }, [checkAndUpdateVideo, getVideo, hashMapVideos, paramName]); useEffect(() => { + const fetchVideos = async () => { + firstFetch.current = true; + console.log("Running useEffect: " + paramName); + await getVideos(); + afterFetch.current = true; + }; + if (!firstFetch.current) { - getVideosHandlerMount(); + fetchVideos(); } - }, [getVideosHandlerMount]); + }, [paramName, getVideos]); return ( @@ -107,7 +127,10 @@ export const VideoListComponentLevel = ({ mode }: VideoListProps) => { }} > - + ); diff --git a/src/state/features/authSlice.ts b/src/state/features/authSlice.ts index 8f5ee20..b116e15 100644 --- a/src/state/features/authSlice.ts +++ b/src/state/features/authSlice.ts @@ -19,9 +19,14 @@ export const authSlice = createSlice({ addUser: (state, action) => { state.user = action.payload; }, + setName: (state, action) => { + state.user.name = action.payload; + }, }, }); export const { addUser } = authSlice.actions; +export const { setName } = authSlice.actions; + export default authSlice.reducer; \ No newline at end of file diff --git a/src/state/global/names.ts b/src/state/global/names.ts new file mode 100644 index 0000000..7a05a6d --- /dev/null +++ b/src/state/global/names.ts @@ -0,0 +1,8 @@ +import { atom } from 'jotai'; +export interface Name { + name: string; +} + +export type Names = Name[]; + +export const namesAtom = atom([]); \ No newline at end of file diff --git a/src/utils/qortalRequestFunctions.ts b/src/utils/qortalRequestFunctions.ts index c64c0bc..81880a8 100644 --- a/src/utils/qortalRequestFunctions.ts +++ b/src/utils/qortalRequestFunctions.ts @@ -41,6 +41,14 @@ export const getAccountNames = async ( return emptyNamesFilled.length > 0 ? emptyNamesFilled : [namelessAddress]; }; +export const getPrimaryAccountName = async (address: string) => { + const primaryName = (await qortalRequest({ + action: "GET_PRIMARY_NAME", + address, + })) as string | null; + return primaryName ?? ""; +} + export const searchTransactions = async (params: TransactionSearchParams) => { return (await qortalRequest({ action: "SEARCH_TRANSACTIONS", diff --git a/src/wrappers/GlobalWrapper.tsx b/src/wrappers/GlobalWrapper.tsx index 53002e8..768b291 100644 --- a/src/wrappers/GlobalWrapper.tsx +++ b/src/wrappers/GlobalWrapper.tsx @@ -29,6 +29,10 @@ import ConsentModal from "../components/common/ConsentModal"; import { useFetchSuperLikes } from "../hooks/useFetchSuperLikes"; import { SUPER_LIKE_BASE } from "../constants/Identifiers.ts"; import { minPriceSuperLike } from "../constants/Misc.ts"; +import { useHandleNameData } from './../hooks/useHandleNameData.tsx'; +import { namesAtom } from './../state/global/names'; +import { useAtom } from 'jotai'; +import { getPrimaryAccountName } from "../utils/qortalRequestFunctions.ts"; interface Props { children: React.ReactNode; @@ -47,24 +51,26 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { const user = useSelector((state: RootState) => state.auth.user); const { addSuperlikeRawDataGetToList } = useFetchSuperLikes(); const interval = useRef(null); - + useHandleNameData(); + const videoPlaying = useSelector( (state: RootState) => state.global.videoPlaying ); + const username = useMemo(() => { if (!user?.name) return ""; return user.name; }, [user]); + + const [names] = useAtom(namesAtom); + const getAvatar = React.useCallback( async (author: string) => { try { - const url = await qortalRequest({ - action: "GET_QDN_RESOURCE_URL", - name: author, - service: "THUMBNAIL", - identifier: "qortal_avatar", - }); + + const url = `/arbitrary/THUMBNAIL/${author}/qortal_avatar`; + if (url) { setUserAvatar(url); dispatch( @@ -89,27 +95,13 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { const { isLoadingGlobal } = useSelector((state: RootState) => state.global); - async function getNameInfo(address: string) { - const response = await qortalRequest({ - action: "GET_ACCOUNT_NAMES", - address: address, - }); - const nameData = response; - - if (nameData?.length > 0) { - return nameData[0].name; - } else { - return ""; - } - } - const askForAccountInformation = React.useCallback(async () => { try { const account = await qortalRequest({ action: "GET_USER_ACCOUNT", }); - const name = await getNameInfo(account.address); + const name = await getPrimaryAccountName(account.address); dispatch(addUser({ ...account, name })); } catch (error) { console.error(error); @@ -223,6 +215,7 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { setTheme={(val: string) => setTheme(val)} isAuthenticated={!!user?.name} userName={user?.name || ""} + allNames={names} userAvatar={userAvatar} authenticate={askForAccountInformation} /> diff --git a/vite.config.ts b/vite.config.ts index 1070b98..ea18dd2 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,12 +1,13 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; + // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], - server: { - host: '0.0.0.0', - port: 3000 - }, + server: { + host: '0.0.0.0', + port: 3000 + }, base: "", });