mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-16 11:47:54 +01:00
web: add support for setting user profile
This commit is contained in:
201
apps/web/package-lock.json
generated
201
apps/web/package-lock.json
generated
@@ -67,6 +67,7 @@
|
||||
"platform": "^1.3.6",
|
||||
"qclone": "^1.2.0",
|
||||
"react": "18.2.0",
|
||||
"react-avatar-editor": "^13.0.2",
|
||||
"react-complex-tree": "^2.2.4",
|
||||
"react-day-picker": "^8.9.1",
|
||||
"react-dom": "18.2.0",
|
||||
@@ -97,6 +98,7 @@
|
||||
"@types/node-fetch": "^2.5.10",
|
||||
"@types/platform": "^1.3.4",
|
||||
"@types/react": "^18.2.39",
|
||||
"@types/react-avatar-editor": "^13.0.2",
|
||||
"@types/react-dom": "^18.2.17",
|
||||
"@types/react-modal": "3.16.3",
|
||||
"@types/tinycolor2": "^1.4.3",
|
||||
@@ -34707,7 +34709,6 @@
|
||||
},
|
||||
"node_modules/@ampproject/remapping": {
|
||||
"version": "2.2.1",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.0",
|
||||
@@ -34845,7 +34846,6 @@
|
||||
},
|
||||
"node_modules/@babel/compat-data": {
|
||||
"version": "7.23.3",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -34853,7 +34853,6 @@
|
||||
},
|
||||
"node_modules/@babel/core": {
|
||||
"version": "7.23.3",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
@@ -34882,7 +34881,6 @@
|
||||
},
|
||||
"node_modules/@babel/generator": {
|
||||
"version": "7.23.3",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.23.3",
|
||||
@@ -34918,7 +34916,6 @@
|
||||
},
|
||||
"node_modules/@babel/helper-compilation-targets": {
|
||||
"version": "7.22.15",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/compat-data": "^7.22.9",
|
||||
@@ -34986,7 +34983,6 @@
|
||||
},
|
||||
"node_modules/@babel/helper-environment-visitor": {
|
||||
"version": "7.22.20",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -34994,7 +34990,6 @@
|
||||
},
|
||||
"node_modules/@babel/helper-function-name": {
|
||||
"version": "7.23.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.22.15",
|
||||
@@ -35006,7 +35001,6 @@
|
||||
},
|
||||
"node_modules/@babel/helper-hoist-variables": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.22.5"
|
||||
@@ -35027,10 +35021,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-module-imports": {
|
||||
"version": "7.22.15",
|
||||
"license": "MIT",
|
||||
"version": "7.24.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz",
|
||||
"integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.22.15"
|
||||
"@babel/types": "^7.24.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -35038,7 +35033,6 @@
|
||||
},
|
||||
"node_modules/@babel/helper-module-transforms": {
|
||||
"version": "7.23.3",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-environment-visitor": "^7.22.20",
|
||||
@@ -35066,9 +35060,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-plugin-utils": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"version": "7.24.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz",
|
||||
"integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
@@ -35107,7 +35101,6 @@
|
||||
},
|
||||
"node_modules/@babel/helper-simple-access": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.22.5"
|
||||
@@ -35129,7 +35122,6 @@
|
||||
},
|
||||
"node_modules/@babel/helper-split-export-declaration": {
|
||||
"version": "7.22.6",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.22.5"
|
||||
@@ -35139,8 +35131,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
"version": "7.22.5",
|
||||
"license": "MIT",
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz",
|
||||
"integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
@@ -35154,7 +35147,6 @@
|
||||
},
|
||||
"node_modules/@babel/helper-validator-option": {
|
||||
"version": "7.22.15",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -35175,7 +35167,6 @@
|
||||
},
|
||||
"node_modules/@babel/helpers": {
|
||||
"version": "7.23.2",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.22.15",
|
||||
@@ -35257,7 +35248,6 @@
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.23.3",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
@@ -36133,6 +36123,63 @@
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-runtime": {
|
||||
"version": "7.24.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.3.tgz",
|
||||
"integrity": "sha512-J0BuRPNlNqlMTRJ72eVptpt9VcInbxO6iP3jaxr+1NPhC0UkKL+6oeX6VXMEYdADnuqmMmsBspt4d5w8Y/TCbQ==",
|
||||
"dependencies": {
|
||||
"@babel/helper-module-imports": "^7.24.3",
|
||||
"@babel/helper-plugin-utils": "^7.24.0",
|
||||
"babel-plugin-polyfill-corejs2": "^0.4.10",
|
||||
"babel-plugin-polyfill-corejs3": "^0.10.1",
|
||||
"babel-plugin-polyfill-regenerator": "^0.6.1",
|
||||
"semver": "^6.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-runtime/node_modules/@babel/helper-define-polyfill-provider": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.1.tgz",
|
||||
"integrity": "sha512-o7SDgTJuvx5vLKD6SFvkydkSMBvahDKGiNJzG22IZYXhiqoe9efY7zocICBgzHV4IRg5wdgl2nEL/tulKIEIbA==",
|
||||
"dependencies": {
|
||||
"@babel/helper-compilation-targets": "^7.22.6",
|
||||
"@babel/helper-plugin-utils": "^7.22.5",
|
||||
"debug": "^4.1.1",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"resolve": "^1.14.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-runtime/node_modules/babel-plugin-polyfill-corejs3": {
|
||||
"version": "0.10.4",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz",
|
||||
"integrity": "sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==",
|
||||
"dependencies": {
|
||||
"@babel/helper-define-polyfill-provider": "^0.6.1",
|
||||
"core-js-compat": "^3.36.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-runtime/node_modules/babel-plugin-polyfill-regenerator": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.1.tgz",
|
||||
"integrity": "sha512-JfTApdE++cgcTWjsiCQlLyFBMbTUft9ja17saCc93lgV33h4tuCVj7tlvu//qpLwaG+3yEz7/KhahGrUMkVq9g==",
|
||||
"dependencies": {
|
||||
"@babel/helper-define-polyfill-provider": "^0.6.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-shorthand-properties": {
|
||||
"version": "7.23.3",
|
||||
"dev": true,
|
||||
@@ -36386,7 +36433,6 @@
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.22.15",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.22.13",
|
||||
@@ -36399,7 +36445,6 @@
|
||||
},
|
||||
"node_modules/@babel/traverse": {
|
||||
"version": "7.23.3",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.22.13",
|
||||
@@ -36418,10 +36463,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.23.3",
|
||||
"license": "MIT",
|
||||
"version": "7.24.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz",
|
||||
"integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.22.5",
|
||||
"@babel/helper-string-parser": "^7.23.4",
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
},
|
||||
@@ -36632,7 +36678,6 @@
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.3",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/set-array": "^1.0.1",
|
||||
@@ -36645,7 +36690,6 @@
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
"version": "3.1.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
@@ -36653,7 +36697,6 @@
|
||||
},
|
||||
"node_modules/@jridgewell/set-array": {
|
||||
"version": "1.1.2",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
@@ -36670,12 +36713,10 @@
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.4.15",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.20",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.1.0",
|
||||
@@ -38049,6 +38090,15 @@
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-avatar-editor": {
|
||||
"version": "13.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-avatar-editor/-/react-avatar-editor-13.0.2.tgz",
|
||||
"integrity": "sha512-vnGU4sx5TDB9JMCuw+kB3+jfDNMhVlpBbgMRZG9NzVnaBb5xUjVV4VmHdD2O8gj/pb5GN+QXKk7jan09aMjG2A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-dom": {
|
||||
"version": "18.2.19",
|
||||
"dev": true,
|
||||
@@ -38658,18 +38708,33 @@
|
||||
}
|
||||
},
|
||||
"node_modules/babel-plugin-polyfill-corejs2": {
|
||||
"version": "0.4.6",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"version": "0.4.10",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.10.tgz",
|
||||
"integrity": "sha512-rpIuu//y5OX6jVU+a5BCn1R5RSZYWAl2Nar76iwaOdycqb6JPxediskWFMMl7stfwNJR4b7eiQvh5fB5TEQJTQ==",
|
||||
"dependencies": {
|
||||
"@babel/compat-data": "^7.22.6",
|
||||
"@babel/helper-define-polyfill-provider": "^0.4.3",
|
||||
"@babel/helper-define-polyfill-provider": "^0.6.1",
|
||||
"semver": "^6.3.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-plugin-polyfill-corejs2/node_modules/@babel/helper-define-polyfill-provider": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.1.tgz",
|
||||
"integrity": "sha512-o7SDgTJuvx5vLKD6SFvkydkSMBvahDKGiNJzG22IZYXhiqoe9efY7zocICBgzHV4IRg5wdgl2nEL/tulKIEIbA==",
|
||||
"dependencies": {
|
||||
"@babel/helper-compilation-targets": "^7.22.6",
|
||||
"@babel/helper-plugin-utils": "^7.22.5",
|
||||
"debug": "^4.1.1",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"resolve": "^1.14.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-plugin-polyfill-corejs3": {
|
||||
"version": "0.8.6",
|
||||
"dev": true,
|
||||
@@ -38808,8 +38873,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.22.1",
|
||||
"dev": true,
|
||||
"version": "4.23.0",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz",
|
||||
"integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -38824,11 +38890,10 @@
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001541",
|
||||
"electron-to-chromium": "^1.4.535",
|
||||
"node-releases": "^2.0.13",
|
||||
"caniuse-lite": "^1.0.30001587",
|
||||
"electron-to-chromium": "^1.4.668",
|
||||
"node-releases": "^2.0.14",
|
||||
"update-browserslist-db": "^1.0.13"
|
||||
},
|
||||
"bin": {
|
||||
@@ -38954,8 +39019,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001562",
|
||||
"dev": true,
|
||||
"version": "1.0.30001599",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001599.tgz",
|
||||
"integrity": "sha512-LRAQHZ4yT1+f9LemSMeqdMpMxZcc4RMWdj4tiFe3G8tNkWK+E58g+/tzotb5cU6TbcVJLr4fySiAW7XmxQvZQA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -38969,8 +39035,7 @@
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "CC-BY-4.0"
|
||||
]
|
||||
},
|
||||
"node_modules/canvas": {
|
||||
"version": "2.11.2",
|
||||
@@ -39184,15 +39249,14 @@
|
||||
},
|
||||
"node_modules/convert-source-map": {
|
||||
"version": "2.0.0",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/core-js-compat": {
|
||||
"version": "3.33.2",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"version": "3.36.1",
|
||||
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.1.tgz",
|
||||
"integrity": "sha512-Dk997v9ZCt3X/npqzyGdTlq6t7lDBhZwGvV94PKzDArjp7BTRm7WlDAXYd/OWdeFHO8OChQYRJNJvUCqCbrtKA==",
|
||||
"dependencies": {
|
||||
"browserslist": "^4.22.1"
|
||||
"browserslist": "^4.23.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
@@ -39572,9 +39636,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.585",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
"version": "1.4.713",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.713.tgz",
|
||||
"integrity": "sha512-vDarADhwntXiULEdmWd77g2dV6FrNGa8ecAC29MZ4TwPut2fvosD0/5sJd1qWNNe8HcJFAC+F5Lf9jW1NPtWmw=="
|
||||
},
|
||||
"node_modules/electron-trpc": {
|
||||
"version": "0.5.2",
|
||||
@@ -39955,7 +40019,6 @@
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.1.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
@@ -40400,7 +40463,6 @@
|
||||
},
|
||||
"node_modules/gensync": {
|
||||
"version": "1.0.0-beta.2",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -40513,7 +40575,6 @@
|
||||
},
|
||||
"node_modules/globals": {
|
||||
"version": "11.12.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
@@ -41317,7 +41378,6 @@
|
||||
},
|
||||
"node_modules/jsesc": {
|
||||
"version": "2.5.2",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"jsesc": "bin/jsesc"
|
||||
@@ -41353,7 +41413,6 @@
|
||||
},
|
||||
"node_modules/json5": {
|
||||
"version": "2.2.3",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"json5": "lib/cli.js"
|
||||
@@ -41680,7 +41739,6 @@
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "5.1.1",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"yallist": "^3.0.2"
|
||||
@@ -42771,9 +42829,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.13",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"version": "2.0.14",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
|
||||
"integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw=="
|
||||
},
|
||||
"node_modules/nopt": {
|
||||
"version": "5.0.0",
|
||||
@@ -43029,7 +43087,6 @@
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.0.0",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
@@ -43331,6 +43388,20 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-avatar-editor": {
|
||||
"version": "13.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-avatar-editor/-/react-avatar-editor-13.0.2.tgz",
|
||||
"integrity": "sha512-a4ajbi7lwDh98kgEtSEeKMu0vs0CHTczkq4Xcxr1EiwMFH1GlgHCEtwGU8q/H5W8SeLnH4KPK8LUjEEaZXklxQ==",
|
||||
"dependencies": {
|
||||
"@babel/plugin-transform-runtime": "^7.12.1",
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"prop-types": "^15.7.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^0.14.0 || ^16.0.0 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^0.14.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-complex-tree": {
|
||||
"version": "2.3.4",
|
||||
"license": "MIT",
|
||||
@@ -44916,7 +44987,6 @@
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.0.13",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -45902,7 +45972,6 @@
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "3.1.1",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
"platform": "^1.3.6",
|
||||
"qclone": "^1.2.0",
|
||||
"react": "18.2.0",
|
||||
"react-avatar-editor": "^13.0.2",
|
||||
"react-complex-tree": "^2.2.4",
|
||||
"react-day-picker": "^8.9.1",
|
||||
"react-dom": "18.2.0",
|
||||
@@ -95,6 +96,7 @@
|
||||
"@types/node-fetch": "^2.5.10",
|
||||
"@types/platform": "^1.3.4",
|
||||
"@types/react": "^18.2.39",
|
||||
"@types/react-avatar-editor": "^13.0.2",
|
||||
"@types/react-dom": "^18.2.17",
|
||||
"@types/react-modal": "3.16.3",
|
||||
"@types/tinycolor2": "^1.4.3",
|
||||
|
||||
@@ -33,8 +33,13 @@ import { ConfirmDialogProps } from "../dialogs/confirm";
|
||||
import { getFormattedDate } from "@notesnook/common";
|
||||
import { downloadUpdate } from "../utils/updater";
|
||||
import { ThemeMetadata } from "@notesnook/themes-server";
|
||||
import { Color, Reminder, Tag } from "@notesnook/core";
|
||||
import { AuthenticatorType } from "@notesnook/core/dist/api/user-manager";
|
||||
import {
|
||||
Color,
|
||||
Profile,
|
||||
Reminder,
|
||||
Tag,
|
||||
AuthenticatorType
|
||||
} from "@notesnook/core";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { PasswordDialogProps } from "../dialogs/password-dialog";
|
||||
|
||||
@@ -476,6 +481,12 @@ export function showAttachmentsDialog() {
|
||||
));
|
||||
}
|
||||
|
||||
export function showEditProfileDialog(profile?: Profile) {
|
||||
return showDialog("EditProfileDialog", (Dialog, perform) => (
|
||||
<Dialog onClose={(res: boolean) => perform(res)} profile={profile} />
|
||||
));
|
||||
}
|
||||
|
||||
export function showSettings() {
|
||||
return showDialog("SettingsDialog", (Dialog, perform) => (
|
||||
<Dialog onClose={(res: boolean) => perform(res)} />
|
||||
|
||||
@@ -35,7 +35,8 @@ import {
|
||||
Login,
|
||||
Circle,
|
||||
Icon,
|
||||
Reminders
|
||||
Reminders,
|
||||
User
|
||||
} from "../icons";
|
||||
import { AnimatedFlex } from "../animated";
|
||||
import NavigationItem, { SortableNavigationItem } from "./navigation-item";
|
||||
@@ -134,6 +135,7 @@ function NavigationMenu(props: NavigationMenuProps) {
|
||||
const shortcuts = useAppStore((store) => store.shortcuts);
|
||||
const refreshNavItems = useAppStore((store) => store.refreshNavItems);
|
||||
const isLoggedIn = useUserStore((store) => store.isLoggedIn);
|
||||
const profile = useUserStore((store) => store.profile);
|
||||
const isMobile = useMobile();
|
||||
const theme = useThemeStore((store) => store.colorScheme);
|
||||
const toggleNightMode = useThemeStore((store) => store.toggleColorScheme);
|
||||
@@ -463,8 +465,9 @@ function NavigationMenu(props: NavigationMenuProps) {
|
||||
id={settings.id}
|
||||
isTablet={isTablet}
|
||||
key={settings.path}
|
||||
title={settings.title}
|
||||
icon={settings.icon}
|
||||
title={profile?.fullName || settings.title}
|
||||
icon={profile?.fullName ? User : settings.icon}
|
||||
image={profile?.fullName ? profile?.profilePicture : undefined}
|
||||
onClick={() => {
|
||||
if (!isMobile && location === settings.path)
|
||||
return toggleNavigationContainer();
|
||||
|
||||
@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Button, Flex, FlexProps, Text } from "@theme-ui/components";
|
||||
import { Button, Flex, FlexProps, Image, Text } from "@theme-ui/components";
|
||||
import { useStore as useAppStore } from "../../stores/app-store";
|
||||
import { Menu } from "../../hooks/use-menu";
|
||||
import useMobile from "../../hooks/use-mobile";
|
||||
@@ -29,7 +29,8 @@ import { useSortable } from "@dnd-kit/sortable";
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
|
||||
type NavigationItemProps = {
|
||||
icon: Icon;
|
||||
icon?: Icon;
|
||||
image?: string;
|
||||
color?: SchemeColors;
|
||||
title: string;
|
||||
isTablet?: boolean;
|
||||
@@ -49,6 +50,7 @@ function NavigationItem(
|
||||
) {
|
||||
const {
|
||||
icon: Icon,
|
||||
image,
|
||||
color,
|
||||
title,
|
||||
isLoading,
|
||||
@@ -121,11 +123,15 @@ function NavigationItem(
|
||||
if (onClick) onClick();
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
size={isTablet ? 16 : 15}
|
||||
color={color || (selected ? "icon-selected" : "icon")}
|
||||
rotate={isLoading}
|
||||
/>
|
||||
{image ? (
|
||||
<Image src={image} sx={{ borderRadius: 50, size: 20 }} />
|
||||
) : Icon ? (
|
||||
<Icon
|
||||
size={isTablet ? 16 : 15}
|
||||
color={color || (selected ? "icon-selected" : "icon")}
|
||||
rotate={isLoading}
|
||||
/>
|
||||
) : null}
|
||||
{isShortcut && (
|
||||
<Shortcut
|
||||
size={8}
|
||||
|
||||
166
apps/web/src/dialogs/edit-profile-dialog.tsx
Normal file
166
apps/web/src/dialogs/edit-profile-dialog.tsx
Normal file
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Perform } from "../common/dialog-controller";
|
||||
import Dialog from "../components/dialog";
|
||||
import { Profile } from "@notesnook/core";
|
||||
import Field from "../components/field";
|
||||
import AvatarEditor from "react-avatar-editor";
|
||||
import { Avatar, Button, Flex, Slider } from "@theme-ui/components";
|
||||
import { User } from "../components/icons";
|
||||
import { useRef, useState } from "react";
|
||||
import { showFilePicker } from "../utils/file-picker";
|
||||
import { db } from "../common/db";
|
||||
import { useStore as useUserStore } from "../stores/user-store";
|
||||
import { showToast } from "../utils/toast";
|
||||
|
||||
export type EditProfileDialogProps = {
|
||||
onClose: Perform;
|
||||
profile?: Profile;
|
||||
};
|
||||
|
||||
export default function EditProfileDialog(props: EditProfileDialogProps) {
|
||||
const { profile } = props;
|
||||
const [profilePicture, setProfilePicture] = useState<
|
||||
File | string | undefined
|
||||
>(profile?.profilePicture);
|
||||
const profileRef = useRef<AvatarEditor>(null);
|
||||
const [fullName, setFullName] = useState<string | undefined>(
|
||||
profile?.fullName
|
||||
);
|
||||
const [scale, setScale] = useState(1);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [clearProfilePicture, setClearProfilePicture] = useState(false);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
isOpen={true}
|
||||
title={"Edit profile"}
|
||||
description="Your profile data is stored 100% end-to-end encrypted."
|
||||
onClose={() => props.onClose(false)}
|
||||
positiveButton={{
|
||||
loading: isLoading,
|
||||
disabled: isLoading,
|
||||
text: "Save",
|
||||
onClick: async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await db.user.setProfile({
|
||||
fullName,
|
||||
profilePicture: profileRef.current
|
||||
? profileRef.current
|
||||
.getImageScaledToCanvas()
|
||||
.toDataURL("image/jpeg", 1)
|
||||
: clearProfilePicture
|
||||
? undefined
|
||||
: profile?.profilePicture
|
||||
});
|
||||
await useUserStore.getState().refreshUser();
|
||||
showToast("success", "Profile updated!");
|
||||
props.onClose(true);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
showToast("error", (e as Error).message);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
}}
|
||||
width={400}
|
||||
negativeButton={{ text: "Cancel", onClick: () => props.onClose(false) }}
|
||||
>
|
||||
<Flex sx={{ gap: 2, mt: 2 }}>
|
||||
<Flex sx={{ flexDirection: "column" }}>
|
||||
{profilePicture ? (
|
||||
<AvatarEditor
|
||||
ref={profileRef}
|
||||
image={profilePicture}
|
||||
width={150}
|
||||
height={150}
|
||||
border={0}
|
||||
color={[255, 255, 255]}
|
||||
borderRadius={100}
|
||||
scale={scale}
|
||||
style={{
|
||||
width: 150,
|
||||
height: 150
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Flex
|
||||
variant="columnCenter"
|
||||
sx={{
|
||||
bg: "shade",
|
||||
mr: 2,
|
||||
size: 150,
|
||||
borderRadius: 200,
|
||||
alignSelf: "center"
|
||||
}}
|
||||
>
|
||||
<User size={60} />
|
||||
</Flex>
|
||||
)}
|
||||
<Flex sx={{ gap: 1, alignItems: "center", mt: 2 }}>
|
||||
<Button
|
||||
sx={{ flex: 1 }}
|
||||
variant="secondary"
|
||||
onClick={async () =>
|
||||
setProfilePicture(
|
||||
await showFilePicker({ acceptedFileTypes: "image/*" })
|
||||
)
|
||||
}
|
||||
>
|
||||
{profilePicture ? "Change" : "Set picture"}
|
||||
</Button>
|
||||
{profilePicture ? (
|
||||
<Button
|
||||
sx={{ flex: 1 }}
|
||||
variant="secondary"
|
||||
onClick={async () => {
|
||||
if (profile?.profilePicture) setClearProfilePicture(true);
|
||||
setProfilePicture(undefined);
|
||||
setScale(1);
|
||||
}}
|
||||
>
|
||||
{profile?.profilePicture ? "Clear" : "Reset"}
|
||||
</Button>
|
||||
) : null}
|
||||
</Flex>
|
||||
{profilePicture ? (
|
||||
<Slider
|
||||
max={5}
|
||||
min={1}
|
||||
step={0.1}
|
||||
value={scale}
|
||||
sx={{ color: "accent" }}
|
||||
onChange={(e) => setScale(e.target.valueAsNumber)}
|
||||
/>
|
||||
) : null}
|
||||
</Flex>
|
||||
<Field
|
||||
label="Full name"
|
||||
maxLength={200}
|
||||
value={fullName}
|
||||
autoFocus
|
||||
onChange={(e) => setFullName(e.target.value)}
|
||||
/>
|
||||
</Flex>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -56,6 +56,7 @@ const BackupPasswordDialog = React.lazy(
|
||||
() => import("./backup-password-dialog")
|
||||
);
|
||||
const CreateColorDialog = React.lazy(() => import("./create-color-dialog"));
|
||||
const EditProfileDialog = React.lazy(() => import("./edit-profile-dialog"));
|
||||
|
||||
export const Dialogs = {
|
||||
AddNotebookDialog,
|
||||
@@ -85,5 +86,6 @@ export const Dialogs = {
|
||||
SettingsDialog,
|
||||
ThemeDetailsDialog,
|
||||
BackupPasswordDialog,
|
||||
CreateColorDialog
|
||||
CreateColorDialog,
|
||||
EditProfileDialog
|
||||
};
|
||||
|
||||
@@ -17,17 +17,19 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Flex, Text } from "@theme-ui/components";
|
||||
import { User } from "../../../components/icons";
|
||||
import { Button, Flex, Image, Text } from "@theme-ui/components";
|
||||
import { Edit, User } from "../../../components/icons";
|
||||
import { useStore as useUserStore } from "../../../stores/user-store";
|
||||
import { getObjectIdTimestamp } from "@notesnook/core/dist/utils/object-id";
|
||||
import { getFormattedDate } from "@notesnook/common";
|
||||
import { SUBSCRIPTION_STATUS } from "../../../common/constants";
|
||||
import dayjs from "dayjs";
|
||||
import { useMemo } from "react";
|
||||
import { showEditProfileDialog } from "../../../common/dialog-controller";
|
||||
|
||||
export function UserProfile() {
|
||||
const user = useUserStore((store) => store.user);
|
||||
const profile = useUserStore((store) => store.profile);
|
||||
|
||||
const {
|
||||
isTrial,
|
||||
@@ -88,42 +90,82 @@ export function UserProfile() {
|
||||
sx={{
|
||||
borderRadius: "default",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
bg: "var(--background-secondary)",
|
||||
p: 2,
|
||||
mb: 4
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
variant="columnCenter"
|
||||
sx={{
|
||||
bg: "shade",
|
||||
mr: 2,
|
||||
size: 60,
|
||||
borderRadius: 80
|
||||
}}
|
||||
>
|
||||
<User size={30} />
|
||||
</Flex>
|
||||
<Flex sx={{ flexDirection: "column" }}>
|
||||
<Text
|
||||
variant="subBody"
|
||||
<Flex sx={{ alignItems: "center" }}>
|
||||
<Flex
|
||||
variant="columnCenter"
|
||||
sx={{
|
||||
color: "accent"
|
||||
bg: "shade",
|
||||
mr: 2,
|
||||
size: 60,
|
||||
borderRadius: 80,
|
||||
overflow: "hidden"
|
||||
}}
|
||||
>
|
||||
{remainingDays > 0 && (isPro || isProCancelled)
|
||||
? `PRO`
|
||||
: remainingDays > 0 && isTrial
|
||||
? "TRIAL"
|
||||
: isBeta
|
||||
? "BETA TESTER"
|
||||
: "BASIC"}
|
||||
</Text>
|
||||
<Text variant={"title"}>{user.email}</Text>
|
||||
<Text variant={"subBody"}>
|
||||
Member since {getFormattedDate(getObjectIdTimestamp(user.id), "date")}
|
||||
</Text>
|
||||
{profile?.profilePicture ? (
|
||||
<Image
|
||||
sx={{ width: "100%", height: "100%", objectFit: "contain" }}
|
||||
src={profile.profilePicture}
|
||||
/>
|
||||
) : (
|
||||
<User size={30} />
|
||||
)}
|
||||
</Flex>
|
||||
<Flex sx={{ flexDirection: "column" }}>
|
||||
<Text
|
||||
variant="subBody"
|
||||
sx={{
|
||||
color: "accent"
|
||||
}}
|
||||
>
|
||||
{remainingDays > 0 && (isPro || isProCancelled)
|
||||
? `PRO`
|
||||
: remainingDays > 0 && isTrial
|
||||
? "TRIAL"
|
||||
: isBeta
|
||||
? "BETA TESTER"
|
||||
: "BASIC"}
|
||||
</Text>
|
||||
|
||||
{profile?.fullName ? (
|
||||
<>
|
||||
<Text variant={"title"}>{profile?.fullName}</Text>
|
||||
<Text variant={"subBody"}>
|
||||
{user.email} • Member since{" "}
|
||||
{getFormattedDate(getObjectIdTimestamp(user.id), "date")}
|
||||
</Text>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Text variant={"title"}>{user.email}</Text>
|
||||
<Text variant={"subBody"}>
|
||||
Member since
|
||||
{getFormattedDate(getObjectIdTimestamp(user.id), "date")}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Button
|
||||
variant="icon"
|
||||
sx={{
|
||||
borderRadius: 50,
|
||||
p: 0,
|
||||
m: 0,
|
||||
width: 30,
|
||||
height: 30,
|
||||
alignSelf: "end"
|
||||
}}
|
||||
title="Edit profile"
|
||||
onClick={() => showEditProfileDialog(profile)}
|
||||
>
|
||||
<Edit size={18} />
|
||||
</Button>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ import { hashNavigate } from "../navigation";
|
||||
import { isUserPremium } from "../hooks/use-is-user-premium";
|
||||
import { SUBSCRIPTION_STATUS } from "../common/constants";
|
||||
import { ANALYTICS_EVENTS, trackEvent } from "../utils/analytics";
|
||||
import { User } from "@notesnook/core/dist/api/user-manager";
|
||||
import { AuthenticatorType, Profile, User } from "@notesnook/core";
|
||||
|
||||
class UserStore extends BaseStore<UserStore> {
|
||||
isLoggedIn?: boolean;
|
||||
@@ -39,6 +39,7 @@ class UserStore extends BaseStore<UserStore> {
|
||||
isSigningIn = false;
|
||||
|
||||
user?: User = undefined;
|
||||
profile?: Profile;
|
||||
counter = 0;
|
||||
|
||||
init = () => {
|
||||
@@ -47,32 +48,35 @@ class UserStore extends BaseStore<UserStore> {
|
||||
window.location.replace("/sessionexpired");
|
||||
});
|
||||
|
||||
db.user.getUser().then(async (user) => {
|
||||
db.user.getUser().then((user) => {
|
||||
if (!user) {
|
||||
this.set((state) => {
|
||||
state.isLoggedIn = false;
|
||||
});
|
||||
this.set({ isLoggedIn: false });
|
||||
return;
|
||||
}
|
||||
this.set((state) => {
|
||||
state.user = user;
|
||||
state.isLoggedIn = true;
|
||||
this.set({
|
||||
user,
|
||||
isLoggedIn: true
|
||||
});
|
||||
if (Config.get("sessionExpired")) EV.publish(EVENTS.userSessionExpired);
|
||||
});
|
||||
|
||||
db.user.getProfile().then((profile) => this.set({ profile }));
|
||||
|
||||
if (Config.get("sessionExpired")) return;
|
||||
|
||||
return db.user.fetchUser().then(async (user) => {
|
||||
if (!user) return false;
|
||||
|
||||
EV.remove(EVENTS.userSubscriptionUpdated, EVENTS.userEmailConfirmed);
|
||||
const profile = await db.user.getProfile();
|
||||
|
||||
this.set((state) => {
|
||||
state.user = user;
|
||||
state.isLoggedIn = true;
|
||||
this.set({
|
||||
profile,
|
||||
user,
|
||||
isLoggedIn: true
|
||||
});
|
||||
|
||||
EV.remove(EVENTS.userSubscriptionUpdated, EVENTS.userEmailConfirmed);
|
||||
|
||||
EV.subscribe(EVENTS.userSubscriptionUpdated, (subscription) => {
|
||||
const wasUserPremium = isUserPremium();
|
||||
this.set((state) => {
|
||||
@@ -107,18 +111,29 @@ class UserStore extends BaseStore<UserStore> {
|
||||
|
||||
refreshUser = async () => {
|
||||
return db.user.fetchUser().then(async (user) => {
|
||||
this.set((state) => (state.user = user));
|
||||
const profile = await db.user.getProfile();
|
||||
this.set({ user, profile });
|
||||
});
|
||||
};
|
||||
|
||||
login = async (form, skipInit = false, sessionExpired = false) => {
|
||||
login = async (
|
||||
form:
|
||||
| { email: string }
|
||||
| { email: string; password: string }
|
||||
| { code: string; method: AuthenticatorType },
|
||||
skipInit = false,
|
||||
sessionExpired = false
|
||||
) => {
|
||||
this.set((state) => (state.isLoggingIn = true));
|
||||
const { email, password, code, method } = form;
|
||||
|
||||
try {
|
||||
if (code) {
|
||||
if ("email" in form && !("password" in form)) {
|
||||
return await db.user.authenticateEmail(form.email);
|
||||
} else if ("code" in form) {
|
||||
const { code, method } = form;
|
||||
return await db.user.authenticateMultiFactorCode(code, method);
|
||||
} else if (password) {
|
||||
} else if ("password" in form) {
|
||||
const { email, password } = form;
|
||||
await db.user.authenticatePassword(
|
||||
email,
|
||||
password,
|
||||
@@ -129,15 +144,13 @@ class UserStore extends BaseStore<UserStore> {
|
||||
|
||||
if (skipInit) return true;
|
||||
return this.init();
|
||||
} else if (email) {
|
||||
return await db.user.authenticateEmail(email);
|
||||
}
|
||||
} finally {
|
||||
this.set((state) => (state.isLoggingIn = false));
|
||||
}
|
||||
};
|
||||
|
||||
signup = (form) => {
|
||||
signup = (form: { email: string; password: string }) => {
|
||||
this.set((state) => (state.isSigningIn = true));
|
||||
return db.user
|
||||
.signup(form.email.toLowerCase(), form.password)
|
||||
|
||||
Reference in New Issue
Block a user