web: add support for setting user profile

This commit is contained in:
Abdullah Atta
2024-02-28 23:21:52 +05:00
parent e58fc38a6d
commit 54ba654b97
9 changed files with 442 additions and 128 deletions

View File

@@ -67,6 +67,7 @@
"platform": "^1.3.6", "platform": "^1.3.6",
"qclone": "^1.2.0", "qclone": "^1.2.0",
"react": "18.2.0", "react": "18.2.0",
"react-avatar-editor": "^13.0.2",
"react-complex-tree": "^2.2.4", "react-complex-tree": "^2.2.4",
"react-day-picker": "^8.9.1", "react-day-picker": "^8.9.1",
"react-dom": "18.2.0", "react-dom": "18.2.0",
@@ -97,6 +98,7 @@
"@types/node-fetch": "^2.5.10", "@types/node-fetch": "^2.5.10",
"@types/platform": "^1.3.4", "@types/platform": "^1.3.4",
"@types/react": "^18.2.39", "@types/react": "^18.2.39",
"@types/react-avatar-editor": "^13.0.2",
"@types/react-dom": "^18.2.17", "@types/react-dom": "^18.2.17",
"@types/react-modal": "3.16.3", "@types/react-modal": "3.16.3",
"@types/tinycolor2": "^1.4.3", "@types/tinycolor2": "^1.4.3",
@@ -34707,7 +34709,6 @@
}, },
"node_modules/@ampproject/remapping": { "node_modules/@ampproject/remapping": {
"version": "2.2.1", "version": "2.2.1",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/gen-mapping": "^0.3.0",
@@ -34845,7 +34846,6 @@
}, },
"node_modules/@babel/compat-data": { "node_modules/@babel/compat-data": {
"version": "7.23.3", "version": "7.23.3",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@@ -34853,7 +34853,6 @@
}, },
"node_modules/@babel/core": { "node_modules/@babel/core": {
"version": "7.23.3", "version": "7.23.3",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@ampproject/remapping": "^2.2.0", "@ampproject/remapping": "^2.2.0",
@@ -34882,7 +34881,6 @@
}, },
"node_modules/@babel/generator": { "node_modules/@babel/generator": {
"version": "7.23.3", "version": "7.23.3",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/types": "^7.23.3", "@babel/types": "^7.23.3",
@@ -34918,7 +34916,6 @@
}, },
"node_modules/@babel/helper-compilation-targets": { "node_modules/@babel/helper-compilation-targets": {
"version": "7.22.15", "version": "7.22.15",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/compat-data": "^7.22.9", "@babel/compat-data": "^7.22.9",
@@ -34986,7 +34983,6 @@
}, },
"node_modules/@babel/helper-environment-visitor": { "node_modules/@babel/helper-environment-visitor": {
"version": "7.22.20", "version": "7.22.20",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@@ -34994,7 +34990,6 @@
}, },
"node_modules/@babel/helper-function-name": { "node_modules/@babel/helper-function-name": {
"version": "7.23.0", "version": "7.23.0",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/template": "^7.22.15", "@babel/template": "^7.22.15",
@@ -35006,7 +35001,6 @@
}, },
"node_modules/@babel/helper-hoist-variables": { "node_modules/@babel/helper-hoist-variables": {
"version": "7.22.5", "version": "7.22.5",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/types": "^7.22.5" "@babel/types": "^7.22.5"
@@ -35027,10 +35021,11 @@
} }
}, },
"node_modules/@babel/helper-module-imports": { "node_modules/@babel/helper-module-imports": {
"version": "7.22.15", "version": "7.24.3",
"license": "MIT", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz",
"integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==",
"dependencies": { "dependencies": {
"@babel/types": "^7.22.15" "@babel/types": "^7.24.0"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@@ -35038,7 +35033,6 @@
}, },
"node_modules/@babel/helper-module-transforms": { "node_modules/@babel/helper-module-transforms": {
"version": "7.23.3", "version": "7.23.3",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-environment-visitor": "^7.22.20",
@@ -35066,9 +35060,9 @@
} }
}, },
"node_modules/@babel/helper-plugin-utils": { "node_modules/@babel/helper-plugin-utils": {
"version": "7.22.5", "version": "7.24.0",
"dev": true, "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz",
"license": "MIT", "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==",
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
@@ -35107,7 +35101,6 @@
}, },
"node_modules/@babel/helper-simple-access": { "node_modules/@babel/helper-simple-access": {
"version": "7.22.5", "version": "7.22.5",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/types": "^7.22.5" "@babel/types": "^7.22.5"
@@ -35129,7 +35122,6 @@
}, },
"node_modules/@babel/helper-split-export-declaration": { "node_modules/@babel/helper-split-export-declaration": {
"version": "7.22.6", "version": "7.22.6",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/types": "^7.22.5" "@babel/types": "^7.22.5"
@@ -35139,8 +35131,9 @@
} }
}, },
"node_modules/@babel/helper-string-parser": { "node_modules/@babel/helper-string-parser": {
"version": "7.22.5", "version": "7.24.1",
"license": "MIT", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz",
"integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==",
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
@@ -35154,7 +35147,6 @@
}, },
"node_modules/@babel/helper-validator-option": { "node_modules/@babel/helper-validator-option": {
"version": "7.22.15", "version": "7.22.15",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@@ -35175,7 +35167,6 @@
}, },
"node_modules/@babel/helpers": { "node_modules/@babel/helpers": {
"version": "7.23.2", "version": "7.23.2",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/template": "^7.22.15", "@babel/template": "^7.22.15",
@@ -35257,7 +35248,6 @@
}, },
"node_modules/@babel/parser": { "node_modules/@babel/parser": {
"version": "7.23.3", "version": "7.23.3",
"dev": true,
"license": "MIT", "license": "MIT",
"bin": { "bin": {
"parser": "bin/babel-parser.js" "parser": "bin/babel-parser.js"
@@ -36133,6 +36123,63 @@
"@babel/core": "^7.0.0-0" "@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": { "node_modules/@babel/plugin-transform-shorthand-properties": {
"version": "7.23.3", "version": "7.23.3",
"dev": true, "dev": true,
@@ -36386,7 +36433,6 @@
}, },
"node_modules/@babel/template": { "node_modules/@babel/template": {
"version": "7.22.15", "version": "7.22.15",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.22.13", "@babel/code-frame": "^7.22.13",
@@ -36399,7 +36445,6 @@
}, },
"node_modules/@babel/traverse": { "node_modules/@babel/traverse": {
"version": "7.23.3", "version": "7.23.3",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.22.13", "@babel/code-frame": "^7.22.13",
@@ -36418,10 +36463,11 @@
} }
}, },
"node_modules/@babel/types": { "node_modules/@babel/types": {
"version": "7.23.3", "version": "7.24.0",
"license": "MIT", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz",
"integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==",
"dependencies": { "dependencies": {
"@babel/helper-string-parser": "^7.22.5", "@babel/helper-string-parser": "^7.23.4",
"@babel/helper-validator-identifier": "^7.22.20", "@babel/helper-validator-identifier": "^7.22.20",
"to-fast-properties": "^2.0.0" "to-fast-properties": "^2.0.0"
}, },
@@ -36632,7 +36678,6 @@
}, },
"node_modules/@jridgewell/gen-mapping": { "node_modules/@jridgewell/gen-mapping": {
"version": "0.3.3", "version": "0.3.3",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jridgewell/set-array": "^1.0.1", "@jridgewell/set-array": "^1.0.1",
@@ -36645,7 +36690,6 @@
}, },
"node_modules/@jridgewell/resolve-uri": { "node_modules/@jridgewell/resolve-uri": {
"version": "3.1.1", "version": "3.1.1",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6.0.0" "node": ">=6.0.0"
@@ -36653,7 +36697,6 @@
}, },
"node_modules/@jridgewell/set-array": { "node_modules/@jridgewell/set-array": {
"version": "1.1.2", "version": "1.1.2",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6.0.0" "node": ">=6.0.0"
@@ -36670,12 +36713,10 @@
}, },
"node_modules/@jridgewell/sourcemap-codec": { "node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.15", "version": "1.4.15",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@jridgewell/trace-mapping": { "node_modules/@jridgewell/trace-mapping": {
"version": "0.3.20", "version": "0.3.20",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/resolve-uri": "^3.1.0",
@@ -38049,6 +38090,15 @@
"csstype": "^3.0.2" "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": { "node_modules/@types/react-dom": {
"version": "18.2.19", "version": "18.2.19",
"dev": true, "dev": true,
@@ -38658,18 +38708,33 @@
} }
}, },
"node_modules/babel-plugin-polyfill-corejs2": { "node_modules/babel-plugin-polyfill-corejs2": {
"version": "0.4.6", "version": "0.4.10",
"dev": true, "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.10.tgz",
"license": "MIT", "integrity": "sha512-rpIuu//y5OX6jVU+a5BCn1R5RSZYWAl2Nar76iwaOdycqb6JPxediskWFMMl7stfwNJR4b7eiQvh5fB5TEQJTQ==",
"dependencies": { "dependencies": {
"@babel/compat-data": "^7.22.6", "@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" "semver": "^6.3.1"
}, },
"peerDependencies": { "peerDependencies": {
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" "@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": { "node_modules/babel-plugin-polyfill-corejs3": {
"version": "0.8.6", "version": "0.8.6",
"dev": true, "dev": true,
@@ -38808,8 +38873,9 @@
} }
}, },
"node_modules/browserslist": { "node_modules/browserslist": {
"version": "4.22.1", "version": "4.23.0",
"dev": true, "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz",
"integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@@ -38824,11 +38890,10 @@
"url": "https://github.com/sponsors/ai" "url": "https://github.com/sponsors/ai"
} }
], ],
"license": "MIT",
"dependencies": { "dependencies": {
"caniuse-lite": "^1.0.30001541", "caniuse-lite": "^1.0.30001587",
"electron-to-chromium": "^1.4.535", "electron-to-chromium": "^1.4.668",
"node-releases": "^2.0.13", "node-releases": "^2.0.14",
"update-browserslist-db": "^1.0.13" "update-browserslist-db": "^1.0.13"
}, },
"bin": { "bin": {
@@ -38954,8 +39019,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001562", "version": "1.0.30001599",
"dev": true, "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001599.tgz",
"integrity": "sha512-LRAQHZ4yT1+f9LemSMeqdMpMxZcc4RMWdj4tiFe3G8tNkWK+E58g+/tzotb5cU6TbcVJLr4fySiAW7XmxQvZQA==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@@ -38969,8 +39035,7 @@
"type": "github", "type": "github",
"url": "https://github.com/sponsors/ai" "url": "https://github.com/sponsors/ai"
} }
], ]
"license": "CC-BY-4.0"
}, },
"node_modules/canvas": { "node_modules/canvas": {
"version": "2.11.2", "version": "2.11.2",
@@ -39184,15 +39249,14 @@
}, },
"node_modules/convert-source-map": { "node_modules/convert-source-map": {
"version": "2.0.0", "version": "2.0.0",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/core-js-compat": { "node_modules/core-js-compat": {
"version": "3.33.2", "version": "3.36.1",
"dev": true, "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.1.tgz",
"license": "MIT", "integrity": "sha512-Dk997v9ZCt3X/npqzyGdTlq6t7lDBhZwGvV94PKzDArjp7BTRm7WlDAXYd/OWdeFHO8OChQYRJNJvUCqCbrtKA==",
"dependencies": { "dependencies": {
"browserslist": "^4.22.1" "browserslist": "^4.23.0"
}, },
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
@@ -39572,9 +39636,9 @@
} }
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.4.585", "version": "1.4.713",
"dev": true, "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.713.tgz",
"license": "ISC" "integrity": "sha512-vDarADhwntXiULEdmWd77g2dV6FrNGa8ecAC29MZ4TwPut2fvosD0/5sJd1qWNNe8HcJFAC+F5Lf9jW1NPtWmw=="
}, },
"node_modules/electron-trpc": { "node_modules/electron-trpc": {
"version": "0.5.2", "version": "0.5.2",
@@ -39955,7 +40019,6 @@
}, },
"node_modules/escalade": { "node_modules/escalade": {
"version": "3.1.1", "version": "3.1.1",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6" "node": ">=6"
@@ -40400,7 +40463,6 @@
}, },
"node_modules/gensync": { "node_modules/gensync": {
"version": "1.0.0-beta.2", "version": "1.0.0-beta.2",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@@ -40513,7 +40575,6 @@
}, },
"node_modules/globals": { "node_modules/globals": {
"version": "11.12.0", "version": "11.12.0",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=4" "node": ">=4"
@@ -41317,7 +41378,6 @@
}, },
"node_modules/jsesc": { "node_modules/jsesc": {
"version": "2.5.2", "version": "2.5.2",
"dev": true,
"license": "MIT", "license": "MIT",
"bin": { "bin": {
"jsesc": "bin/jsesc" "jsesc": "bin/jsesc"
@@ -41353,7 +41413,6 @@
}, },
"node_modules/json5": { "node_modules/json5": {
"version": "2.2.3", "version": "2.2.3",
"dev": true,
"license": "MIT", "license": "MIT",
"bin": { "bin": {
"json5": "lib/cli.js" "json5": "lib/cli.js"
@@ -41680,7 +41739,6 @@
}, },
"node_modules/lru-cache": { "node_modules/lru-cache": {
"version": "5.1.1", "version": "5.1.1",
"dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"yallist": "^3.0.2" "yallist": "^3.0.2"
@@ -42771,9 +42829,9 @@
} }
}, },
"node_modules/node-releases": { "node_modules/node-releases": {
"version": "2.0.13", "version": "2.0.14",
"dev": true, "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
"license": "MIT" "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw=="
}, },
"node_modules/nopt": { "node_modules/nopt": {
"version": "5.0.0", "version": "5.0.0",
@@ -43029,7 +43087,6 @@
}, },
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.0.0", "version": "1.0.0",
"dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/picomatch": { "node_modules/picomatch": {
@@ -43331,6 +43388,20 @@
"node": ">=0.10.0" "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": { "node_modules/react-complex-tree": {
"version": "2.3.4", "version": "2.3.4",
"license": "MIT", "license": "MIT",
@@ -44916,7 +44987,6 @@
}, },
"node_modules/update-browserslist-db": { "node_modules/update-browserslist-db": {
"version": "1.0.13", "version": "1.0.13",
"dev": true,
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@@ -45902,7 +45972,6 @@
}, },
"node_modules/yallist": { "node_modules/yallist": {
"version": "3.1.1", "version": "3.1.1",
"dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/yaml": { "node_modules/yaml": {

View File

@@ -65,6 +65,7 @@
"platform": "^1.3.6", "platform": "^1.3.6",
"qclone": "^1.2.0", "qclone": "^1.2.0",
"react": "18.2.0", "react": "18.2.0",
"react-avatar-editor": "^13.0.2",
"react-complex-tree": "^2.2.4", "react-complex-tree": "^2.2.4",
"react-day-picker": "^8.9.1", "react-day-picker": "^8.9.1",
"react-dom": "18.2.0", "react-dom": "18.2.0",
@@ -95,6 +96,7 @@
"@types/node-fetch": "^2.5.10", "@types/node-fetch": "^2.5.10",
"@types/platform": "^1.3.4", "@types/platform": "^1.3.4",
"@types/react": "^18.2.39", "@types/react": "^18.2.39",
"@types/react-avatar-editor": "^13.0.2",
"@types/react-dom": "^18.2.17", "@types/react-dom": "^18.2.17",
"@types/react-modal": "3.16.3", "@types/react-modal": "3.16.3",
"@types/tinycolor2": "^1.4.3", "@types/tinycolor2": "^1.4.3",

View File

@@ -33,8 +33,13 @@ import { ConfirmDialogProps } from "../dialogs/confirm";
import { getFormattedDate } from "@notesnook/common"; import { getFormattedDate } from "@notesnook/common";
import { downloadUpdate } from "../utils/updater"; import { downloadUpdate } from "../utils/updater";
import { ThemeMetadata } from "@notesnook/themes-server"; import { ThemeMetadata } from "@notesnook/themes-server";
import { Color, Reminder, Tag } from "@notesnook/core"; import {
import { AuthenticatorType } from "@notesnook/core/dist/api/user-manager"; Color,
Profile,
Reminder,
Tag,
AuthenticatorType
} from "@notesnook/core";
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
import { PasswordDialogProps } from "../dialogs/password-dialog"; 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() { export function showSettings() {
return showDialog("SettingsDialog", (Dialog, perform) => ( return showDialog("SettingsDialog", (Dialog, perform) => (
<Dialog onClose={(res: boolean) => perform(res)} /> <Dialog onClose={(res: boolean) => perform(res)} />

View File

@@ -35,7 +35,8 @@ import {
Login, Login,
Circle, Circle,
Icon, Icon,
Reminders Reminders,
User
} from "../icons"; } from "../icons";
import { AnimatedFlex } from "../animated"; import { AnimatedFlex } from "../animated";
import NavigationItem, { SortableNavigationItem } from "./navigation-item"; import NavigationItem, { SortableNavigationItem } from "./navigation-item";
@@ -134,6 +135,7 @@ function NavigationMenu(props: NavigationMenuProps) {
const shortcuts = useAppStore((store) => store.shortcuts); const shortcuts = useAppStore((store) => store.shortcuts);
const refreshNavItems = useAppStore((store) => store.refreshNavItems); const refreshNavItems = useAppStore((store) => store.refreshNavItems);
const isLoggedIn = useUserStore((store) => store.isLoggedIn); const isLoggedIn = useUserStore((store) => store.isLoggedIn);
const profile = useUserStore((store) => store.profile);
const isMobile = useMobile(); const isMobile = useMobile();
const theme = useThemeStore((store) => store.colorScheme); const theme = useThemeStore((store) => store.colorScheme);
const toggleNightMode = useThemeStore((store) => store.toggleColorScheme); const toggleNightMode = useThemeStore((store) => store.toggleColorScheme);
@@ -463,8 +465,9 @@ function NavigationMenu(props: NavigationMenuProps) {
id={settings.id} id={settings.id}
isTablet={isTablet} isTablet={isTablet}
key={settings.path} key={settings.path}
title={settings.title} title={profile?.fullName || settings.title}
icon={settings.icon} icon={profile?.fullName ? User : settings.icon}
image={profile?.fullName ? profile?.profilePicture : undefined}
onClick={() => { onClick={() => {
if (!isMobile && location === settings.path) if (!isMobile && location === settings.path)
return toggleNavigationContainer(); return toggleNavigationContainer();

View File

@@ -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/>. 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 { useStore as useAppStore } from "../../stores/app-store";
import { Menu } from "../../hooks/use-menu"; import { Menu } from "../../hooks/use-menu";
import useMobile from "../../hooks/use-mobile"; import useMobile from "../../hooks/use-mobile";
@@ -29,7 +29,8 @@ import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities"; import { CSS } from "@dnd-kit/utilities";
type NavigationItemProps = { type NavigationItemProps = {
icon: Icon; icon?: Icon;
image?: string;
color?: SchemeColors; color?: SchemeColors;
title: string; title: string;
isTablet?: boolean; isTablet?: boolean;
@@ -49,6 +50,7 @@ function NavigationItem(
) { ) {
const { const {
icon: Icon, icon: Icon,
image,
color, color,
title, title,
isLoading, isLoading,
@@ -121,11 +123,15 @@ function NavigationItem(
if (onClick) onClick(); if (onClick) onClick();
}} }}
> >
<Icon {image ? (
size={isTablet ? 16 : 15} <Image src={image} sx={{ borderRadius: 50, size: 20 }} />
color={color || (selected ? "icon-selected" : "icon")} ) : Icon ? (
rotate={isLoading} <Icon
/> size={isTablet ? 16 : 15}
color={color || (selected ? "icon-selected" : "icon")}
rotate={isLoading}
/>
) : null}
{isShortcut && ( {isShortcut && (
<Shortcut <Shortcut
size={8} size={8}

View 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>
);
}

View File

@@ -56,6 +56,7 @@ const BackupPasswordDialog = React.lazy(
() => import("./backup-password-dialog") () => import("./backup-password-dialog")
); );
const CreateColorDialog = React.lazy(() => import("./create-color-dialog")); const CreateColorDialog = React.lazy(() => import("./create-color-dialog"));
const EditProfileDialog = React.lazy(() => import("./edit-profile-dialog"));
export const Dialogs = { export const Dialogs = {
AddNotebookDialog, AddNotebookDialog,
@@ -85,5 +86,6 @@ export const Dialogs = {
SettingsDialog, SettingsDialog,
ThemeDetailsDialog, ThemeDetailsDialog,
BackupPasswordDialog, BackupPasswordDialog,
CreateColorDialog CreateColorDialog,
EditProfileDialog
}; };

View File

@@ -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/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { Flex, Text } from "@theme-ui/components"; import { Button, Flex, Image, Text } from "@theme-ui/components";
import { User } from "../../../components/icons"; import { Edit, User } from "../../../components/icons";
import { useStore as useUserStore } from "../../../stores/user-store"; import { useStore as useUserStore } from "../../../stores/user-store";
import { getObjectIdTimestamp } from "@notesnook/core/dist/utils/object-id"; import { getObjectIdTimestamp } from "@notesnook/core/dist/utils/object-id";
import { getFormattedDate } from "@notesnook/common"; import { getFormattedDate } from "@notesnook/common";
import { SUBSCRIPTION_STATUS } from "../../../common/constants"; import { SUBSCRIPTION_STATUS } from "../../../common/constants";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { useMemo } from "react"; import { useMemo } from "react";
import { showEditProfileDialog } from "../../../common/dialog-controller";
export function UserProfile() { export function UserProfile() {
const user = useUserStore((store) => store.user); const user = useUserStore((store) => store.user);
const profile = useUserStore((store) => store.profile);
const { const {
isTrial, isTrial,
@@ -88,42 +90,82 @@ export function UserProfile() {
sx={{ sx={{
borderRadius: "default", borderRadius: "default",
alignItems: "center", alignItems: "center",
justifyContent: "space-between",
bg: "var(--background-secondary)", bg: "var(--background-secondary)",
p: 2, p: 2,
mb: 4 mb: 4
}} }}
> >
<Flex <Flex sx={{ alignItems: "center" }}>
variant="columnCenter" <Flex
sx={{ variant="columnCenter"
bg: "shade",
mr: 2,
size: 60,
borderRadius: 80
}}
>
<User size={30} />
</Flex>
<Flex sx={{ flexDirection: "column" }}>
<Text
variant="subBody"
sx={{ sx={{
color: "accent" bg: "shade",
mr: 2,
size: 60,
borderRadius: 80,
overflow: "hidden"
}} }}
> >
{remainingDays > 0 && (isPro || isProCancelled) {profile?.profilePicture ? (
? `PRO` <Image
: remainingDays > 0 && isTrial sx={{ width: "100%", height: "100%", objectFit: "contain" }}
? "TRIAL" src={profile.profilePicture}
: isBeta />
? "BETA TESTER" ) : (
: "BASIC"} <User size={30} />
</Text> )}
<Text variant={"title"}>{user.email}</Text> </Flex>
<Text variant={"subBody"}> <Flex sx={{ flexDirection: "column" }}>
Member since {getFormattedDate(getObjectIdTimestamp(user.id), "date")} <Text
</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> </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> </Flex>
); );
} }

View File

@@ -31,7 +31,7 @@ import { hashNavigate } from "../navigation";
import { isUserPremium } from "../hooks/use-is-user-premium"; import { isUserPremium } from "../hooks/use-is-user-premium";
import { SUBSCRIPTION_STATUS } from "../common/constants"; import { SUBSCRIPTION_STATUS } from "../common/constants";
import { ANALYTICS_EVENTS, trackEvent } from "../utils/analytics"; 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> { class UserStore extends BaseStore<UserStore> {
isLoggedIn?: boolean; isLoggedIn?: boolean;
@@ -39,6 +39,7 @@ class UserStore extends BaseStore<UserStore> {
isSigningIn = false; isSigningIn = false;
user?: User = undefined; user?: User = undefined;
profile?: Profile;
counter = 0; counter = 0;
init = () => { init = () => {
@@ -47,32 +48,35 @@ class UserStore extends BaseStore<UserStore> {
window.location.replace("/sessionexpired"); window.location.replace("/sessionexpired");
}); });
db.user.getUser().then(async (user) => { db.user.getUser().then((user) => {
if (!user) { if (!user) {
this.set((state) => { this.set({ isLoggedIn: false });
state.isLoggedIn = false;
});
return; return;
} }
this.set((state) => { this.set({
state.user = user; user,
state.isLoggedIn = true; isLoggedIn: true
}); });
if (Config.get("sessionExpired")) EV.publish(EVENTS.userSessionExpired); if (Config.get("sessionExpired")) EV.publish(EVENTS.userSessionExpired);
}); });
db.user.getProfile().then((profile) => this.set({ profile }));
if (Config.get("sessionExpired")) return; if (Config.get("sessionExpired")) return;
return db.user.fetchUser().then(async (user) => { return db.user.fetchUser().then(async (user) => {
if (!user) return false; if (!user) return false;
EV.remove(EVENTS.userSubscriptionUpdated, EVENTS.userEmailConfirmed); const profile = await db.user.getProfile();
this.set((state) => { this.set({
state.user = user; profile,
state.isLoggedIn = true; user,
isLoggedIn: true
}); });
EV.remove(EVENTS.userSubscriptionUpdated, EVENTS.userEmailConfirmed);
EV.subscribe(EVENTS.userSubscriptionUpdated, (subscription) => { EV.subscribe(EVENTS.userSubscriptionUpdated, (subscription) => {
const wasUserPremium = isUserPremium(); const wasUserPremium = isUserPremium();
this.set((state) => { this.set((state) => {
@@ -107,18 +111,29 @@ class UserStore extends BaseStore<UserStore> {
refreshUser = async () => { refreshUser = async () => {
return db.user.fetchUser().then(async (user) => { 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)); this.set((state) => (state.isLoggingIn = true));
const { email, password, code, method } = form;
try { 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); return await db.user.authenticateMultiFactorCode(code, method);
} else if (password) { } else if ("password" in form) {
const { email, password } = form;
await db.user.authenticatePassword( await db.user.authenticatePassword(
email, email,
password, password,
@@ -129,15 +144,13 @@ class UserStore extends BaseStore<UserStore> {
if (skipInit) return true; if (skipInit) return true;
return this.init(); return this.init();
} else if (email) {
return await db.user.authenticateEmail(email);
} }
} finally { } finally {
this.set((state) => (state.isLoggingIn = false)); this.set((state) => (state.isLoggingIn = false));
} }
}; };
signup = (form) => { signup = (form: { email: string; password: string }) => {
this.set((state) => (state.isSigningIn = true)); this.set((state) => (state.isSigningIn = true));
return db.user return db.user
.signup(form.email.toLowerCase(), form.password) .signup(form.email.toLowerCase(), form.password)