Compare commits
4 Commits
swipe_to_s
...
improvedas
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da0832fc7d | ||
|
|
827ac9ddf7 | ||
|
|
2e35b1eeb4 | ||
|
|
65c72f6cf5 |
596
infra/staff/package-lock.json
generated
596
infra/staff/package-lock.json
generated
@@ -8,12 +8,16 @@
|
||||
"name": "staff",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@mui/icons-material": "^5.16.0",
|
||||
"date-fns": "^3.6.0",
|
||||
"react": "^18",
|
||||
"react-datepicker": "^7.1.0",
|
||||
"react-dom": "^18",
|
||||
"react-toastify": "^10.0.5",
|
||||
"zod": "^3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"@typescript-eslint/eslint-plugin": "^7",
|
||||
@@ -26,7 +30,7 @@
|
||||
"prettier": "^3",
|
||||
"prettier-plugin-organize-imports": "^3.2",
|
||||
"prettier-plugin-packagejson": "^2.5",
|
||||
"typescript": "^5.4.5",
|
||||
"typescript": "^5",
|
||||
"vite": "^5.2"
|
||||
}
|
||||
},
|
||||
@@ -339,6 +343,17 @@
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz",
|
||||
"integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.24.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.6.tgz",
|
||||
@@ -388,6 +403,43 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/cache": {
|
||||
"version": "11.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz",
|
||||
"integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@emotion/memoize": "^0.8.1",
|
||||
"@emotion/sheet": "^1.2.2",
|
||||
"@emotion/utils": "^1.2.1",
|
||||
"@emotion/weak-memoize": "^0.3.1",
|
||||
"stylis": "4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/memoize": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz",
|
||||
"integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@emotion/sheet": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz",
|
||||
"integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@emotion/utils": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz",
|
||||
"integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@emotion/weak-memoize": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz",
|
||||
"integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
|
||||
@@ -849,6 +901,54 @@
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/core": {
|
||||
"version": "1.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.4.tgz",
|
||||
"integrity": "sha512-a4IowK4QkXl4SCWTGUR0INAfEOX3wtsYw3rKK5InQEHMGObkR8Xk44qYQD9P4r6HHw0iIfK6GUKECmY8sTkqRA==",
|
||||
"dependencies": {
|
||||
"@floating-ui/utils": "^0.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/dom": {
|
||||
"version": "1.6.7",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.7.tgz",
|
||||
"integrity": "sha512-wmVfPG5o2xnKDU4jx/m4w5qva9FWHcnZ8BvzEe90D/RpwsJaTAVYPEPdQ8sbr/N8zZTAHlZUTQdqg8ZUbzHmng==",
|
||||
"dependencies": {
|
||||
"@floating-ui/core": "^1.6.0",
|
||||
"@floating-ui/utils": "^0.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/react": {
|
||||
"version": "0.26.19",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.19.tgz",
|
||||
"integrity": "sha512-Jk6zITdjjIvjO/VdQFvpRaD3qPwOHH6AoDHxjhpy+oK4KFgaSP871HYWUAPdnLmx1gQ+w/pB312co3tVml+BXA==",
|
||||
"dependencies": {
|
||||
"@floating-ui/react-dom": "^2.1.1",
|
||||
"@floating-ui/utils": "^0.2.4",
|
||||
"tabbable": "^6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/react-dom": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.1.tgz",
|
||||
"integrity": "sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg==",
|
||||
"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.4",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.4.tgz",
|
||||
"integrity": "sha512-dWO2pw8hhi+WrXq1YJy2yCuWoL20PddgGaqTgVe4cOS9Q6qklXCiA1tJEqX6BEwRNSCP84/afac9hd4MS+zEUA=="
|
||||
},
|
||||
"node_modules/@humanwhocodes/config-array": {
|
||||
"version": "0.11.14",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
|
||||
@@ -952,6 +1052,271 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/base": {
|
||||
"version": "5.0.0-beta.40",
|
||||
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz",
|
||||
"integrity": "sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.9",
|
||||
"@floating-ui/react-dom": "^2.0.8",
|
||||
"@mui/types": "^7.2.14",
|
||||
"@mui/utils": "^5.15.14",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"clsx": "^2.1.0",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^17.0.0 || ^18.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/core-downloads-tracker": {
|
||||
"version": "5.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.16.0.tgz",
|
||||
"integrity": "sha512-8SLffXYPRVpcZx5QzxNE8fytTqzp+IuU3deZbQWg/vSaTlDpR5YVrQ4qQtXTi5cRdhOufV5INylmwlKK+//nPw==",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/icons-material": {
|
||||
"version": "5.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.16.0.tgz",
|
||||
"integrity": "sha512-6ISoOhkp9w5gD0PEW9JklrcbyARDkFWNTBdwXZ1Oy5IGlyu9B0zG0hnUIe4H17IaF1Vgj6C8VI+v4tkSdK0veg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@mui/material": "^5.0.0",
|
||||
"@types/react": "^17.0.0 || ^18.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/material": {
|
||||
"version": "5.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.16.0.tgz",
|
||||
"integrity": "sha512-DbR1NckTLpjt9Zut9EGQ70th86HfN0BYQgyYro6aXQrNfjzSwe3BJS1AyBQ5mJ7TdL6YVRqohfukxj9JlqZZUg==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.9",
|
||||
"@mui/base": "5.0.0-beta.40",
|
||||
"@mui/core-downloads-tracker": "^5.16.0",
|
||||
"@mui/system": "^5.16.0",
|
||||
"@mui/types": "^7.2.14",
|
||||
"@mui/utils": "^5.16.0",
|
||||
"@types/react-transition-group": "^4.4.10",
|
||||
"clsx": "^2.1.0",
|
||||
"csstype": "^3.1.3",
|
||||
"prop-types": "^15.8.1",
|
||||
"react-is": "^18.2.0",
|
||||
"react-transition-group": "^4.4.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.5.0",
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"@types/react": "^17.0.0 || ^18.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@emotion/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@emotion/styled": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/material/node_modules/react-is": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
||||
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@mui/private-theming": {
|
||||
"version": "5.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.0.tgz",
|
||||
"integrity": "sha512-sYpubkO1MZOnxNyVOClrPNOTs0MfuRVVnAvCeMaOaXt6GimgQbnUcshYv2pSr6PFj+Mqzdff/FYOBceK8u5QgA==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.9",
|
||||
"@mui/utils": "^5.16.0",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^17.0.0 || ^18.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/styled-engine": {
|
||||
"version": "5.15.14",
|
||||
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.14.tgz",
|
||||
"integrity": "sha512-RILkuVD8gY6PvjZjqnWhz8fu68dVkqhM5+jYWfB5yhlSQKg+2rHkmEwm75XIeAqI3qwOndK6zELK5H6Zxn4NHw==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.9",
|
||||
"@emotion/cache": "^11.11.0",
|
||||
"csstype": "^3.1.3",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.4.1",
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"react": "^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@emotion/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@emotion/styled": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/system": {
|
||||
"version": "5.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.16.0.tgz",
|
||||
"integrity": "sha512-9YbkC2m3+pNumAvubYv+ijLtog6puJ0fJ6rYfzfLCM47pWrw3m+30nXNM8zMgDaKL6vpfWJcCXm+LPaWBpy7sw==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.9",
|
||||
"@mui/private-theming": "^5.16.0",
|
||||
"@mui/styled-engine": "^5.15.14",
|
||||
"@mui/types": "^7.2.14",
|
||||
"@mui/utils": "^5.16.0",
|
||||
"clsx": "^2.1.0",
|
||||
"csstype": "^3.1.3",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.5.0",
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"@types/react": "^17.0.0 || ^18.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@emotion/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@emotion/styled": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/types": {
|
||||
"version": "7.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.14.tgz",
|
||||
"integrity": "sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ==",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/utils": {
|
||||
"version": "5.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.16.0.tgz",
|
||||
"integrity": "sha512-kLLi5J1xY+mwtUlMb8Ubdxf4qFAA1+U7WPBvjM/qQ4CIwLCohNb0sHo1oYPufjSIH/Z9+dhVxD7dJlfGjd1AVA==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.9",
|
||||
"@types/prop-types": "^15.7.11",
|
||||
"prop-types": "^15.8.1",
|
||||
"react-is": "^18.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^17.0.0 || ^18.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/utils/node_modules/react-is": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
||||
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
@@ -999,6 +1364,80 @@
|
||||
"url": "https://opencollective.com/unts"
|
||||
}
|
||||
},
|
||||
"node_modules/@popperjs/core": {
|
||||
"version": "2.11.8",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/plugin-node-resolve": {
|
||||
"version": "15.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz",
|
||||
"integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@rollup/pluginutils": "^5.0.1",
|
||||
"@types/resolve": "1.20.2",
|
||||
"deepmerge": "^4.2.2",
|
||||
"is-builtin-module": "^3.2.1",
|
||||
"is-module": "^1.0.0",
|
||||
"resolve": "^1.22.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rollup": "^2.78.0||^3.0.0||^4.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"rollup": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/plugin-node-resolve/node_modules/resolve": {
|
||||
"version": "1.22.8",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-core-module": "^2.13.0",
|
||||
"path-parse": "^1.0.7",
|
||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"resolve": "bin/resolve"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/pluginutils": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz",
|
||||
"integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
"picomatch": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"rollup": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz",
|
||||
@@ -1257,14 +1696,12 @@
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
|
||||
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==",
|
||||
"dev": true
|
||||
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q=="
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz",
|
||||
"integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.0.2"
|
||||
@@ -1279,6 +1716,21 @@
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-transition-group": {
|
||||
"version": "4.4.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz",
|
||||
"integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/resolve": {
|
||||
"version": "1.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
|
||||
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.11.0.tgz",
|
||||
@@ -1778,6 +2230,18 @@
|
||||
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||
}
|
||||
},
|
||||
"node_modules/builtin-modules": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz",
|
||||
"integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
|
||||
@@ -1892,8 +2356,7 @@
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
|
||||
},
|
||||
"node_modules/data-view-buffer": {
|
||||
"version": "1.0.1",
|
||||
@@ -1946,6 +2409,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/date-fns": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
|
||||
"integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/kossnocorp"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.5",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
|
||||
@@ -1969,6 +2441,15 @@
|
||||
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/deepmerge": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
|
||||
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/define-data-property": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
||||
@@ -2048,6 +2529,16 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-helpers": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
|
||||
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.8.7",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.788",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.788.tgz",
|
||||
@@ -2616,6 +3107,12 @@
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/estree-walker": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/esutils": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
|
||||
@@ -3194,6 +3691,21 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-builtin-module": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz",
|
||||
"integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"builtin-modules": "^3.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-callable": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
|
||||
@@ -3308,6 +3820,12 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-module": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
|
||||
"integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/is-negative-zero": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz",
|
||||
@@ -3732,7 +4250,6 @@
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -4070,7 +4587,6 @@
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
@@ -4117,6 +4633,22 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-datepicker": {
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-7.3.0.tgz",
|
||||
"integrity": "sha512-EqRKLAtLZUTztiq6a+tjSjQX9ES0Xd229JPckAtyZZ4GoY3rtvNWAzkYZnQUf6zTWT50Ki0+t+W9VRQIkSJLfg==",
|
||||
"dependencies": {
|
||||
"@floating-ui/react": "^0.26.2",
|
||||
"clsx": "^2.1.0",
|
||||
"date-fns": "^3.3.1",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-onclickoutside": "^6.13.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.9.0 || ^17 || ^18",
|
||||
"react-dom": "^16.9.0 || ^17 || ^18"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
@@ -4132,8 +4664,20 @@
|
||||
"node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"node_modules/react-onclickoutside": {
|
||||
"version": "6.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.13.1.tgz",
|
||||
"integrity": "sha512-LdrrxK/Yh9zbBQdFbMTXPp3dTSN9B+9YJQucdDu3JNKRrbdU+H+/TVONJoWtOwy4II8Sqf1y/DTI6w/vGPYW0w==",
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://github.com/Pomax/react-onclickoutside/blob/master/FUNDING.md"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^15.5.x || ^16.x || ^17.x || ^18.x",
|
||||
"react-dom": "^15.5.x || ^16.x || ^17.x || ^18.x"
|
||||
}
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
"version": "0.14.2",
|
||||
@@ -4156,6 +4700,22 @@
|
||||
"react-dom": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/react-transition-group": {
|
||||
"version": "4.4.5",
|
||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
||||
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"dom-helpers": "^5.0.1",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.6.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.6.0",
|
||||
"react-dom": ">=16.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/reflect.getprototypeof": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz",
|
||||
@@ -4177,6 +4737,11 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"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.2",
|
||||
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz",
|
||||
@@ -4604,6 +5169,12 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/stylis": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
|
||||
"integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
@@ -4644,6 +5215,11 @@
|
||||
"url": "https://opencollective.com/unts"
|
||||
}
|
||||
},
|
||||
"node_modules/tabbable": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
|
||||
"integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="
|
||||
},
|
||||
"node_modules/text-table": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
|
||||
|
||||
@@ -11,9 +11,17 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@date-io/date-fns": "^3.0.0",
|
||||
"@emotion/react": "^11.11.4",
|
||||
"@emotion/styled": "^11.11.5",
|
||||
"@mui/icons-material": "^5.16.0",
|
||||
"@mui/lab": "^5.0.0-alpha.171",
|
||||
"@mui/material": "^5.16.0",
|
||||
"@mui/x-date-pickers": "^7.9.0",
|
||||
"@types/react-datepicker": "^6.2.0",
|
||||
"date-fns": "^3.6.0",
|
||||
"react": "^18",
|
||||
"react-datepicker": "^7.1.0",
|
||||
"react-datepicker": "^7.3.0",
|
||||
"react-dom": "^18",
|
||||
"react-toastify": "^10.0.5",
|
||||
"zod": "^3"
|
||||
|
||||
@@ -1,456 +1,306 @@
|
||||
.fetch-button-container {
|
||||
margin-right: 180px;
|
||||
margin-left: 10px;
|
||||
.container {
|
||||
position: relative; /* Ensure the parent is relatively positioned */
|
||||
height: 100vh; /* Full viewport height */
|
||||
width: 100vw; /* Full viewport width */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
.center-table {
|
||||
display: table;
|
||||
}
|
||||
|
||||
.input-form {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-left: 175px;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.horizontal-group {
|
||||
position: absolute; /* Use absolute positioning */
|
||||
top: 32px; /* 32px below the top of the page */
|
||||
left: 32px; /* 32px from the leftmost edge of the page */
|
||||
right: 32px;
|
||||
display: flex;
|
||||
align-items: center; /* Align items vertically centered */
|
||||
gap: 20px; /* Adjust the gap between elements as needed */
|
||||
background-color: #fafafa;
|
||||
height: 104px;
|
||||
width: 1375px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.fetch-button-container button {
|
||||
padding: 10px 20px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
background-color: #009879;
|
||||
background-color: #00b33c;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
#submitbtn {
|
||||
padding: 10px 20px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
background-color: #009879;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
#submitbtn:hover {
|
||||
background-color: #007c6c;
|
||||
width: 199px;
|
||||
height: 56px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.fetch-button-container button:hover {
|
||||
background-color: #007c6c;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
padding: 20px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
.link-text {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
padding: 0 16px; /* Add padding for better appearance */
|
||||
text-decoration: none; /* Remove underline */
|
||||
color: inherit; /* Inherit color from parent */
|
||||
font-weight: bold; /* Make the text bold */
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 10px 20px;
|
||||
font-size: 16px;
|
||||
.text-field-token {
|
||||
margin-left: 70px !important; /* Adjust margin for Token field */
|
||||
}
|
||||
|
||||
.text-field-email {
|
||||
margin-left: 20px !important; /* Adjust margin for Email field */
|
||||
}
|
||||
.duckie-container {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
.center-content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh; /* Adjust as necessary */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
margin-top: 20px;
|
||||
color: red;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.message {
|
||||
position: fixed;
|
||||
bottom: 10px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
background-color: #f0f0f0;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||
.duckie-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.container {
|
||||
position: relative; /* Ensure the parent is relatively positioned */
|
||||
height: 100vh; /* Full viewport height */
|
||||
width: 100vw; /* Full viewport width */
|
||||
display: flex;
|
||||
flex-direction: column; /* Add this */
|
||||
justify-content: center; /* Center vertically */
|
||||
align-items: center; /* Center horizontally */
|
||||
}
|
||||
|
||||
.message.error {
|
||||
background-color: #f44336;
|
||||
color: white;
|
||||
.input-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
z-index: 1; /* Ensure form is on top */
|
||||
}
|
||||
|
||||
.message.success {
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
.horizontal-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px; /* Adjust the gap between elements as needed */
|
||||
background-color: #fafafa;
|
||||
padding: 20px; /* Add padding around content */
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.dropdown-container {
|
||||
margin-bottom: 20px;
|
||||
position: relative; /* Ensure relative positioning for dropdown menu */
|
||||
}
|
||||
|
||||
.more-button {
|
||||
padding: 10px 20px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
background-color: #009879;
|
||||
.fetch-button-container button {
|
||||
background-color: #00b33c;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
margin-top: 1px;
|
||||
width: 199px;
|
||||
height: 56px;
|
||||
}
|
||||
|
||||
.more-button:hover {
|
||||
.fetch-button-container button:hover {
|
||||
background-color: #007c6c;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
position: absolute;
|
||||
top: calc(100% + 5px); /* Position right below the More button */
|
||||
left: 0;
|
||||
z-index: 100;
|
||||
display: block;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
max-height: 150px; /* Example: Set max height for scrollability */
|
||||
overflow-y: auto; /* Enable vertical scroll */
|
||||
}
|
||||
|
||||
/* Remove bullets from unordered list */
|
||||
.dropdown-menu ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.dropdown-menu button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.dropdown-menu button:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
.modal {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: #ffffff;
|
||||
padding: 20px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
z-index: 1000;
|
||||
max-width: 80%;
|
||||
max-height: 80%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
.link-text {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
padding: 0 16px; /* Add padding for better appearance */
|
||||
text-decoration: none; /* Remove underline */
|
||||
color: inherit; /* Inherit color from parent */
|
||||
font-weight: bold; /* Make the text bold */
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
.modal-header .close-btn {
|
||||
cursor: pointer;
|
||||
color: #777;
|
||||
font-size: 20px;
|
||||
.text-field-token {
|
||||
margin-left: 70px !important; /* Adjust margin for Token field */
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
margin-top: 10px;
|
||||
.text-field-email {
|
||||
margin-left: 50px !important; /* Adjust margin for Email field */
|
||||
}
|
||||
|
||||
/* Styles for draggable modal */
|
||||
.modal.draggable {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.modal.draggable .modal-header {
|
||||
cursor: move;
|
||||
}
|
||||
.popup {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: lightgreen;
|
||||
padding: 20px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
.center-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.popup-content div {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
:root {
|
||||
--popup-bg-color-light: #fff;
|
||||
--popup-bg-color-dark: #2c2c2c;
|
||||
--popup-border-color-light: #ccc;
|
||||
--popup-border-color-dark: #444;
|
||||
--popup-text-color-light: #000;
|
||||
--popup-text-color-dark: #fff;
|
||||
--popup-shadow-light: rgba(0, 0, 0, 0.1);
|
||||
--popup-shadow-dark: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.update-subscription-popup {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 400px;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--popup-bg-color-light);
|
||||
border: 1px solid var(--popup-border-color-light);
|
||||
padding: 20px;
|
||||
z-index: 1000;
|
||||
box-shadow: 0px 0px 10px var(--popup-shadow-light);
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
align-self: flex-end;
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
color: var(--popup-text-color-light);
|
||||
}
|
||||
|
||||
.popup-content h2 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.popup-content form label {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.popup-content form input,
|
||||
.popup-content form select {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
margin-top: 5px;
|
||||
background-color: var(--popup-bg-color-light);
|
||||
color: var(--popup-text-color-light);
|
||||
border: 1px solid var(--popup-border-color-light);
|
||||
}
|
||||
|
||||
.popup-content form button {
|
||||
padding: 10px 15px;
|
||||
margin-top: 10px;
|
||||
cursor: pointer;
|
||||
background-color: var(--popup-bg-color-light);
|
||||
color: var(--popup-text-color-light);
|
||||
border: 1px solid var(--popup-border-color-light);
|
||||
}
|
||||
|
||||
.custom-select {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.custom-select select {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
background-color: var(--popup-bg-color-light);
|
||||
color: var(--popup-text-color-light);
|
||||
border: 1px solid var(--popup-border-color-light);
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.custom-select::after {
|
||||
content: "\25BC";
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 10px;
|
||||
transform: translateY(-50%);
|
||||
pointer-events: none;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-top: 10px;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
.duckie-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.message.error {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
/* Example CSS to maintain tabs fixed position */
|
||||
.tabs-container {
|
||||
position: sticky;
|
||||
top: 20px; /* Adjust as needed */
|
||||
z-index: 1000; /* Ensure tabs are above other content */
|
||||
}
|
||||
.container {
|
||||
position: relative; /* Ensure the parent is relatively positioned */
|
||||
height: 100vh; /* Full viewport height */
|
||||
width: 100vw; /* Full viewport width */
|
||||
display: flex;
|
||||
flex-direction: column; /* Ensure children stack vertically */
|
||||
justify-content: center; /* Center vertically */
|
||||
align-items: center; /* Center horizontally */
|
||||
}
|
||||
|
||||
.message.success {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
.input-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
z-index: 1; /* Ensure form is on top */
|
||||
margin-bottom: 20px; /* Add space between form and tabs */
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.update-subscription-popup {
|
||||
background-color: var(--popup-bg-color-dark);
|
||||
border-color: var(--popup-border-color-dark);
|
||||
color: var(--popup-text-color-dark);
|
||||
box-shadow: 0px 0px 10px var(--popup-shadow-dark);
|
||||
}
|
||||
|
||||
.close-button {
|
||||
color: var(--popup-text-color-dark);
|
||||
}
|
||||
|
||||
.popup-content form input,
|
||||
.popup-content form select {
|
||||
background-color: var(--popup-bg-color-dark);
|
||||
color: var(--popup-text-color-dark);
|
||||
border: 1px solid var(--popup-border-color-dark);
|
||||
}
|
||||
|
||||
.popup-content form button {
|
||||
background-color: var(--popup-bg-color-dark);
|
||||
color: var(--popup-text-color-dark);
|
||||
border: 1px solid var(--popup-border-color-dark);
|
||||
}
|
||||
}
|
||||
:root {
|
||||
--popup-bg-color-light: #fff;
|
||||
--popup-bg-color-dark: #2c2c2c;
|
||||
--popup-border-color-light: #ccc;
|
||||
--popup-border-color-dark: #444;
|
||||
--popup-text-color-light: #000;
|
||||
--popup-text-color-dark: #fff;
|
||||
--popup-shadow-light: rgba(0, 0, 0, 0.1);
|
||||
--popup-shadow-dark: rgba(255, 255, 255, 0.1);
|
||||
.horizontal-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px; /* Adjust the gap between elements as needed */
|
||||
background-color: #fafafa;
|
||||
padding: 20px; /* Add padding around content */
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.update-subscription-popup {
|
||||
position: fixed;
|
||||
.fetch-button-container button {
|
||||
background-color: #00b33c;
|
||||
color: white;
|
||||
border: none;
|
||||
width: 199px;
|
||||
height: 56px;
|
||||
}
|
||||
|
||||
.fetch-button-container button:hover {
|
||||
background-color: #007c6c;
|
||||
}
|
||||
|
||||
.link-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 16px; /* Add padding for better appearance */
|
||||
text-decoration: none; /* Remove underline */
|
||||
color: inherit; /* Inherit color from parent */
|
||||
font-weight: bold; /* Make the text bold */
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
.text-field-token {
|
||||
margin-left: 70px !important; /* Adjust margin for Token field */
|
||||
}
|
||||
|
||||
.text-field-email {
|
||||
margin-left: 50px !important; /* Adjust margin for Email field */
|
||||
}
|
||||
|
||||
.center-content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--popup-bg-color-light);
|
||||
border: 1px solid var(--popup-border-color-light);
|
||||
padding: 20px;
|
||||
z-index: 1000;
|
||||
box-shadow: 0px 0px 10px var(--popup-shadow-light);
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
.duckie-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
align-self: flex-end;
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
color: var(--popup-text-color-light);
|
||||
.tabs-container {
|
||||
position: sticky; /* Make the tabs sticky */
|
||||
top: 0; /* Stick to the top of the viewport */
|
||||
z-index: 1000; /* Ensure tabs are above other content */
|
||||
background-color: #fafafa; /* Optional: Add background color to tabs */
|
||||
padding: 10px 20px; /* Optional: Add padding for better appearance */
|
||||
border-bottom: 1px solid #ccc; /* Optional: Add bottom border */
|
||||
}
|
||||
.dialog-popup-container {
|
||||
/* Styles for the overlay/background */
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5); /* Semi-transparent black */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.popup-content h2 {
|
||||
margin-top: 0;
|
||||
.dialog-popup {
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
.dialog-popup-header h2 {
|
||||
margin-bottom: 20px; /* Add space between heading and fields */
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
.dialog-popup-field {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.dialog-popup-field label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.form-group input,
|
||||
.form-group select {
|
||||
.dialog-popup-field input {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
background-color: var(--popup-bg-color-light);
|
||||
color: var(--popup-text-color-light);
|
||||
border: 1px solid var(--popup-border-color-light);
|
||||
border-radius: 5px;
|
||||
margin-top: 5px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.custom-select {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.custom-select select {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
.submit-button {
|
||||
background-color: #4caf50; /* Green */
|
||||
color: white;
|
||||
padding: 10px 15px;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
background-color: var(--popup-bg-color-light);
|
||||
color: var(--popup-text-color-light);
|
||||
border: 1px solid var(--popup-border-color-light);
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.custom-select::after {
|
||||
content: "\25BC";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 10px;
|
||||
transform: translateY(-50%);
|
||||
pointer-events: none;
|
||||
/* Specific styles for each field to match the image */
|
||||
.name-field input,
|
||||
.shortname-field input,
|
||||
.code-field input {
|
||||
width: calc(100% - 100px); /* Adjust as needed */
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-top: 10px;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.message.error {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.message.success {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.update-subscription-popup {
|
||||
background-color: var(--popup-bg-color-dark);
|
||||
border-color: var(--popup-border-color-dark);
|
||||
color: var(--popup-text-color-dark);
|
||||
box-shadow: 0px 0px 10px var(--popup-shadow-dark);
|
||||
}
|
||||
|
||||
.close-button {
|
||||
color: var(--popup-text-color-dark);
|
||||
}
|
||||
|
||||
.form-group input,
|
||||
.form-group select {
|
||||
background-color: var(--popup-bg-color-dark);
|
||||
color: var(--popup-text-color-dark);
|
||||
border: 1px solid var(--popup-border-color-dark);
|
||||
}
|
||||
|
||||
.form-group button {
|
||||
background-color: var(--popup-bg-color-dark);
|
||||
color: var(--popup-text-color-dark);
|
||||
border: 1px solid var(--popup-border-color-dark);
|
||||
}
|
||||
.active-field input {
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
@@ -1,160 +1,183 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Box from "@mui/material/Box";
|
||||
import Button from "@mui/material/Button";
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
import Tab from "@mui/material/Tab";
|
||||
import Tabs from "@mui/material/Tabs";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import * as React from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import "./App.css";
|
||||
import { Sidebar } from "./components/Sidebar";
|
||||
import type { UserData } from "./components/UserComponent";
|
||||
import UserComponent from "./components/UserComponent";
|
||||
import duckieimage from "./components/duckie.png";
|
||||
import { apiOrigin } from "./services/support";
|
||||
import S from "./utils/strings";
|
||||
|
||||
type User = Record<
|
||||
string,
|
||||
string | number | boolean | null | undefined | Record<string, unknown>
|
||||
>;
|
||||
type UserData = Record<string, User>;
|
||||
// Define and export email and token variables and their setter functions
|
||||
export let email = "";
|
||||
export let token = "";
|
||||
|
||||
export const App: React.FC = () => {
|
||||
const [token, setToken] = useState<string>("");
|
||||
const [email, setEmail] = useState<string>("");
|
||||
export const setEmail = (newEmail: string) => {
|
||||
email = newEmail;
|
||||
};
|
||||
|
||||
export const setToken = (newToken: string) => {
|
||||
token = newToken;
|
||||
};
|
||||
|
||||
export const getEmail = () => email;
|
||||
export const getToken = () => token;
|
||||
|
||||
interface User {
|
||||
ID: string;
|
||||
email: string;
|
||||
creationTime: number;
|
||||
}
|
||||
|
||||
interface Subscription {
|
||||
productID: string;
|
||||
paymentProvider: string;
|
||||
expiryTime: number;
|
||||
storage: number;
|
||||
}
|
||||
|
||||
interface Security {
|
||||
isEmailMFAEnabled: boolean;
|
||||
isTwoFactorEnabled: boolean;
|
||||
passkeys: string; // Replace with actual passkey value if available
|
||||
}
|
||||
|
||||
interface UserResponse {
|
||||
user: User;
|
||||
subscription: Subscription;
|
||||
details?: {
|
||||
usage?: number;
|
||||
storageBonus?: number;
|
||||
profileData: Security;
|
||||
};
|
||||
}
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [localEmail, setLocalEmail] = useState<string>(getEmail());
|
||||
const [localToken, setLocalToken] = useState<string>(getToken());
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<string>("");
|
||||
const [fetchSuccess, setFetchSuccess] = useState<boolean>(false);
|
||||
const [tabValue, setTabValue] = useState<number>(0);
|
||||
const [userData, setUserData] = useState<UserData | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isDataFetched, setIsDataFetched] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
const storedToken = localStorage.getItem("token");
|
||||
if (storedToken) {
|
||||
setToken(storedToken);
|
||||
setLocalToken(storedToken);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (token) {
|
||||
localStorage.setItem("token", token);
|
||||
if (localToken) {
|
||||
setToken(localToken);
|
||||
localStorage.setItem("token", localToken);
|
||||
} else {
|
||||
localStorage.removeItem("token");
|
||||
}
|
||||
}, [token]);
|
||||
}, [localToken]);
|
||||
|
||||
useEffect(() => {
|
||||
if (localEmail) {
|
||||
setEmail(localEmail);
|
||||
localStorage.setItem("email", localEmail);
|
||||
} else {
|
||||
localStorage.removeItem("email");
|
||||
}
|
||||
}, [localEmail]);
|
||||
|
||||
const fetchData = async () => {
|
||||
setLoading(true);
|
||||
setError("");
|
||||
setFetchSuccess(false);
|
||||
const startTime = Date.now();
|
||||
try {
|
||||
const encodedEmail = encodeURIComponent(email);
|
||||
const encodedToken = encodeURIComponent(token);
|
||||
const encodedEmail = encodeURIComponent(localEmail);
|
||||
const encodedToken = encodeURIComponent(localToken);
|
||||
const url = `${apiOrigin}/admin/user?email=${encodedEmail}&token=${encodedToken}`;
|
||||
console.log(`Fetching data from URL: ${url}`);
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error("Network response was not ok");
|
||||
}
|
||||
const userDataResponse = (await response.json()) as UserData;
|
||||
const userDataResponse: UserResponse =
|
||||
(await response.json()) as UserResponse;
|
||||
console.log("API Response:", userDataResponse);
|
||||
setUserData(userDataResponse);
|
||||
setError(null);
|
||||
setIsDataFetched(true);
|
||||
|
||||
const extractedUserData: UserData = {
|
||||
User: {
|
||||
"User ID": userDataResponse.user.ID || "None",
|
||||
Email: userDataResponse.user.email || "None",
|
||||
"Creation time":
|
||||
new Date(
|
||||
userDataResponse.user.creationTime / 1000,
|
||||
).toLocaleString() || "None",
|
||||
},
|
||||
Storage: {
|
||||
Total: userDataResponse.subscription.storage
|
||||
? userDataResponse.subscription.storage >= 1024 ** 3
|
||||
? `${(userDataResponse.subscription.storage / 1024 ** 3).toFixed(2)} GB`
|
||||
: `${(userDataResponse.subscription.storage / 1024 ** 2).toFixed(2)} MB`
|
||||
: "None",
|
||||
Consumed:
|
||||
userDataResponse.details?.usage !== undefined
|
||||
? userDataResponse.details.usage >= 1024 ** 3
|
||||
? `${(userDataResponse.details.usage / 1024 ** 3).toFixed(2)} GB`
|
||||
: `${(userDataResponse.details.usage / 1024 ** 2).toFixed(2)} MB`
|
||||
: "None",
|
||||
Bonus:
|
||||
userDataResponse.details?.storageBonus !== undefined
|
||||
? userDataResponse.details.storageBonus >= 1024 ** 3
|
||||
? `${(userDataResponse.details.storageBonus / 1024 ** 3).toFixed(2)} GB`
|
||||
: `${(userDataResponse.details.storageBonus / 1024 ** 2).toFixed(2)} MB`
|
||||
: "None",
|
||||
},
|
||||
Subscription: {
|
||||
"Product ID":
|
||||
userDataResponse.subscription.productID || "None",
|
||||
Provider:
|
||||
userDataResponse.subscription.paymentProvider || "None",
|
||||
"Expiry time":
|
||||
new Date(
|
||||
userDataResponse.subscription.expiryTime / 1000,
|
||||
).toLocaleString() || "None",
|
||||
},
|
||||
Security: {
|
||||
"Email MFA": userDataResponse.details?.profileData
|
||||
.isEmailMFAEnabled
|
||||
? "Enabled"
|
||||
: "Disabled",
|
||||
"Two factor 2FA": userDataResponse.details?.profileData
|
||||
.isTwoFactorEnabled
|
||||
? "Enabled"
|
||||
: "Disabled",
|
||||
Passkeys: "None", // Replace with actual passkey value if available
|
||||
},
|
||||
};
|
||||
|
||||
const elapsedTime = Date.now() - startTime;
|
||||
const delay = Math.max(3000 - elapsedTime, 0);
|
||||
setTimeout(() => {
|
||||
setLoading(false);
|
||||
setFetchSuccess(true);
|
||||
setUserData(extractedUserData);
|
||||
}, delay);
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
setError((error as Error).message);
|
||||
setIsDataFetched(false);
|
||||
const elapsedTime = Date.now() - startTime;
|
||||
const delay = Math.max(3000 - elapsedTime, 0);
|
||||
setTimeout(() => {
|
||||
setLoading(false);
|
||||
setError("Invalid token or email id");
|
||||
}, delay);
|
||||
}
|
||||
};
|
||||
|
||||
const renderAttributes = (
|
||||
data: Record<string, unknown> | User | null,
|
||||
): React.ReactNode => {
|
||||
if (!data) return null;
|
||||
|
||||
const nullAttributes: string[] = [];
|
||||
|
||||
const rows = Object.entries(data).map(([key, value]) => {
|
||||
console.log("Processing key:", key, "value:", value);
|
||||
|
||||
if (
|
||||
typeof value === "object" &&
|
||||
value !== null &&
|
||||
!Array.isArray(value)
|
||||
) {
|
||||
return (
|
||||
<React.Fragment key={key}>
|
||||
<tr>
|
||||
<td
|
||||
colSpan={2}
|
||||
style={{
|
||||
fontWeight: "bold",
|
||||
backgroundColor: "#f1f1f1",
|
||||
padding: "10px",
|
||||
}}
|
||||
>
|
||||
{key.toUpperCase()}
|
||||
</td>
|
||||
</tr>
|
||||
{renderAttributes(
|
||||
value as Record<string, unknown> | User,
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
} else {
|
||||
if (value === null) {
|
||||
nullAttributes.push(key);
|
||||
}
|
||||
|
||||
let displayValue: React.ReactNode;
|
||||
if (key === "expiryTime" && typeof value === "number") {
|
||||
displayValue = new Date(value / 1000).toLocaleString();
|
||||
} else if (
|
||||
key === "creationTime" &&
|
||||
typeof value === "number"
|
||||
) {
|
||||
displayValue = new Date(value / 1000).toLocaleString();
|
||||
} else if (key === "storage" && typeof value === "number") {
|
||||
displayValue = `${(value / 1024 ** 3).toFixed(2)} GB`;
|
||||
} else if (typeof value === "string") {
|
||||
try {
|
||||
const parsedValue = JSON.parse(
|
||||
value,
|
||||
) as React.ReactNode;
|
||||
displayValue = parsedValue;
|
||||
} catch (error) {
|
||||
displayValue = value;
|
||||
}
|
||||
} else if (typeof value === "object" && value !== null) {
|
||||
displayValue = JSON.stringify(value, null, 2);
|
||||
} else if (value === null) {
|
||||
displayValue = "null";
|
||||
} else if (
|
||||
typeof value === "boolean" ||
|
||||
typeof value === "number"
|
||||
) {
|
||||
displayValue = value.toString();
|
||||
} else if (typeof value === "undefined") {
|
||||
displayValue = "undefined";
|
||||
} else {
|
||||
displayValue = value as string;
|
||||
}
|
||||
|
||||
return (
|
||||
<tr key={key}>
|
||||
<td
|
||||
style={{
|
||||
padding: "10px",
|
||||
border: "1px solid #ddd",
|
||||
}}
|
||||
>
|
||||
{key}
|
||||
</td>
|
||||
<td
|
||||
style={{
|
||||
padding: "10px",
|
||||
border: "1px solid #ddd",
|
||||
}}
|
||||
>
|
||||
{displayValue}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
console.log("Attributes with null values:", nullAttributes);
|
||||
|
||||
return rows;
|
||||
};
|
||||
|
||||
const handleKeyPress = (event: React.KeyboardEvent<HTMLFormElement>) => {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
@@ -164,94 +187,135 @@ export const App: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleTabChange = (
|
||||
_event: React.SyntheticEvent,
|
||||
newValue: number,
|
||||
) => {
|
||||
setTabValue(newValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container center-table">
|
||||
<h1>{S.hello}</h1>
|
||||
<form className="input-form" onKeyPress={handleKeyPress}>
|
||||
<div className="input-group">
|
||||
<label>
|
||||
Token:
|
||||
<input
|
||||
type="text"
|
||||
value={token}
|
||||
onChange={(e) => setToken(e.target.value)}
|
||||
style={{
|
||||
padding: "10px",
|
||||
margin: "10px",
|
||||
width: "100%",
|
||||
<div className="horizontal-group">
|
||||
<a
|
||||
href="https://staff.ente.sh"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="link-text"
|
||||
>
|
||||
staff.ente.sh
|
||||
</a>
|
||||
|
||||
<TextField
|
||||
label="Token"
|
||||
value={localToken}
|
||||
onChange={(e) => {
|
||||
setLocalToken(e.target.value);
|
||||
setToken(e.target.value);
|
||||
}}
|
||||
size="medium"
|
||||
className="text-field-token"
|
||||
style={{ width: "350px" }}
|
||||
/>
|
||||
<TextField
|
||||
label="Email"
|
||||
value={localEmail}
|
||||
onChange={(e) => {
|
||||
setLocalEmail(e.target.value);
|
||||
setEmail(e.target.value);
|
||||
}}
|
||||
size="medium"
|
||||
className="text-field-email"
|
||||
style={{ width: "350px" }}
|
||||
/>
|
||||
<div className="fetch-button-container">
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => {
|
||||
fetchData().catch((error: unknown) =>
|
||||
console.error("Fetch data error:", error),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="input-group">
|
||||
<label>
|
||||
Email id:
|
||||
<input
|
||||
type="text"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
className="fetch-button"
|
||||
style={{
|
||||
padding: "10px",
|
||||
margin: "10px",
|
||||
width: "100%",
|
||||
padding: "0 16px",
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
>
|
||||
FETCH
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div className="content-wrapper">
|
||||
{isDataFetched && <Sidebar token={token} email={email} />}
|
||||
<div className="fetch-button-container">
|
||||
<button
|
||||
onClick={() => {
|
||||
fetchData().catch((error: unknown) =>
|
||||
console.error("Fetch data error:", error),
|
||||
);
|
||||
}}
|
||||
>
|
||||
FETCH
|
||||
</button>
|
||||
</div>
|
||||
<div className="content-container">
|
||||
{loading ? (
|
||||
<CircularProgress sx={{ color: "black" }} />
|
||||
) : error ? (
|
||||
<div className="error-message">{error}</div>
|
||||
) : fetchSuccess ? (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
maxWidth: "600px",
|
||||
bgcolor: "#FAFAFA",
|
||||
marginTop: "300px",
|
||||
borderRadius: "7px",
|
||||
position: "relative",
|
||||
zIndex: 1000,
|
||||
}}
|
||||
>
|
||||
<Tabs
|
||||
value={tabValue}
|
||||
onChange={handleTabChange}
|
||||
centered
|
||||
sx={{
|
||||
"& .MuiTabs-indicator": {
|
||||
backgroundColor: "#00B33C",
|
||||
height: "5px",
|
||||
borderRadius: "20px",
|
||||
},
|
||||
"& .MuiTab-root": {
|
||||
textTransform: "none",
|
||||
},
|
||||
"& .Mui-selected": {
|
||||
color: "black !important",
|
||||
},
|
||||
"& .MuiTab-root.Mui-selected": {
|
||||
color: "black !important",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Tab label="User" />
|
||||
<Tab label="Family" />
|
||||
<Tab label="Bonuses" />
|
||||
</Tabs>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
maxWidth: "600px",
|
||||
mt: 4,
|
||||
minHeight: "400px",
|
||||
}}
|
||||
>
|
||||
{tabValue === 0 && (
|
||||
<UserComponent userData={userData} />
|
||||
)}
|
||||
{tabValue === 1 && <div>Family tab content</div>}
|
||||
{tabValue === 2 && <div>Bonuses tab content</div>}
|
||||
</Box>
|
||||
</>
|
||||
) : (
|
||||
<div className="duckie-container">
|
||||
<img
|
||||
src={duckieimage}
|
||||
alt="Duckie"
|
||||
className="duckie-image"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<br />
|
||||
{error && <p style={{ color: "red" }}>{`Error: ${error}`}</p>}
|
||||
{userData && (
|
||||
<table
|
||||
style={{
|
||||
width: "100%",
|
||||
borderCollapse: "collapse",
|
||||
margin: "20px 0",
|
||||
fontSize: "1em",
|
||||
minWidth: "400px",
|
||||
boxShadow: "0 0 20px rgba(0, 0, 0, 0.15)",
|
||||
}}
|
||||
>
|
||||
<tbody>
|
||||
{Object.keys(userData).map((category) => (
|
||||
<React.Fragment key={category}>
|
||||
<tr>
|
||||
<td
|
||||
colSpan={2}
|
||||
style={{
|
||||
fontWeight: "bold",
|
||||
backgroundColor: "#f1f1f1",
|
||||
padding: "10px",
|
||||
}}
|
||||
>
|
||||
{category.toUpperCase()}
|
||||
</td>
|
||||
</tr>
|
||||
{renderAttributes(userData[category] ?? null)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
<footer className="footer">
|
||||
<p>
|
||||
<a href="https://help.ente.io">help.ente.io</a>
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
192
infra/staff/src/components/ChangeEmail.tsx
Normal file
192
infra/staff/src/components/ChangeEmail.tsx
Normal file
@@ -0,0 +1,192 @@
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
TextField,
|
||||
} from "@mui/material";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { getEmail, getToken } from "../App";
|
||||
import { apiOrigin } from "../services/support";
|
||||
interface ErrorResponse {
|
||||
message: string;
|
||||
}
|
||||
|
||||
interface ChangeEmailProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
interface UserDataResponse {
|
||||
subscription: {
|
||||
userID: string;
|
||||
} | null;
|
||||
}
|
||||
|
||||
const ChangeEmail: React.FC<ChangeEmailProps> = ({ open, onClose }) => {
|
||||
const [newEmail, setNewEmail] = useState<string>("");
|
||||
const [userID, setUserID] = useState<string>("");
|
||||
|
||||
useEffect(() => {
|
||||
const fetchUserID = async () => {
|
||||
const token = getToken();
|
||||
const email = getEmail();
|
||||
setNewEmail(email); // Set initial email state
|
||||
|
||||
const encodedEmail = encodeURIComponent(email);
|
||||
const encodedToken = encodeURIComponent(token);
|
||||
const url = `${apiOrigin}/admin/user?email=${encodedEmail}&token=${encodedToken}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-AUTH-TOKEN": token,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Network response was not ok");
|
||||
}
|
||||
|
||||
const data = (await response.json()) as UserDataResponse;
|
||||
if (data.subscription) {
|
||||
setUserID(data.subscription.userID); // Update userID state
|
||||
} else {
|
||||
throw new Error("Subscription data not found");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching user ID:", error);
|
||||
}
|
||||
};
|
||||
|
||||
if (open) {
|
||||
fetchUserID().catch((error: unknown) =>
|
||||
console.error("Error in fetchUserID:", error),
|
||||
);
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setNewEmail(event.target.value); // Update newEmail state on input change
|
||||
};
|
||||
|
||||
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
|
||||
const token = getToken();
|
||||
const url = `${apiOrigin}/admin/user/change-email?token=${token}`;
|
||||
|
||||
const body = {
|
||||
userID,
|
||||
email: newEmail,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-AUTH-TOKEN": token,
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
let errorData;
|
||||
try {
|
||||
errorData = (await response.json()) as ErrorResponse;
|
||||
} catch (error) {
|
||||
console.error("Error parsing error response:", error);
|
||||
}
|
||||
throw new Error(
|
||||
errorData?.message ?? "Network response was not ok",
|
||||
);
|
||||
}
|
||||
|
||||
console.log("Email updated successfully");
|
||||
onClose();
|
||||
} catch (error) {
|
||||
console.error("Error updating email:", error);
|
||||
}
|
||||
};
|
||||
const handleSubmitSync: React.FormEventHandler<HTMLFormElement> = (
|
||||
event,
|
||||
) => {
|
||||
handleSubmit(event).catch((error: unknown) => {
|
||||
console.error("Error in handleSubmit:", error);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
BackdropProps={{
|
||||
style: {
|
||||
backdropFilter: "blur(5px)",
|
||||
backgroundColor: "rgba(255, 255, 255, 0.8)",
|
||||
},
|
||||
}}
|
||||
PaperProps={{
|
||||
style: {
|
||||
width: "444px",
|
||||
height: "300px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DialogTitle style={{ marginBottom: "20px", marginTop: "20px" }}>
|
||||
Change Email
|
||||
<Button
|
||||
onClick={onClose}
|
||||
style={{ position: "absolute", right: 10, top: 10 }}
|
||||
>
|
||||
<CloseIcon style={{ color: "black" }} />
|
||||
</Button>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<form onSubmit={handleSubmitSync}>
|
||||
<div style={{ marginBottom: "16px" }}>
|
||||
<label
|
||||
htmlFor="newEmail"
|
||||
style={{
|
||||
textAlign: "left",
|
||||
display: "block",
|
||||
marginBottom: "4px",
|
||||
}}
|
||||
>
|
||||
Email
|
||||
</label>
|
||||
<TextField
|
||||
id="newEmail"
|
||||
name="newEmail"
|
||||
value={newEmail}
|
||||
onChange={handleChange}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
|
||||
<DialogActions
|
||||
style={{ justifyContent: "center", marginTop: "40px" }}
|
||||
>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
style={{
|
||||
backgroundColor: "#00B33C",
|
||||
color: "white",
|
||||
}}
|
||||
>
|
||||
Change Email
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChangeEmail;
|
||||
100
infra/staff/src/components/DeleteAccont.tsx
Normal file
100
infra/staff/src/components/DeleteAccont.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
Paper,
|
||||
} from "@mui/material";
|
||||
import React from "react";
|
||||
import { getEmail, getToken } from "../App"; // Import getEmail and getToken functions
|
||||
import { apiOrigin } from "../services/support";
|
||||
|
||||
interface DeleteAccountProps {
|
||||
open: boolean;
|
||||
handleClose: () => void;
|
||||
}
|
||||
|
||||
const DeleteAccount: React.FC<DeleteAccountProps> = ({ open, handleClose }) => {
|
||||
const handleDelete = async () => {
|
||||
try {
|
||||
const encodedEmail = encodeURIComponent(getEmail());
|
||||
console.log(encodedEmail);
|
||||
const encodedToken = encodeURIComponent(getToken());
|
||||
console.log(encodedToken);
|
||||
const deleteUrl = `${apiOrigin}/admin/user/delete?email=${encodedEmail}&token=${encodedToken}`;
|
||||
const response = await fetch(deleteUrl, { method: "DELETE" });
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to delete user account");
|
||||
}
|
||||
handleClose(); // Close dialog on successful delete
|
||||
console.log("Account deleted successfully");
|
||||
} catch (error) {
|
||||
console.error("Error deleting user account:", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
PaperComponent={Paper}
|
||||
sx={{
|
||||
width: "499px",
|
||||
height: "286px",
|
||||
margin: "auto",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
BackdropProps={{
|
||||
style: {
|
||||
backgroundColor: "rgba(255, 255, 255, 0.9)", // Semi-transparent backdrop
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">
|
||||
{"Delete Account?"}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
Are you sure you want to delete the account?
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ justifyContent: "center" }}>
|
||||
<Button
|
||||
onClick={handleClose}
|
||||
sx={{
|
||||
bgcolor: "white",
|
||||
color: "black",
|
||||
"&:hover": { bgcolor: "#FAFAFA" },
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
handleDelete().catch((error: unknown) =>
|
||||
console.error("Fetch data error:", error),
|
||||
);
|
||||
}}
|
||||
sx={{
|
||||
bgcolor: "#F4473D",
|
||||
color: "white",
|
||||
"&:hover": { bgcolor: "#E53935" },
|
||||
}}
|
||||
>
|
||||
Delete{" "}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeleteAccount;
|
||||
156
infra/staff/src/components/Disable2FA.tsx
Normal file
156
infra/staff/src/components/Disable2FA.tsx
Normal file
@@ -0,0 +1,156 @@
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
Paper,
|
||||
} from "@mui/material";
|
||||
import React, { useState } from "react";
|
||||
import { getEmail, getToken } from "../App"; // Import getEmail and getToken functions
|
||||
import { apiOrigin } from "../services/support";
|
||||
|
||||
interface UserData {
|
||||
subscription?: {
|
||||
userID: string;
|
||||
// Add other properties as per your API response structure
|
||||
};
|
||||
// Add other properties as per your API response structure
|
||||
}
|
||||
|
||||
interface Disable2FAProps {
|
||||
open: boolean;
|
||||
handleClose: () => void;
|
||||
handleDisable2FA: () => void; // Callback to handle 2FA disablement
|
||||
}
|
||||
|
||||
const Disable2FA: React.FC<Disable2FAProps> = ({
|
||||
open,
|
||||
handleClose,
|
||||
handleDisable2FA,
|
||||
}) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleDisable = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const email = getEmail();
|
||||
const token = getToken();
|
||||
|
||||
if (!email) {
|
||||
throw new Error("Email not found");
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
throw new Error("Token not found");
|
||||
}
|
||||
|
||||
const encodedEmail = encodeURIComponent(email);
|
||||
const encodedToken = encodeURIComponent(token);
|
||||
|
||||
// Fetch user data
|
||||
const userUrl = `${apiOrigin}/admin/user?email=${encodedEmail}&token=${encodedToken}`;
|
||||
const userResponse = await fetch(userUrl);
|
||||
if (!userResponse.ok) {
|
||||
throw new Error("Failed to fetch user data");
|
||||
}
|
||||
const userData = (await userResponse.json()) as UserData;
|
||||
const userId = userData.subscription?.userID;
|
||||
|
||||
if (!userId) {
|
||||
throw new Error("User ID not found");
|
||||
}
|
||||
|
||||
// Disable 2FA
|
||||
const disableUrl = `${apiOrigin}/admin/user/disable-2fa?token=${encodedToken}`;
|
||||
const body = JSON.stringify({ userId });
|
||||
const disableResponse = await fetch(disableUrl, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: body,
|
||||
});
|
||||
|
||||
if (!disableResponse.ok) {
|
||||
const errorResponse = await disableResponse.text();
|
||||
throw new Error(`Failed to disable 2FA: ${errorResponse}`);
|
||||
}
|
||||
|
||||
handleDisable2FA(); // Notify parent component of successful disable
|
||||
handleClose(); // Close dialog on successful disable
|
||||
console.log("2FA disabled successfully");
|
||||
} catch (error) {
|
||||
console.error("Error disabling 2FA:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
handleClose(); // Close dialog
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
PaperComponent={Paper}
|
||||
sx={{
|
||||
width: "499px",
|
||||
height: "286px",
|
||||
margin: "auto",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
BackdropProps={{
|
||||
style: {
|
||||
backgroundColor: "rgba(255, 255, 255, 0.9)", // Semi-transparent backdrop
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">
|
||||
{"Disable 2FA?"}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
Are you sure you want to disable 2FA for this account?
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ justifyContent: "center" }}>
|
||||
<Button
|
||||
onClick={handleCancel}
|
||||
sx={{
|
||||
bgcolor: "white",
|
||||
color: "black",
|
||||
"&:hover": { bgcolor: "#FAFAFA" },
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
handleDisable().catch((error: unknown) =>
|
||||
console.error(error),
|
||||
);
|
||||
}}
|
||||
sx={{
|
||||
bgcolor: "#F4473D",
|
||||
color: "white",
|
||||
"&:hover": { bgcolor: "#E53935" },
|
||||
}}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? "Disabling..." : "Disable"}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Disable2FA;
|
||||
@@ -1,256 +0,0 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import "../App.css";
|
||||
import { apiOrigin } from "../services/support";
|
||||
import UpdateSubscription from "./UpdateSubscription"; // Import the UpdateSubscription component
|
||||
|
||||
interface SidebarProps {
|
||||
token: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
interface UserData {
|
||||
user: {
|
||||
ID: string;
|
||||
};
|
||||
}
|
||||
interface ActionResponse {
|
||||
success?: boolean;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export const Sidebar: React.FC<SidebarProps> = ({ token, email }) => {
|
||||
const [userId, setUserId] = useState<string | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [message, setMessage] = useState<string | null>(null);
|
||||
const [dropdownVisible, setDropdownVisible] = useState<boolean>(false);
|
||||
const [showUpdateSubscription, setShowUpdateSubscription] =
|
||||
useState<boolean>(false); // State to control UpdateSubscription popup
|
||||
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
dropdownRef.current &&
|
||||
!dropdownRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setDropdownVisible(false);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchData = async (): Promise<string | null> => {
|
||||
if (!email || !token) {
|
||||
setError("Email or token is missing.");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const url = `${apiOrigin}/admin/user?email=${encodeURIComponent(email)}&token=${encodeURIComponent(token)}`;
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error("Network response was not ok");
|
||||
}
|
||||
const userDataResponse = (await response.json()) as UserData;
|
||||
const fetchedUserId = userDataResponse.user.ID;
|
||||
if (!fetchedUserId) {
|
||||
throw new Error("User ID not found in response");
|
||||
}
|
||||
setUserId(fetchedUserId);
|
||||
setError(null);
|
||||
return fetchedUserId;
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
setError(
|
||||
error instanceof Error && typeof error.message === "string"
|
||||
? error.message
|
||||
: "An unexpected error occurred",
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
setError(null);
|
||||
}, 1000);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const performAction = async (userId: string, action: string) => {
|
||||
try {
|
||||
const actionUrls: Record<string, string> = {
|
||||
Disable2FA: "/admin/user/disable-2fa",
|
||||
DisablePasskeys: "/admin/user/disable-passkeys",
|
||||
Closefamily: "/admin/user/close-family",
|
||||
};
|
||||
|
||||
const url = `${apiOrigin}${actionUrls[action]}?id=${encodeURIComponent(userId)}&token=${encodeURIComponent(token)}`;
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ userId }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Network response was not ok: ${response.status}`,
|
||||
);
|
||||
}
|
||||
|
||||
const result = (await response.json()) as ActionResponse;
|
||||
console.log("API Response:", result);
|
||||
|
||||
setMessage(`${action} completed successfully`);
|
||||
setError(null);
|
||||
setTimeout(() => {
|
||||
setMessage(null);
|
||||
}, 1000);
|
||||
setDropdownVisible(false);
|
||||
} catch (error) {
|
||||
console.error(`Error ${action}:`, error);
|
||||
setError(
|
||||
error instanceof Error && typeof error.message === "string"
|
||||
? error.message
|
||||
: "An unexpected error occurred",
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
setError(null);
|
||||
}, 1000);
|
||||
setMessage(null);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteUser = async () => {
|
||||
try {
|
||||
const url = `${apiOrigin}/admin/user/delete?email=${encodeURIComponent(email)}&token=${encodeURIComponent(token)}`;
|
||||
const response = await fetch(url, {
|
||||
method: "DELETE",
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Network response was not ok: ${response.status}`,
|
||||
);
|
||||
}
|
||||
|
||||
setMessage("Delete Account completed successfully");
|
||||
setError(null);
|
||||
setTimeout(() => {
|
||||
setMessage(null);
|
||||
}, 1000);
|
||||
setDropdownVisible(false);
|
||||
} catch (error) {
|
||||
console.error(`Error deleting account:`, error);
|
||||
setError(
|
||||
error instanceof Error && typeof error.message === "string"
|
||||
? error.message
|
||||
: "An unexpected error occurred",
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
setError(null);
|
||||
}, 1000);
|
||||
setMessage(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleActionClick = async (action: string) => {
|
||||
try {
|
||||
if (action === "UpdateSubscription") {
|
||||
const fetchedUserId = await fetchData();
|
||||
if (fetchedUserId) {
|
||||
setShowUpdateSubscription(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (action === "DeleteAccount") {
|
||||
await deleteUser();
|
||||
return;
|
||||
}
|
||||
|
||||
const fetchedUserId = await fetchData();
|
||||
if (!fetchedUserId) {
|
||||
throw new Error("Incorrect email id or token");
|
||||
}
|
||||
|
||||
await performAction(fetchedUserId, action);
|
||||
} catch (error) {
|
||||
console.error(`Error performing ${action}:`, error);
|
||||
setError(
|
||||
error instanceof Error && typeof error.message === "string"
|
||||
? error.message
|
||||
: "An unexpected error occurred",
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
setError(null);
|
||||
}, 1000);
|
||||
setMessage(null);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleDropdown = () => {
|
||||
setDropdownVisible(!dropdownVisible);
|
||||
};
|
||||
|
||||
const dropdownOptions = [
|
||||
{ value: "Disable2FA", label: "Disable 2FA" },
|
||||
{ value: "Closefamily", label: "Close Family" },
|
||||
{ value: "DisablePasskeys", label: "Disable Passkeys" },
|
||||
{ value: "DeleteAccount", label: "Delete Account" },
|
||||
{ value: "UpdateSubscription", label: "Update Subscription" }, // New option added here
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="sidebar">
|
||||
<div className="dropdown-container">
|
||||
<button className="more-button" onClick={toggleDropdown}>
|
||||
MORE
|
||||
</button>
|
||||
{dropdownVisible && (
|
||||
<div className="dropdown-menu" ref={dropdownRef}>
|
||||
<ul>
|
||||
{dropdownOptions.map((option) => (
|
||||
<li key={option.value}>
|
||||
<button
|
||||
onClick={() => {
|
||||
handleActionClick(
|
||||
option.value,
|
||||
).catch((error: unknown) =>
|
||||
console.error(
|
||||
"Error handling action:",
|
||||
error,
|
||||
),
|
||||
);
|
||||
}}
|
||||
>
|
||||
{option.label}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{(error ?? message) && (
|
||||
<div className={`message ${error ? "error" : "success"}`}>
|
||||
{error ? `Error: ${error}` : `Success: ${message}`}
|
||||
</div>
|
||||
)}
|
||||
{showUpdateSubscription && userId && (
|
||||
<UpdateSubscription
|
||||
token={token}
|
||||
userId={userId}
|
||||
onClose={() => setShowUpdateSubscription(false)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Sidebar;
|
||||
@@ -1,178 +1,366 @@
|
||||
import CalendarTodayIcon from "@mui/icons-material/CalendarToday";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import Button from "@mui/material/Button";
|
||||
import Dialog from "@mui/material/Dialog";
|
||||
import DialogActions from "@mui/material/DialogActions";
|
||||
import DialogContent from "@mui/material/DialogContent";
|
||||
import DialogTitle from "@mui/material/DialogTitle";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import InputAdornment from "@mui/material/InputAdornment";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import Select, { type SelectChangeEvent } from "@mui/material/Select";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import DatePicker from "react-datepicker";
|
||||
import "react-datepicker/dist/react-datepicker.css";
|
||||
import "../App.css";
|
||||
import { getEmail, getToken } from "../App";
|
||||
import { apiOrigin } from "../services/support";
|
||||
interface Subscription {
|
||||
productID: string;
|
||||
paymentProvider: string;
|
||||
storage: number;
|
||||
originalTransactionID: string;
|
||||
expiryTime: number;
|
||||
userID: string;
|
||||
}
|
||||
|
||||
interface UserDataResponse {
|
||||
subscription: Subscription | null;
|
||||
}
|
||||
|
||||
interface UpdateSubscriptionProps {
|
||||
token: string;
|
||||
userId: string;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const UpdateSubscription: React.FC<UpdateSubscriptionProps> = ({
|
||||
token,
|
||||
userId,
|
||||
interface FormValues {
|
||||
productId: string;
|
||||
provider: string;
|
||||
storage: number;
|
||||
transactionId: string;
|
||||
expiryTime: string | Date | null;
|
||||
userId: string;
|
||||
}
|
||||
|
||||
const UpdateSubscription: React.FC<UpdateSubscriptionProps> = ({
|
||||
open,
|
||||
onClose,
|
||||
}) => {
|
||||
const [expiryTime, setExpiryTime] = useState<Date | null>(null);
|
||||
const [productId, setProductId] = useState<string>("50gb_monthly");
|
||||
const [paymentProvider, setPaymentProvider] = useState<string>("bitpay");
|
||||
const [transactionId, setTransactionId] = useState<string>("");
|
||||
const [message, setMessage] = useState<string | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [storage, setStorage] = useState<number | "">("");
|
||||
const [values, setValues] = useState<FormValues>({
|
||||
productId: "",
|
||||
provider: "",
|
||||
storage: 0,
|
||||
transactionId: "",
|
||||
expiryTime: "",
|
||||
userId: "",
|
||||
});
|
||||
|
||||
const [isDatePickerOpen, setIsDatePickerOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (productId === "50gb_yearly" || productId === "50gb_monthly") {
|
||||
setStorage(50 * 1024 * 1024 * 1024);
|
||||
} else if (
|
||||
productId === "200gb_yearly" ||
|
||||
productId === "200gb_monthly"
|
||||
) {
|
||||
setStorage(200 * 1024 * 1024 * 1024);
|
||||
} else if (
|
||||
productId === "500gb_yearly" ||
|
||||
productId === "500gb_monthly"
|
||||
) {
|
||||
setStorage(500 * 1024 * 1024 * 1024);
|
||||
} else if (
|
||||
productId === "2000gb_yearly" ||
|
||||
productId === "2000gb_monthly"
|
||||
) {
|
||||
setStorage(2000 * 1024 * 1024 * 1024);
|
||||
} else {
|
||||
setStorage("");
|
||||
}
|
||||
}, [productId]);
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const email = getEmail();
|
||||
const token = getToken();
|
||||
const encodedEmail = encodeURIComponent(email);
|
||||
const encodedToken = encodeURIComponent(token);
|
||||
const url = `${apiOrigin}/admin/user?email=${encodedEmail}&token=${encodedToken}`;
|
||||
|
||||
const handleSubmit = async (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error("Network response was not ok");
|
||||
}
|
||||
const userDataResponse =
|
||||
(await response.json()) as UserDataResponse;
|
||||
|
||||
const expiryTimeTimestamp = expiryTime
|
||||
? expiryTime.getTime() * 1000
|
||||
: "";
|
||||
if (!userDataResponse.subscription) {
|
||||
throw new Error("Subscription data not found");
|
||||
}
|
||||
|
||||
const url = `${apiOrigin}/admin/user/subscription`;
|
||||
const body = {
|
||||
userId,
|
||||
storage,
|
||||
expiryTime: expiryTimeTimestamp,
|
||||
productId,
|
||||
paymentProvider,
|
||||
transactionId,
|
||||
const expiryTime = new Date(
|
||||
userDataResponse.subscription.expiryTime / 1000,
|
||||
);
|
||||
|
||||
setValues({
|
||||
productId: userDataResponse.subscription.productID || "",
|
||||
provider:
|
||||
userDataResponse.subscription.paymentProvider || "",
|
||||
storage:
|
||||
userDataResponse.subscription.storage /
|
||||
(1024 * 1024 * 1024) || 0,
|
||||
transactionId:
|
||||
userDataResponse.subscription.originalTransactionID ||
|
||||
"",
|
||||
expiryTime: expiryTime,
|
||||
userId: userDataResponse.subscription.userID || "",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-AUTH-TOKEN": token,
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
fetchData().catch((error: unknown) => {
|
||||
console.error("Unhandled promise rejection:", error);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleCalendarClick = () => {
|
||||
setIsDatePickerOpen(true);
|
||||
};
|
||||
|
||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { name, value } = event.target;
|
||||
setValues({
|
||||
...values,
|
||||
[name]: name === "storage" ? parseInt(value, 10) : value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleChangeProvider = (event: SelectChangeEvent) => {
|
||||
const { name, value } = event.target;
|
||||
|
||||
if (name) {
|
||||
setValues({
|
||||
...values,
|
||||
[name]: value,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Network response was not ok: ${response.status}`,
|
||||
);
|
||||
}
|
||||
|
||||
setMessage("Subscription updated successfully");
|
||||
setError(null);
|
||||
setTimeout(() => {
|
||||
setMessage(null);
|
||||
onClose();
|
||||
}, 1000);
|
||||
} catch (error) {
|
||||
console.error("Error updating subscription:", error);
|
||||
setError(
|
||||
error instanceof Error && typeof error.message === "string"
|
||||
? error.message
|
||||
: "An unexpected error occurred",
|
||||
);
|
||||
setTimeout(() => {
|
||||
setError(null);
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmitWrapper = (event: React.FormEvent) => {
|
||||
handleSubmit(event).catch((error: unknown) => {
|
||||
console.error("Error in handleSubmit:", error);
|
||||
const handleDatePickerChange = (date: Date | null) => {
|
||||
setValues({
|
||||
...values,
|
||||
expiryTime: date,
|
||||
});
|
||||
setIsDatePickerOpen(false);
|
||||
};
|
||||
|
||||
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
(async () => {
|
||||
const token = getToken();
|
||||
const url = `${apiOrigin}/admin/user/subscription`;
|
||||
|
||||
let expiryTime = null;
|
||||
if (values.expiryTime instanceof Date) {
|
||||
const utcExpiryTime = new Date(values.expiryTime);
|
||||
expiryTime = utcExpiryTime.getTime() * 1000;
|
||||
}
|
||||
|
||||
const body = {
|
||||
userId: values.userId,
|
||||
storage: values.storage * (1024 * 1024 * 1024),
|
||||
expiryTime: expiryTime,
|
||||
productId: values.productId,
|
||||
paymentProvider: values.provider,
|
||||
transactionId: values.transactionId,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-AUTH-TOKEN": token,
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Network response was not ok");
|
||||
}
|
||||
console.log("Subscription updated successfully");
|
||||
onClose();
|
||||
} catch (error) {
|
||||
console.error("Error updating subscription:", error);
|
||||
}
|
||||
})().catch((error: unknown) => {
|
||||
console.error("Unhandled promise rejection:", error);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="update-subscription-popup">
|
||||
<div className="popup-content">
|
||||
<button className="close-button" onClick={onClose}>
|
||||
X
|
||||
</button>
|
||||
<h2>Update Subscription</h2>
|
||||
<form onSubmit={handleSubmitWrapper}>
|
||||
<div className="form-group">
|
||||
<label htmlFor="expiry-time">Expiry Time:</label>
|
||||
<DatePicker
|
||||
id="expiry-time"
|
||||
selected={expiryTime}
|
||||
onChange={(date) => setExpiryTime(date)}
|
||||
dateFormat="dd/MM/yyyy"
|
||||
showYearDropdown
|
||||
scrollableYearDropdown
|
||||
yearDropdownItemNumber={15}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="product-id">Choose Your Plan:</label>
|
||||
<select
|
||||
id="product-id"
|
||||
value={productId}
|
||||
onChange={(e) => setProductId(e.target.value)}
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
BackdropProps={{
|
||||
style: {
|
||||
backdropFilter: "blur(5px)",
|
||||
backgroundColor: "rgba(255, 255, 255, 0.8)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DialogTitle style={{ marginBottom: "20px", marginTop: "20px" }}>
|
||||
Update Subscription
|
||||
<Button
|
||||
onClick={onClose}
|
||||
style={{ position: "absolute", right: 10, top: 10 }}
|
||||
>
|
||||
<CloseIcon style={{ color: "black" }} />
|
||||
</Button>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Grid container spacing={4}>
|
||||
<Grid item xs={6}>
|
||||
<div style={{ marginBottom: "8px" }}>
|
||||
<label
|
||||
htmlFor="productId"
|
||||
style={{
|
||||
textAlign: "left",
|
||||
display: "block",
|
||||
marginBottom: "4px",
|
||||
}}
|
||||
>
|
||||
Product ID
|
||||
</label>
|
||||
<TextField
|
||||
id="productId"
|
||||
name="productId"
|
||||
value={values.productId}
|
||||
onChange={handleChange}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<div style={{ marginBottom: "8px" }}>
|
||||
<label
|
||||
htmlFor="provider"
|
||||
style={{
|
||||
textAlign: "left",
|
||||
display: "block",
|
||||
marginBottom: "4px",
|
||||
}}
|
||||
>
|
||||
Provider
|
||||
</label>
|
||||
<Select
|
||||
id="provider"
|
||||
name="provider"
|
||||
value={values.provider}
|
||||
onChange={handleChangeProvider}
|
||||
fullWidth
|
||||
style={{ textAlign: "left" }}
|
||||
>
|
||||
<MenuItem value="stripe">Stripe</MenuItem>
|
||||
<MenuItem value="paypal">PayPal</MenuItem>
|
||||
<MenuItem value="bitpay">BitPay</MenuItem>
|
||||
</Select>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<div style={{ marginBottom: "8px" }}>
|
||||
<label
|
||||
htmlFor="storage"
|
||||
style={{
|
||||
textAlign: "left",
|
||||
display: "block",
|
||||
marginBottom: "4px",
|
||||
}}
|
||||
>
|
||||
Storage (GB)
|
||||
</label>
|
||||
<TextField
|
||||
id="storage"
|
||||
name="storage"
|
||||
type="number"
|
||||
value={values.storage}
|
||||
onChange={handleChange}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<div style={{ marginBottom: "8px" }}>
|
||||
<label
|
||||
htmlFor="transactionId"
|
||||
style={{
|
||||
textAlign: "left",
|
||||
display: "block",
|
||||
marginBottom: "4px",
|
||||
}}
|
||||
>
|
||||
Transaction ID
|
||||
</label>
|
||||
<TextField
|
||||
id="transactionId"
|
||||
name="transactionId"
|
||||
value={values.transactionId}
|
||||
onChange={handleChange}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<div style={{ marginBottom: "8px" }}>
|
||||
<label
|
||||
htmlFor="expiryTime"
|
||||
style={{
|
||||
textAlign: "left",
|
||||
display: "block",
|
||||
marginBottom: "4px",
|
||||
}}
|
||||
>
|
||||
Expiry Time
|
||||
</label>
|
||||
<TextField
|
||||
id="expiryTime"
|
||||
name="expiryTime"
|
||||
value={
|
||||
values.expiryTime instanceof Date
|
||||
? values.expiryTime.toLocaleDateString(
|
||||
"en-GB",
|
||||
)
|
||||
: ""
|
||||
}
|
||||
onClick={handleCalendarClick}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<CalendarTodayIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
readOnly: true,
|
||||
}}
|
||||
fullWidth
|
||||
/>
|
||||
{isDatePickerOpen && (
|
||||
<DatePicker
|
||||
showYearDropdown
|
||||
scrollableYearDropdown
|
||||
yearDropdownItemNumber={15}
|
||||
selected={
|
||||
values.expiryTime instanceof Date
|
||||
? values.expiryTime
|
||||
: null
|
||||
}
|
||||
onChange={handleDatePickerChange}
|
||||
onClickOutside={() =>
|
||||
setIsDatePickerOpen(false)
|
||||
}
|
||||
withPortal
|
||||
inline
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<DialogActions style={{ justifyContent: "center" }}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
style={{
|
||||
backgroundColor: "#00B33C",
|
||||
color: "white",
|
||||
}}
|
||||
>
|
||||
<option value="50gb_monthly">50GB/Month</option>
|
||||
<option value="50gb_yearly">50GB/Year</option>
|
||||
<option value="200gb_monthly">200GB/Month</option>
|
||||
<option value="200gb_yearly">200GB/Year</option>
|
||||
<option value="500gb_monthly">500GB/Month</option>
|
||||
<option value="500gb_yearly">500GB/Year</option>
|
||||
<option value="2000gb_monthly">2000GB/Month</option>
|
||||
<option value="2000gb_yearly">2000GB/Year</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="payment-provider">
|
||||
Payment Provider:
|
||||
</label>
|
||||
<select
|
||||
id="payment-provider"
|
||||
value={paymentProvider}
|
||||
onChange={(e) => setPaymentProvider(e.target.value)}
|
||||
>
|
||||
<option value="bitpay">BitPay</option>
|
||||
<option value="paypal">PayPal</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="transaction-id">Transaction ID:</label>
|
||||
<input
|
||||
id="transaction-id"
|
||||
type="text"
|
||||
value={transactionId}
|
||||
onChange={(e) => setTransactionId(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<button type="submit" id="submitbtn">
|
||||
Update
|
||||
</button>
|
||||
Update
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</form>
|
||||
{(error ?? message) && (
|
||||
<div className={`message ${error ? "error" : "success"}`}>
|
||||
{error ? `Error: ${error}` : `Success: ${message}`}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
335
infra/staff/src/components/UserComponent.tsx
Normal file
335
infra/staff/src/components/UserComponent.tsx
Normal file
@@ -0,0 +1,335 @@
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import EditIcon from "@mui/icons-material/Edit";
|
||||
import Box from "@mui/material/Box";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import Paper from "@mui/material/Paper";
|
||||
import Switch from "@mui/material/Switch";
|
||||
import Table from "@mui/material/Table";
|
||||
import TableBody from "@mui/material/TableBody";
|
||||
import TableCell from "@mui/material/TableCell";
|
||||
import TableContainer from "@mui/material/TableContainer";
|
||||
import TableRow from "@mui/material/TableRow";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import * as React from "react";
|
||||
import ChangeEmail from "./ChangeEmail";
|
||||
import DeleteAccount from "./DeleteAccont";
|
||||
import Disable2FA from "./Disable2FA";
|
||||
import UpdateSubscription from "./UpdateSubscription";
|
||||
|
||||
export interface UserData {
|
||||
User: Record<string, string>;
|
||||
Storage: Record<string, string>;
|
||||
Subscription: Record<string, string>;
|
||||
Security: Record<string, string>;
|
||||
}
|
||||
|
||||
interface UserComponentProps {
|
||||
userData: UserData | null;
|
||||
}
|
||||
|
||||
const UserComponent: React.FC<UserComponentProps> = ({ userData }) => {
|
||||
const [deleteAccountOpen, setDeleteAccountOpen] = React.useState(false);
|
||||
const [disable2FAOpen, setDisable2FAOpen] = React.useState(false);
|
||||
const [twoFactorEnabled, setTwoFactorEnabled] = React.useState(false);
|
||||
const [is2FADisabled, setIs2FADisabled] = React.useState(false);
|
||||
const [updateSubscriptionOpen, setUpdateSubscriptionOpen] =
|
||||
React.useState(false);
|
||||
const [changeEmailOpen, setChangeEmailOpen] = React.useState(false); // State for ChangeEmail dialog
|
||||
|
||||
React.useEffect(() => {
|
||||
if (userData?.Security["Two factor 2FA"] === "Enabled") {
|
||||
setTwoFactorEnabled(true);
|
||||
} else {
|
||||
setTwoFactorEnabled(false);
|
||||
}
|
||||
}, [userData]);
|
||||
|
||||
const handleEditEmail = () => {
|
||||
console.log("Edit Email clicked");
|
||||
setChangeEmailOpen(true);
|
||||
};
|
||||
|
||||
const handleCloseChangeEmail = () => {
|
||||
setChangeEmailOpen(false); // Close ChangeEmail dialog
|
||||
};
|
||||
|
||||
const handleDeleteAccountClick = () => {
|
||||
setDeleteAccountOpen(true);
|
||||
};
|
||||
|
||||
const handleCloseDeleteAccount = () => {
|
||||
setDeleteAccountOpen(false);
|
||||
};
|
||||
|
||||
const handleOpenDisable2FA = () => {
|
||||
setDisable2FAOpen(true);
|
||||
};
|
||||
|
||||
const handleCloseDisable2FA = () => {
|
||||
setDisable2FAOpen(false);
|
||||
};
|
||||
|
||||
const handleDisable2FA = () => {
|
||||
setIs2FADisabled(true);
|
||||
};
|
||||
|
||||
const handleCancelDisable2FA = () => {
|
||||
setTwoFactorEnabled(true);
|
||||
handleCloseDisable2FA();
|
||||
};
|
||||
|
||||
const handleEditSubscription = () => {
|
||||
setUpdateSubscriptionOpen(true);
|
||||
};
|
||||
|
||||
const handleCloseUpdateSubscription = () => {
|
||||
setUpdateSubscriptionOpen(false);
|
||||
};
|
||||
|
||||
if (!userData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid container spacing={6} justifyContent="center">
|
||||
{Object.entries(userData).map(([title, data]) => (
|
||||
<Grid item xs={12} sm={10} md={6} key={title}>
|
||||
<TableContainer
|
||||
component={Paper}
|
||||
variant="outlined"
|
||||
sx={{
|
||||
minHeight: 300,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
marginBottom: "20px",
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
padding: "13px",
|
||||
"&:not(:last-child)": {
|
||||
marginBottom: "40px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
padding: "16px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h6"
|
||||
component="div"
|
||||
sx={{
|
||||
fontWeight: "bold",
|
||||
textAlign: "center",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
{title === "User" && (
|
||||
<IconButton
|
||||
edge="start"
|
||||
aria-label="delete"
|
||||
onClick={handleDeleteAccountClick}
|
||||
>
|
||||
<DeleteIcon style={{ color: "" }} />
|
||||
</IconButton>
|
||||
)}
|
||||
{title === "Subscription" && (
|
||||
<IconButton
|
||||
edge="end"
|
||||
aria-label="edit"
|
||||
onClick={handleEditSubscription}
|
||||
>
|
||||
<EditIcon style={{ color: "black" }} />
|
||||
</IconButton>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Table
|
||||
sx={{
|
||||
width: "100%",
|
||||
tableLayout: "fixed",
|
||||
height: "100%",
|
||||
borderBottom: "none",
|
||||
}}
|
||||
aria-label={title}
|
||||
>
|
||||
<TableBody>
|
||||
{Object.entries(
|
||||
data as Record<string, string>,
|
||||
).map(([label, value], index) => (
|
||||
<TableRow key={label}>
|
||||
<TableCell
|
||||
component="th"
|
||||
scope="row"
|
||||
style={{
|
||||
padding: "16px",
|
||||
borderBottom:
|
||||
index === 1 || index === 0
|
||||
? "1px solid rgba(224, 224, 224, 1)"
|
||||
: "none",
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
align="right"
|
||||
style={{
|
||||
padding: "10px",
|
||||
borderBottom:
|
||||
index === 1 || index === 0
|
||||
? "1px solid rgba(224, 224, 224, 1)"
|
||||
: "none",
|
||||
}}
|
||||
>
|
||||
{label === "Email" ? (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent:
|
||||
"flex-end",
|
||||
}}
|
||||
>
|
||||
<Typography>
|
||||
{value}
|
||||
</Typography>
|
||||
<IconButton
|
||||
edge="end"
|
||||
aria-label="edit-email"
|
||||
onClick={
|
||||
handleEditEmail
|
||||
}
|
||||
>
|
||||
<EditIcon
|
||||
style={{
|
||||
color: "black",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
</Box>
|
||||
) : typeof value === "string" ? (
|
||||
label === "Two factor 2FA" ? (
|
||||
is2FADisabled ||
|
||||
value === "Disabled" ? (
|
||||
<Typography
|
||||
sx={{
|
||||
textAlign:
|
||||
"center",
|
||||
width: "100%",
|
||||
paddingLeft:
|
||||
"30px",
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
</Typography>
|
||||
) : (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems:
|
||||
"center",
|
||||
justifyContent:
|
||||
"center",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography>
|
||||
{value}
|
||||
</Typography>
|
||||
{value ===
|
||||
"Enabled" && (
|
||||
<Switch
|
||||
checked={
|
||||
twoFactorEnabled
|
||||
}
|
||||
onChange={(
|
||||
e,
|
||||
) => {
|
||||
const isChecked =
|
||||
e
|
||||
.target
|
||||
.checked;
|
||||
setTwoFactorEnabled(
|
||||
isChecked,
|
||||
);
|
||||
if (
|
||||
!isChecked
|
||||
) {
|
||||
handleOpenDisable2FA();
|
||||
}
|
||||
}}
|
||||
sx={{
|
||||
"& .MuiSwitch-switchBase.Mui-checked":
|
||||
{
|
||||
color: "#00B33C",
|
||||
"&:hover":
|
||||
{
|
||||
backgroundColor:
|
||||
"rgba(0, 179, 60, 0.08)",
|
||||
},
|
||||
},
|
||||
"& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track":
|
||||
{
|
||||
backgroundColor:
|
||||
"#00B33C",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
) : (
|
||||
<Typography>
|
||||
{value}
|
||||
</Typography>
|
||||
)
|
||||
) : (
|
||||
<Typography>
|
||||
{String(value)}
|
||||
</Typography>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Grid>
|
||||
))}
|
||||
|
||||
{/* Render DeleteAccount dialog */}
|
||||
<DeleteAccount
|
||||
open={deleteAccountOpen}
|
||||
handleClose={handleCloseDeleteAccount}
|
||||
/>
|
||||
|
||||
{/* Render Disable2FA dialog */}
|
||||
<Disable2FA
|
||||
open={disable2FAOpen}
|
||||
handleClose={handleCancelDisable2FA}
|
||||
handleDisable2FA={handleDisable2FA}
|
||||
/>
|
||||
|
||||
{/* Render UpdateSubscription dialog */}
|
||||
<UpdateSubscription
|
||||
open={updateSubscriptionOpen}
|
||||
onClose={handleCloseUpdateSubscription}
|
||||
/>
|
||||
|
||||
{/* Render ChangeEmail dialog */}
|
||||
<ChangeEmail
|
||||
open={changeEmailOpen}
|
||||
onClose={handleCloseChangeEmail}
|
||||
/>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserComponent;
|
||||
BIN
infra/staff/src/components/duckie.png
Normal file
BIN
infra/staff/src/components/duckie.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.5 KiB |
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { App } from "./App";
|
||||
import App from "./App";
|
||||
import "./styles/globals.css";
|
||||
|
||||
const root = document.getElementById("root");
|
||||
|
||||
Reference in New Issue
Block a user